Repository: rotki/rotki

Issue:
unit tests for iso8601ts_to_timestamp()
Add unit tests for this function. Try with a few iso8601 formats.

The current function uses `datetime.datetime.fromisoformat` which does not work with all kinds of iso8601 dates by default and thus has some tweaks in it for which unit testing would be good


Discussion:

Resolving patch:
diff --git a/rotkehlchen/utils/misc.py b/rotkehlchen/utils/misc.py
--- a/rotkehlchen/utils/misc.py
+++ b/rotkehlchen/utils/misc.py
@@ -5,6 +5,7 @@
 import logging
 import operator
 import os
+import re
 import sys
 import time
 from functools import wraps
@@ -36,6 +37,9 @@ def create_timestamp(datestr: str, formatstr: str = '%Y-%m-%d %H:%M:%S') -> Time
     return Timestamp(calendar.timegm(time.strptime(datestr, formatstr)))
 
 
+FRACTION_SECS_RE = re.compile(r'.*\.(\d+).*')
+
+
 def iso8601ts_to_timestamp(datestr: str) -> Timestamp:
     """Requires python 3.7 due to fromisoformat()
 
@@ -49,11 +53,24 @@ def iso8601ts_to_timestamp(datestr: str) -> Timestamp:
     """
     # Required due to prolems with fomrisoformat recognizing the ZULU mark
     datestr = datestr.replace("Z", "+00:00")
+    # The following function does not always properly handle fractions of a second
+    # so let's just remove it and round to the nearest second since we only deal
+    # with seconds in the rotkehlchen timestamps
+    match = FRACTION_SECS_RE.search(datestr)
+    add_a_second = False
+    if match:
+        fraction_str = match.group(1)
+        datestr = datestr.replace('.' + fraction_str, '')
+        num = int(fraction_str) / int('1' + '0' * len(fraction_str))
+        if num > 0.5:
+            add_a_second = True
     try:
-        return Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))
+        ts = Timestamp(int(datetime.datetime.fromisoformat(datestr).timestamp()))
     except ValueError:
         raise DeserializationError(f'Couldnt read {datestr} as iso8601ts timestamp')
 
+    return Timestamp(ts + 1) if add_a_second else ts
+
 
 def satoshis_to_btc(satoshis: Numerical) -> Numerical:
     return satoshis * FVal('0.00000001')


Test patch:
diff --git a/rotkehlchen/tests/test_bitmex.py b/rotkehlchen/tests/test_bitmex.py
--- a/rotkehlchen/tests/test_bitmex.py
+++ b/rotkehlchen/tests/test_bitmex.py
@@ -69,7 +69,7 @@ def test_bitmex_api_withdrawals_deposit(test_bitmex):
         AssetMovement(
             exchange=Exchange.BITMEX,
             category='withdrawal',
-            timestamp=1536536706,
+            timestamp=1536536707,
             asset='BTC',
             amount=FVal('0.00700000'),
             fee=FVal('0.00300000'),
@@ -77,7 +77,7 @@ def test_bitmex_api_withdrawals_deposit(test_bitmex):
         AssetMovement(
             exchange=Exchange.BITMEX,
             category='deposit',
-            timestamp=1536486277,
+            timestamp=1536486278,
             asset='BTC',
             amount=FVal('0.46966992'),
             fee=FVal(0),
diff --git a/rotkehlchen/tests/test_utils.py b/rotkehlchen/tests/test_utils.py
--- a/rotkehlchen/tests/test_utils.py
+++ b/rotkehlchen/tests/test_utils.py
@@ -1,4 +1,5 @@
 import json
+import time
 from unittest.mock import patch
 
 import pytest
@@ -42,9 +43,26 @@ def test_tuple_in_process_result():
 
 
 def test_iso8601ts_to_timestamp():
+    # Get timezone offset to UTC
+    utc_offset = time.timezone if (time.localtime().tm_isdst == 0) else time.altzone
+
     assert iso8601ts_to_timestamp('2018-09-09T12:00:00.000Z') == 1536494400
     assert iso8601ts_to_timestamp('2011-01-01T04:13:22.220Z') == 1293855202
-    assert iso8601ts_to_timestamp('1986-11-04T16:23:57.921Z') == 531505437
+    assert iso8601ts_to_timestamp('1986-11-04T16:23:57.921Z') == 531505438
+    # Timezone specific part of the test
+    timezone_ts_str = '1997-07-16T22:30'
+    timezone_ts_at_utc = 869092200
+    assert iso8601ts_to_timestamp(timezone_ts_str + 'Z') == timezone_ts_at_utc
+    assert iso8601ts_to_timestamp(timezone_ts_str) == timezone_ts_at_utc + utc_offset
+    assert iso8601ts_to_timestamp('1997-07-16T22:30+01:00') == 869088600
+    assert iso8601ts_to_timestamp('1997-07-16T22:30:45+01:00') == 869088645
+    assert iso8601ts_to_timestamp('1997-07-16T22:30:45.1+01:00') == 869088645
+    assert iso8601ts_to_timestamp('1997-07-16T22:30:45.01+01:00') == 869088645
+    assert iso8601ts_to_timestamp('1997-07-16T22:30:45.001+01:00') == 869088645
+    assert iso8601ts_to_timestamp('1997-07-16T22:30:45.9+01:00') == 869088646
+    assert iso8601ts_to_timestamp('1997-07-16T22:30:45.99+01:00') == 869088646
+    assert iso8601ts_to_timestamp('1997-07-16T22:30:45.999+01:00') == 869088646
+    assert iso8601ts_to_timestamp('1997-07-16T21:30:45+00:00') == 869088645
 
 
 def test_invert_pair():