From 00c7715c01041edf951c91a45f147e8a0f58d1ae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Philipp=20H=C3=B6rist?= <philipp@hoerist.com>
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 09a3dbe412..1c680c66b0 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 0000000000..17946e02dc
--- /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)
-- 
GitLab