Remove datetime parsing from Gajim
Use nbxmpp's datetime parsing
This commit is contained in:
parent
da46bdda1f
commit
348a8551fe
3 changed files with 2 additions and 295 deletions
|
@ -1,194 +0,0 @@
|
||||||
# This file is part of Gajim.
|
|
||||||
#
|
|
||||||
# Gajim is free software; you can redistribute it and/or modify
|
|
||||||
# it under the terms of the GNU General Public License as published
|
|
||||||
# by the Free Software Foundation; version 3 only.
|
|
||||||
#
|
|
||||||
# Gajim is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
# GNU General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU General Public License
|
|
||||||
# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
# XEP-0082: XMPP Date and Time Profiles
|
|
||||||
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import logging
|
|
||||||
from datetime import datetime
|
|
||||||
from datetime import timedelta
|
|
||||||
from datetime import timezone
|
|
||||||
from datetime import tzinfo
|
|
||||||
|
|
||||||
log = logging.getLogger('gajim.c.m.date_and_time')
|
|
||||||
|
|
||||||
PATTERN_DATETIME = re.compile(
|
|
||||||
r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
|
|
||||||
r'T'
|
|
||||||
r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
|
|
||||||
r'(\.[0-9]{0,6})?'
|
|
||||||
r'(?:[0-9]+)?'
|
|
||||||
r'(?:(Z)|(?:([-+][0-9]{2}):([0-9]{2})))$'
|
|
||||||
)
|
|
||||||
|
|
||||||
PATTERN_DELAY = re.compile(
|
|
||||||
r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
|
|
||||||
r'T'
|
|
||||||
r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
|
|
||||||
r'(\.[0-9]{0,6})?'
|
|
||||||
r'(?:[0-9]+)?'
|
|
||||||
r'(?:(Z)|(?:([-+][0]{2}):([0]{2})))$'
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
ZERO = timedelta(0)
|
|
||||||
HOUR = timedelta(hours=1)
|
|
||||||
SECOND = timedelta(seconds=1)
|
|
||||||
|
|
||||||
STDOFFSET = timedelta(seconds=-time.timezone)
|
|
||||||
if time.daylight:
|
|
||||||
DSTOFFSET = timedelta(seconds=-time.altzone)
|
|
||||||
else:
|
|
||||||
DSTOFFSET = STDOFFSET
|
|
||||||
|
|
||||||
DSTDIFF = DSTOFFSET - STDOFFSET
|
|
||||||
|
|
||||||
|
|
||||||
class LocalTimezone(tzinfo):
|
|
||||||
'''
|
|
||||||
A class capturing the platform's idea of local time.
|
|
||||||
May result in wrong values on historical times in
|
|
||||||
timezones where UTC offset and/or the DST rules had
|
|
||||||
changed in the past.
|
|
||||||
'''
|
|
||||||
def fromutc(self, dt):
|
|
||||||
assert dt.tzinfo is self
|
|
||||||
stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND
|
|
||||||
args = time.localtime(stamp)[:6]
|
|
||||||
dst_diff = DSTDIFF // SECOND
|
|
||||||
# Detect fold
|
|
||||||
fold = (args == time.localtime(stamp - dst_diff))
|
|
||||||
return datetime(*args, microsecond=dt.microsecond,
|
|
||||||
tzinfo=self, fold=fold)
|
|
||||||
|
|
||||||
def utcoffset(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return DSTOFFSET
|
|
||||||
return STDOFFSET
|
|
||||||
|
|
||||||
def dst(self, dt):
|
|
||||||
if self._isdst(dt):
|
|
||||||
return DSTDIFF
|
|
||||||
return ZERO
|
|
||||||
|
|
||||||
def tzname(self, dt):
|
|
||||||
return 'local'
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _isdst(dt):
|
|
||||||
tt = (dt.year, dt.month, dt.day,
|
|
||||||
dt.hour, dt.minute, dt.second,
|
|
||||||
dt.weekday(), 0, 0)
|
|
||||||
stamp = time.mktime(tt)
|
|
||||||
tt = time.localtime(stamp)
|
|
||||||
return tt.tm_isdst > 0
|
|
||||||
|
|
||||||
|
|
||||||
def create_tzinfo(hours=0, minutes=0, tz_string=None):
|
|
||||||
if tz_string is None:
|
|
||||||
return timezone(timedelta(hours=hours, minutes=minutes))
|
|
||||||
|
|
||||||
if tz_string.lower() == 'z':
|
|
||||||
return timezone.utc
|
|
||||||
|
|
||||||
try:
|
|
||||||
hours, minutes = map(int, tz_string.split(':'))
|
|
||||||
except Exception:
|
|
||||||
log.warning('Wrong tz string: %s', tz_string)
|
|
||||||
return
|
|
||||||
|
|
||||||
if hours not in range(-24, 24):
|
|
||||||
log.warning('Wrong tz string: %s', tz_string)
|
|
||||||
return
|
|
||||||
|
|
||||||
if minutes not in range(0, 59):
|
|
||||||
log.warning('Wrong tz string: %s', tz_string)
|
|
||||||
return
|
|
||||||
|
|
||||||
if hours in (24, -24) and minutes != 0:
|
|
||||||
log.warning('Wrong tz string: %s', tz_string)
|
|
||||||
return
|
|
||||||
return timezone(timedelta(hours=hours, minutes=minutes))
|
|
||||||
|
|
||||||
|
|
||||||
def parse_datetime(timestring, check_utc=False,
|
|
||||||
convert='utc', epoch=False):
|
|
||||||
'''
|
|
||||||
Parse a XEP-0082 DateTime Profile String
|
|
||||||
|
|
||||||
:param timestring: a XEP-0082 DateTime profile formated string
|
|
||||||
|
|
||||||
:param check_utc: if True, returns None if timestring is not
|
|
||||||
a timestring expressing UTC
|
|
||||||
|
|
||||||
:param convert: convert the given timestring to utc or local time
|
|
||||||
|
|
||||||
:param epoch: if True, returns the time in epoch
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
'2017-11-05T01:41:20Z'
|
|
||||||
'2017-11-05T01:41:20.123Z'
|
|
||||||
'2017-11-05T01:41:20.123+05:00'
|
|
||||||
|
|
||||||
return a datetime or epoch
|
|
||||||
'''
|
|
||||||
if convert not in (None, 'utc', 'local'):
|
|
||||||
raise TypeError('"%s" is not a valid value for convert')
|
|
||||||
if check_utc:
|
|
||||||
match = PATTERN_DELAY.match(timestring)
|
|
||||||
else:
|
|
||||||
match = PATTERN_DATETIME.match(timestring)
|
|
||||||
|
|
||||||
if match:
|
|
||||||
timestring = ''.join(match.groups(''))
|
|
||||||
strformat = '%Y-%m-%d%H:%M:%S%z'
|
|
||||||
if match.group(3):
|
|
||||||
# Fractional second addendum to Time
|
|
||||||
strformat = '%Y-%m-%d%H:%M:%S.%f%z'
|
|
||||||
if match.group(4):
|
|
||||||
# UTC string denoted by addition of the character 'Z'
|
|
||||||
timestring = timestring[:-1] + '+0000'
|
|
||||||
try:
|
|
||||||
date_time = datetime.strptime(timestring, strformat)
|
|
||||||
except ValueError:
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
if check_utc:
|
|
||||||
if convert != 'utc':
|
|
||||||
raise ValueError(
|
|
||||||
'check_utc can only be used with convert="utc"')
|
|
||||||
date_time.replace(tzinfo=timezone.utc)
|
|
||||||
if epoch:
|
|
||||||
return date_time.timestamp()
|
|
||||||
return date_time
|
|
||||||
|
|
||||||
if convert == 'utc':
|
|
||||||
date_time = date_time.astimezone(timezone.utc)
|
|
||||||
if epoch:
|
|
||||||
return date_time.timestamp()
|
|
||||||
return date_time
|
|
||||||
|
|
||||||
if epoch:
|
|
||||||
# epoch is always UTC, use convert='utc' or check_utc=True
|
|
||||||
raise ValueError(
|
|
||||||
'epoch not available while converting to local')
|
|
||||||
|
|
||||||
if convert == 'local':
|
|
||||||
date_time = date_time.astimezone(LocalTimezone())
|
|
||||||
return date_time
|
|
||||||
|
|
||||||
# convert=None
|
|
||||||
return date_time
|
|
||||||
return None
|
|
|
@ -18,12 +18,12 @@ import time
|
||||||
|
|
||||||
import nbxmpp
|
import nbxmpp
|
||||||
from nbxmpp.structs import StanzaHandler
|
from nbxmpp.structs import StanzaHandler
|
||||||
|
from nbxmpp.modules.date_and_time import parse_datetime
|
||||||
|
from nbxmpp.modules.date_and_time import create_tzinfo
|
||||||
|
|
||||||
from gajim.common import app
|
from gajim.common import app
|
||||||
from gajim.common.nec import NetworkEvent
|
from gajim.common.nec import NetworkEvent
|
||||||
from gajim.common.modules.base import BaseModule
|
from gajim.common.modules.base import BaseModule
|
||||||
from gajim.common.modules.date_and_time import parse_datetime
|
|
||||||
from gajim.common.modules.date_and_time import create_tzinfo
|
|
||||||
|
|
||||||
|
|
||||||
class EntityTime(BaseModule):
|
class EntityTime(BaseModule):
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
import unittest
|
|
||||||
from datetime import datetime
|
|
||||||
from datetime import timezone
|
|
||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from gajim.common.modules.date_and_time import parse_datetime
|
|
||||||
from gajim.common.modules.date_and_time import LocalTimezone
|
|
||||||
from gajim.common.modules.date_and_time import create_tzinfo
|
|
||||||
|
|
||||||
|
|
||||||
class TestDateTime(unittest.TestCase):
|
|
||||||
|
|
||||||
def test_convert_to_utc(self):
|
|
||||||
|
|
||||||
strings = {
|
|
||||||
# Valid UTC strings and fractions
|
|
||||||
'2017-11-05T01:41:20Z': 1509846080.0,
|
|
||||||
'2017-11-05T01:41:20.123Z': 1509846080.123,
|
|
||||||
'2017-11-05T01:41:20.123123123+00:00': 1509846080.123123,
|
|
||||||
'2017-11-05T01:41:20.123123123123123-00:00': 1509846080.123123,
|
|
||||||
|
|
||||||
# Invalid strings
|
|
||||||
'2017-11-05T01:41:20Z+05:00': None,
|
|
||||||
'2017-11-05T01:41:20+0000': None,
|
|
||||||
'2017-11-05T01:41:20-0000': None,
|
|
||||||
|
|
||||||
# Valid strings with offset
|
|
||||||
'2017-11-05T01:41:20-05:00': 1509864080.0,
|
|
||||||
'2017-11-05T01:41:20+05:00': 1509828080.0,
|
|
||||||
}
|
|
||||||
|
|
||||||
strings2 = {
|
|
||||||
# Valid strings with offset
|
|
||||||
'2017-11-05T01:41:20-05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=-5)),
|
|
||||||
'2017-11-05T01:41:20+05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=5)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for time_string, expected_value in strings.items():
|
|
||||||
result = parse_datetime(time_string, convert='utc', epoch=True)
|
|
||||||
self.assertEqual(result, expected_value)
|
|
||||||
|
|
||||||
for time_string, expected_value in strings2.items():
|
|
||||||
result = parse_datetime(time_string, convert='utc')
|
|
||||||
self.assertEqual(result, expected_value.astimezone(timezone.utc))
|
|
||||||
|
|
||||||
def test_convert_to_local(self):
|
|
||||||
|
|
||||||
strings = {
|
|
||||||
# Valid UTC strings and fractions
|
|
||||||
'2017-11-05T01:41:20Z': datetime(2017, 11, 5, 1, 41, 20, 0, timezone.utc),
|
|
||||||
'2017-11-05T01:41:20.123Z': datetime(2017, 11, 5, 1, 41, 20, 123000, timezone.utc),
|
|
||||||
'2017-11-05T01:41:20.123123123+00:00': datetime(2017, 11, 5, 1, 41, 20, 123123, timezone.utc),
|
|
||||||
'2017-11-05T01:41:20.123123123123123-00:00': datetime(2017, 11, 5, 1, 41, 20, 123123, timezone.utc),
|
|
||||||
|
|
||||||
# Valid strings with offset
|
|
||||||
'2017-11-05T01:41:20-05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=-5)),
|
|
||||||
'2017-11-05T01:41:20+05:00': datetime(2017, 11, 5, 1, 41, 20, 0, create_tzinfo(hours=5)),
|
|
||||||
}
|
|
||||||
|
|
||||||
for time_string, expected_value in strings.items():
|
|
||||||
result = parse_datetime(time_string, convert='local')
|
|
||||||
self.assertEqual(result, expected_value.astimezone(LocalTimezone()))
|
|
||||||
|
|
||||||
def test_no_convert(self):
|
|
||||||
|
|
||||||
strings = {
|
|
||||||
# Valid UTC strings and fractions
|
|
||||||
'2017-11-05T01:41:20Z': timedelta(0),
|
|
||||||
'2017-11-05T01:41:20.123Z': timedelta(0),
|
|
||||||
'2017-11-05T01:41:20.123123123+00:00': timedelta(0),
|
|
||||||
'2017-11-05T01:41:20.123123123123123-00:00': timedelta(0),
|
|
||||||
|
|
||||||
# Valid strings with offset
|
|
||||||
'2017-11-05T01:41:20-05:00': timedelta(hours=-5),
|
|
||||||
'2017-11-05T01:41:20+05:00': timedelta(hours=5),
|
|
||||||
}
|
|
||||||
|
|
||||||
for time_string, expected_value in strings.items():
|
|
||||||
result = parse_datetime(time_string, convert=None)
|
|
||||||
self.assertEqual(result.utcoffset(), expected_value)
|
|
||||||
|
|
||||||
def test_check_utc(self):
|
|
||||||
|
|
||||||
strings = {
|
|
||||||
# Valid UTC strings and fractions
|
|
||||||
'2017-11-05T01:41:20Z': 1509846080.0,
|
|
||||||
'2017-11-05T01:41:20.123Z': 1509846080.123,
|
|
||||||
'2017-11-05T01:41:20.123123123+00:00': 1509846080.123123,
|
|
||||||
'2017-11-05T01:41:20.123123123123123-00:00': 1509846080.123123,
|
|
||||||
|
|
||||||
# Valid strings with offset
|
|
||||||
'2017-11-05T01:41:20-05:00': None,
|
|
||||||
'2017-11-05T01:41:20+05:00': None,
|
|
||||||
}
|
|
||||||
|
|
||||||
for time_string, expected_value in strings.items():
|
|
||||||
result = parse_datetime(
|
|
||||||
time_string, check_utc=True, epoch=True)
|
|
||||||
self.assertEqual(result, expected_value)
|
|
Loading…
Add table
Reference in a new issue