From 00c7715c01041edf951c91a45f147e8a0f58d1ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Philipp=20H=C3=B6rist?= Date: Sat, 17 Nov 2018 13:21:05 +0100 Subject: [PATCH] Refactor parse_datetime and add unit test --- gajim/common/modules/date_and_time.py | 34 +++++++-- test/no_gui/unit/test_date_time.py | 99 +++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 4 deletions(-) create mode 100644 test/no_gui/unit/test_date_time.py diff --git a/gajim/common/modules/date_and_time.py b/gajim/common/modules/date_and_time.py index 09a3dbe41..1c680c66b 100644 --- a/gajim/common/modules/date_and_time.py +++ b/gajim/common/modules/date_and_time.py @@ -16,7 +16,10 @@ import re import time -from datetime import datetime, timedelta, timezone, tzinfo +from datetime import datetime +from datetime import timedelta +from datetime import timezone +from datetime import tzinfo PATTERN_DATETIME = re.compile( @@ -91,6 +94,10 @@ class LocalTimezone(tzinfo): return tt.tm_isdst > 0 +def create_tzinfo(hours=0, minutes=0): + return timezone(timedelta(hours=hours, minutes=minutes)) + + def parse_datetime(timestring, check_utc=False, convert='utc', epoch=False): ''' @@ -133,11 +140,30 @@ def parse_datetime(timestring, check_utc=False, except ValueError: pass else: - if not check_utc and convert == 'utc': + 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()) - if epoch: - return date_time.timestamp() + return date_time + + # convert=None return date_time return None diff --git a/test/no_gui/unit/test_date_time.py b/test/no_gui/unit/test_date_time.py new file mode 100644 index 000000000..17946e02d --- /dev/null +++ b/test/no_gui/unit/test_date_time.py @@ -0,0 +1,99 @@ +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)