Commit bef83279 authored by Patrik Dufresne's avatar Patrik Dufresne

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.
parent 61da3a31
......@@ -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
......
......@@ -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'
......
......@@ -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)
......
......@@ -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 = []
......
......@@ -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))
......
......@@ -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)
......
......@@ -47,7 +47,7 @@
{{ entry.file_size | filesize }}
{% endif %}
</td>
<td data-value="{{ entry.last_change_date and entry.last_change_date.getSeconds() }}">
<td data-value="{{ entry.last_change_date and entry.last_change_date.epoch() }}">
{% if entry.change_dates %}
<div class="dropdown">
<button class="btn btn-link dropdown-toggle btn-block" type="button" id="menu1" data-toggle="dropdown">
......
......@@ -376,21 +376,26 @@ class RdiffTimeTest(unittest.TestCase):
Check various constructor.
"""
t0 = RdiffTime()
self.assertAlmostEqual(int(time.time()), t0.timeInSeconds, delta=5000)
self.assertAlmostEqual(int(time.time()), t0.epoch(), delta=5000)
t1 = RdiffTime(1415221470)
self.assertEqual(1415221470, t1.timeInSeconds)
self.assertEqual(1415221470, t1.epoch())
t2 = RdiffTime('2014-11-05T21:04:30Z')
self.assertEqual(1415221470, t2.timeInSeconds)
self.assertEqual(1415221470, t2.epoch())
t3 = RdiffTime('2014-11-05T16:04:30-05:00')
self.assertEqual(1415221470, t3.epoch())
def test_int(self):
"""Check if int(RdiffTime) return expected value."""
self.assertEqual(1415221470, int(RdiffTime(1415221470)))
self.assertEqual(1415217870, int(RdiffTime(1415221470, 3600)))
def test_str(self):
"""Check if __str__ is working."""
self.assertEqual('2014-11-05 21:04:30', str(RdiffTime(1415221470)))
self.assertEqual('2014-11-05 21:04:30Z', str(RdiffTime(1415221470)))
self.assertEqual('2014-11-05 21:04:30+01:00', str(RdiffTime(1415221470, 3600)))
def test_sub(self):
"""Check if addition with timedelta is working as expected."""
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment