diff --git a/gajim/common/modules/date_and_time.py b/gajim/common/modules/date_and_time.py deleted file mode 100644 index 28030ba15a73c0250ba308e4e28b7bde88171fec..0000000000000000000000000000000000000000 --- a/gajim/common/modules/date_and_time.py +++ /dev/null @@ -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 diff --git a/gajim/common/modules/entity_time.py b/gajim/common/modules/entity_time.py index d304f6132a2ba1d1c85d8854fc9e3066bd66af76..f6d38bd91a1a1d41f1656071faf47ae11d742291 100644 --- a/gajim/common/modules/entity_time.py +++ b/gajim/common/modules/entity_time.py @@ -18,12 +18,12 @@ import time import nbxmpp 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.nec import NetworkEvent 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): diff --git a/test/no_gui/unit/test_date_time.py b/test/no_gui/unit/test_date_time.py deleted file mode 100644 index 17946e02dcaab03d11d0fe09946e197b515027f5..0000000000000000000000000000000000000000 --- a/test/no_gui/unit/test_date_time.py +++ /dev/null @@ -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)