From bef8327982931f66cae9ac2c46e1a0774627e69f Mon Sep 17 00:00:00 2001 From: Patrik Dufresne Date: Fri, 16 Feb 2018 18:07:15 -0500 Subject: [PATCH] Fix timezone issue. When using RdiffDate, we need to show the localtime to user. Basically, the rdiff backup date without timezone. This change also make possible to run testcases without enforcing the timezone. --- .travis.yml | 3 - Jenkinsfile | 2 - rdiffweb/librdiff.py | 118 +++++++++++++--------------- rdiffweb/page_status.py | 20 ++--- rdiffweb/plugins/graphs/__init__.py | 2 +- rdiffweb/rdw_templating.py | 14 +--- rdiffweb/templates/browse.html | 2 +- rdiffweb/tests/test_librdiff.py | 13 ++- 8 files changed, 78 insertions(+), 96 deletions(-) diff --git a/.travis.yml b/.travis.yml index 17f7c36e..b72b430e 100644 --- a/.travis.yml +++ b/.travis.yml @@ -30,9 +30,6 @@ matrix: env: CHERRYPY=3.2 before_install: - # Enforce timezone - - echo 'Canada/Eastern' | sudo tee /etc/timezone - - sudo dpkg-reconfigure --frontend noninteractive tzdata # Install requirements - sudo apt-get install python-pysqlite2 rdiff-backup - sudo pip install tox-travis diff --git a/Jenkinsfile b/Jenkinsfile index d54838ac..7a790d1c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -23,8 +23,6 @@ for (z in axisCherrypy) { deleteDir() // Checkout checkout scm - echo 'Enforce timezone for tests to work.' - sh 'ln -snf /usr/share/zoneinfo/America/Montreal /etc/localtime && echo "America/Montreal" > /etc/timezone' echo 'Upgrade python and install dependencies to avoid compiling from sources.' sh 'apt-get update && apt-get -qq install python-pysqlite2 libldap2-dev libsasl2-dev rdiff-backup node-less' sh 'pip install pip setuptools tox --upgrade' diff --git a/rdiffweb/librdiff.py b/rdiffweb/librdiff.py index 77d94fe1..27d994b6 100755 --- a/rdiffweb/librdiff.py +++ b/rdiffweb/librdiff.py @@ -125,21 +125,18 @@ class RdiffTime(object): assert value is None or isinstance(value, int) or isinstance(value, str) if value is None: # Get GMT time. - self.timeInSeconds = int(time.time()) - self.tzOffset = tz_offset or 0 + self._time_seconds = int(time.time()) + self._tz_offset = 0 elif isinstance(value, int): - self.timeInSeconds = value - self.tzOffset = tz_offset or 0 + self._time_seconds = value + self._tz_offset = tz_offset or 0 + elif isinstance(RdiffTime, int): + self._time_seconds = value._time_seconds + self._tz_offset = value._tz_offset else: - self._initFromString(value) + self._from_str(value) - def initFromMidnightUTC(self, daysFromToday): - self.timeInSeconds = time.time() - self.timeInSeconds -= self.timeInSeconds % (24 * 60 * 60) - self.timeInSeconds += daysFromToday * 24 * 60 * 60 - self.tzOffset = 0 - - def _initFromString(self, timeString): + def _from_str(self, timeString): try: date, daytime = timeString[:19].split("T") year, month, day = list(map(int, date.split("-"))) @@ -152,54 +149,44 @@ class RdiffTime(object): assert 0 <= second <= 61 # leap seconds timetuple = (year, month, day, hour, minute, second, -1, -1, 0) - self.timeInSeconds = calendar.timegm(timetuple) - self.tzOffset = self._tzdtoseconds(timeString[19:]) - self.getTimeZoneString() # to get assertions there + self._time_seconds = calendar.timegm(timetuple) + self._tz_offset = self._tzdtoseconds(timeString[19:]) + self._tz_str() # to get assertions there except (TypeError, ValueError, AssertionError): raise ValueError(timeString) - def getLocalDaysSinceEpoch(self): - return self.getLocalSeconds() // (24 * 60 * 60) - - def getLocalSeconds(self): - return self.timeInSeconds + def get_local_day_since_epoch(self): + return self._time_seconds // (24 * 60 * 60) - def getSeconds(self): - return self.timeInSeconds - self.tzOffset - - def getDisplayString(self): - value = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(self.getLocalSeconds())) - if isinstance(value, bytes): - value = value.decode(encoding='latin1') - return value + def epoch(self): + return self._time_seconds - self._tz_offset - def getTimeZoneString(self): - if self.tzOffset: - tzinfo = self._getTimeZoneDisplayInfo() - return "%s%s:%s" % (tzinfo["plusMinus"], tzinfo["hours"], tzinfo["minutes"]) + def _tz_str(self): + if self._tz_offset: + hours, minutes = divmod(old_div(abs(self._tz_offset), 60), 60) + assert 0 <= hours <= 23 + assert 0 <= minutes <= 59 + if self._tz_offset > 0: + plusMinus = "+" + else: + plusMinus = "-" + return "%s%s:%s" % (plusMinus, "%02d" % hours, "%02d" % minutes) else: return "Z" - def setTime(self, hour, minute, second): - year = time.gmtime(self.timeInSeconds)[0] - month = time.gmtime(self.timeInSeconds)[1] - day = time.gmtime(self.timeInSeconds)[2] - self.timeInSeconds = calendar.timegm( + def set_time(self, hour, minute, second): + year = time.gmtime(self._time_seconds)[0] + month = time.gmtime(self._time_seconds)[1] + day = time.gmtime(self._time_seconds)[2] + self._time_seconds = calendar.timegm( (year, month, day, hour, minute, second, -1, -1, 0)) - def _getTimeZoneDisplayInfo(self): - hours, minutes = divmod(old_div(abs(self.tzOffset), 60), 60) - assert 0 <= hours <= 23 - assert 0 <= minutes <= 59 - - if self.tzOffset > 0: - plusMinus = "+" - else: - plusMinus = "-" - return {"plusMinus": plusMinus, - "hours": "%02d" % hours, - "minutes": "%02d" % minutes} + def strftime(self, dateformat): + value = time.strftime(dateformat, time.gmtime(self._time_seconds)) + if isinstance(value, bytes): + value = value.decode(encoding='latin1') + return value def _tzdtoseconds(self, tzd): """Given w3 compliant TZD, converts it to number of seconds from UTC""" @@ -218,60 +205,64 @@ class RdiffTime(object): def __add__(self, other): """Support plus (+) timedelta""" assert isinstance(other, timedelta) - return RdiffTime(self.timeInSeconds + int(other.total_seconds()), self.tzOffset) + return RdiffTime(self._time_seconds + int(other.total_seconds()), self._tz_offset) def __sub__(self, other): """Support minus (-) timedelta""" assert isinstance(other, timedelta) or isinstance(other, RdiffTime) # Sub with timedelta, return RdiffTime if isinstance(other, timedelta): - return RdiffTime(self.timeInSeconds - int(other.total_seconds()), self.tzOffset) + return RdiffTime(self._time_seconds - int(other.total_seconds()), self._tz_offset) # Sub with RdiffTime, return timedelta if isinstance(other, RdiffTime): - return timedelta(seconds=self.timeInSeconds - other.timeInSeconds) + return timedelta(seconds=self._time_seconds - other._time_seconds) def __int__(self): """Return this date as seconds since epoch.""" - return self.timeInSeconds + return self.epoch() def __lt__(self, other): assert isinstance(other, RdiffTime) - return self.getSeconds() < other.getSeconds() + return self.epoch() < other.epoch() def __le__(self, other): assert isinstance(other, RdiffTime) - return self.getSeconds() <= other.getSeconds() + return self.epoch() <= other.epoch() def __gt__(self, other): assert isinstance(other, RdiffTime) - return self.getSeconds() > other.getSeconds() + return self.epoch() > other.epoch() def __ge__(self, other): assert isinstance(other, RdiffTime) - return self.getSeconds() >= other.getSeconds() + return self.epoch() >= other.epoch() def __cmp__(self, other): assert isinstance(other, RdiffTime) - return cmp(self.getSeconds(), other.getSeconds()) + return cmp(self.epoch(), other.epoch()) def __eq__(self, other): return (isinstance(other, RdiffTime) and - self.getSeconds() == other.getSeconds()) + self.epoch() == other.epoch()) def __hash__(self): - return hash(self.getSeconds()) + return hash(self.epoch()) def __str__(self): """return utf-8 string""" - return self.getDisplayString() + value = time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(self._time_seconds)) + if isinstance(value, bytes): + value = value.decode(encoding='latin1') + return value + self._tz_str() def __repr__(self): """return second since epoch""" - return str(self.getSeconds()) + return "RdiffTime('" + str(self) + "')" # Interfaced objects # + class DirEntry(object): """Includes name, isDir, fileSize, exists, and dict (changeDates) of sorted @@ -1014,7 +1005,7 @@ class RdiffRepo(object): # Convert the date into epoch. if isinstance(restore_date, RdiffTime): - restore_date = restore_date.getSeconds() + restore_date = restore_date.epoch() # Define a nice filename for the archive or file to be created. if path == b"": @@ -1176,6 +1167,7 @@ class RdiffRepo(object): return bytes([int(match.group()[1:])]) except: return match.group + # Remove quote using regex return re.sub(b";[0-9]{3}", unquoted_char, name, re.S) diff --git a/rdiffweb/page_status.py b/rdiffweb/page_status.py index 620062fc..ca98b8b2 100644 --- a/rdiffweb/page_status.py +++ b/rdiffweb/page_status.py @@ -22,6 +22,7 @@ from __future__ import unicode_literals from builtins import bytes from builtins import str import cherrypy +from datetime import timedelta import logging from rdiffweb import librdiff @@ -29,7 +30,6 @@ from rdiffweb import page_main from rdiffweb import rdw_helpers from rdiffweb.rdw_helpers import unquote_url - # Define the logger logger = logging.getLogger(__name__) @@ -122,23 +122,19 @@ class StatusPage(page_main.MainPage): # Set the start and end time to be the start and end of the day, # respectively, to get all entries for that day - startTime = librdiff.RdiffTime() - startTime.timeInSeconds = date.timeInSeconds - startTime.tzOffset = date.tzOffset - startTime.setTime(0, 0, 0) + startTime = librdiff.RdiffTime(date) + startTime.set_time(0, 0, 0) - endTime = librdiff.RdiffTime() - endTime.timeInSeconds = date.timeInSeconds - endTime.tzOffset = date.tzOffset - endTime.setTime(23, 59, 59) + endTime = librdiff.RdiffTime(date) + endTime.set_time(23, 59, 59) return self._getUserMessages(userRepos, True, False, startTime, endTime) def _get_recent_user_messages(self, failuresOnly): user_repos = self.app.currentuser.repos - asOfDate = librdiff.RdiffTime() - asOfDate.initFromMidnightUTC(-5) + + asOfDate = librdiff.RdiffTime() - timedelta(days=5) return self._getUserMessages(user_repos, not failuresOnly, True, asOfDate, None) @@ -176,7 +172,7 @@ class StatusPage(page_main.MainPage): if successfulBackups: lastSuccessDate = successfulBackups[0]["date"] successfulBackups = rdw_helpers.groupby( - successfulBackups, lambda x: x["date"].getLocalDaysSinceEpoch()) + successfulBackups, lambda x: x["date"].get_local_day_since_epoch()) userMessages = [] diff --git a/rdiffweb/plugins/graphs/__init__.py b/rdiffweb/plugins/graphs/__init__.py index 9dce598c..e2ac434a 100644 --- a/rdiffweb/plugins/graphs/__init__.py +++ b/rdiffweb/plugins/graphs/__init__.py @@ -83,7 +83,7 @@ class GraphsPage(page_main.MainPage): yield '\n' # Content for d, s in iteritems(repo_obj.session_statistics): - yield str(d.getSeconds()) + yield str(d.epoch()) for attr in attrs: yield ',' yield str(getattr(s, attr)) diff --git a/rdiffweb/rdw_templating.py b/rdiffweb/rdw_templating.py index 224ed708..179a4f32 100755 --- a/rdiffweb/rdw_templating.py +++ b/rdiffweb/rdw_templating.py @@ -33,7 +33,6 @@ from rdiffweb import i18n from rdiffweb import librdiff from rdiffweb import rdw_helpers - # Define the logger logger = logging.getLogger(__name__) @@ -95,15 +94,10 @@ def do_filter(sequence, attribute_name): def do_format_datetime(value, dateformat='%Y-%m-%d %H:%M'): """Used to format an epoch into local time.""" - if not value: return "" - - if isinstance(value, librdiff.RdiffTime): - value = value.getSeconds() - - # TODO Try to figure out the time zone name (it's a ) - return time.strftime(dateformat, time.localtime(value)) + assert isinstance(value, librdiff.RdiffTime) + return value.strftime(dateformat) def do_format_filesize(value, binary=True): @@ -186,7 +180,7 @@ def url_for_restore(repo, path, date, kind=None): url.append(rdw_helpers.quote_url(path)) # Append date url.append("?date=") - url.append(str(date.getSeconds())) + url.append(str(date.epoch())) if kind: url.append("&kind=%s" % kind) return ''.join(url) @@ -213,7 +207,7 @@ def url_for_status_entry(date, repo=None): url.append("/") if date: url.append("?date=") - url.append(str(date.getSeconds())) + url.append(str(date.epoch())) return ''.join(url) diff --git a/rdiffweb/templates/browse.html b/rdiffweb/templates/browse.html index a241a115..3f1b071e 100644 --- a/rdiffweb/templates/browse.html +++ b/rdiffweb/templates/browse.html @@ -47,7 +47,7 @@ {{ entry.file_size | filesize }} {% endif %} - + {% if entry.change_dates %}