diff --git a/swh/model/identifiers.py b/swh/model/identifiers.py index cf3b3265f1e0392b76430b880ffaee2c8cd2171b..c53513ae6bfbb9798a0845b7d1b38c2399b8c62a 100644 --- a/swh/model/identifiers.py +++ b/swh/model/identifiers.py @@ -171,19 +171,21 @@ def format_date(date): microseconds (postgres type "datetime with timezone"). Therefore, we print timestamps with no microseconds as integers, and - timestamps with microseconds as floating point values. + timestamps with microseconds as floating point values. We elide the + trailing zeroes from microsecond values, to "future-proof" our + representation if we ever need more precision in timestamps. """ - if isinstance(date, datetime.datetime): - if date.microsecond == 0: - date = int(date.timestamp()) - else: - date = date.timestamp() - return str(date).encode() + if not isinstance(date, dict): + raise ValueError('format_date only supports dicts, %r received' % date) + + seconds = date.get('seconds', 0) + microseconds = date.get('microseconds', 0) + if not microseconds: + return str(seconds).encode() else: - if date == int(date): - date = int(date) - return str(date).encode() + float_value = ('%d.%06d' % (seconds, microseconds)) + return float_value.rstrip('0').encode() @lru_cache() @@ -221,8 +223,9 @@ def normalize_timestamp(time_representation): Returns: a normalized dictionary with three keys - - timestamp: a number of seconds since the UNIX epoch (1970-01-01 at 00:00 - UTC) + - timestamp: a dict with two optional keys: + - seconds: the integral number of seconds since the UNIX epoch + - microseconds: the integral number of microseconds - offset: the timezone offset as a number of minutes relative to UTC - negative_utc: a boolean representing whether the offset is -0000 when offset = 0. @@ -235,12 +238,23 @@ def normalize_timestamp(time_representation): negative_utc = False if isinstance(time_representation, dict): - timestamp = time_representation['timestamp'] + ts = time_representation['timestamp'] + if isinstance(ts, dict): + seconds = ts.get('seconds', 0) + microseconds = ts.get('microseconds', 0) + elif isinstance(ts, int): + seconds = ts + microseconds = 0 + else: + raise ValueError( + 'normalize_timestamp received non-integer timestamp member:' + ' %r' % ts) offset = time_representation['offset'] if 'negative_utc' in time_representation: negative_utc = time_representation['negative_utc'] elif isinstance(time_representation, datetime.datetime): - timestamp = time_representation.timestamp() + seconds = int(time_representation.timestamp()) + microseconds = time_representation.microsecond utcoffset = time_representation.utcoffset() if utcoffset is None: raise ValueError( @@ -250,12 +264,20 @@ def normalize_timestamp(time_representation): # utcoffset is an integer number of minutes seconds_offset = utcoffset.total_seconds() offset = int(seconds_offset) // 60 - else: - timestamp = time_representation + elif isinstance(time_representation, int): + seconds = time_representation + microseconds = 0 offset = 0 + else: + raise ValueError( + 'normalize_timestamp received non-integer timestamp:' + ' %r' % time_representation) return { - 'timestamp': timestamp, + 'timestamp': { + 'seconds': seconds, + 'microseconds': microseconds, + }, 'offset': offset, 'negative_utc': negative_utc, } diff --git a/swh/model/tests/test_identifiers.py b/swh/model/tests/test_identifiers.py index e1adfea34f08ac419af4fc424fb4460e3eaabc6e..16a34bb9c76561bde5e97e42e2298f973b37e75e 100644 --- a/swh/model/tests/test_identifiers.py +++ b/swh/model/tests/test_identifiers.py @@ -55,15 +55,23 @@ class UtilityFunctionsIdentifier(unittest.TestCase): class UtilityFunctionsDateOffset(unittest.TestCase): def setUp(self): - self.date = datetime.datetime( - 2015, 11, 22, 16, 33, 56, tzinfo=datetime.timezone.utc) - self.date_int = int(self.date.timestamp()) - self.date_repr = b'1448210036' - - self.date_microseconds = datetime.datetime( - 2015, 11, 22, 16, 33, 56, 2342, tzinfo=datetime.timezone.utc) - self.date_microseconds_float = self.date_microseconds.timestamp() - self.date_microseconds_repr = b'1448210036.002342' + self.dates = { + b'1448210036': { + 'seconds': 1448210036, + 'microseconds': 0, + }, + b'1448210036.002342': { + 'seconds': 1448210036, + 'microseconds': 2342, + }, + b'1448210036.12': { + 'seconds': 1448210036, + 'microseconds': 120000, + } + } + self.broken_dates = [ + 1448210036.12, + ] self.offsets = { 0: b'+0000', @@ -73,12 +81,14 @@ class UtilityFunctionsDateOffset(unittest.TestCase): @istest def format_date(self): - for date in [self.date, self.date_int]: - self.assertEqual(identifiers.format_date(date), self.date_repr) + for date_repr, date in self.dates.items(): + self.assertEqual(identifiers.format_date(date), date_repr) - for date in [self.date_microseconds, self.date_microseconds_float]: - self.assertEqual(identifiers.format_date(date), - self.date_microseconds_repr) + @istest + def format_date_fail(self): + for date in self.broken_dates: + with self.assertRaises(ValueError): + identifiers.format_date(date) @istest def format_offset(self): @@ -285,7 +295,7 @@ dg1KdHOa34shrKDaOVzW 'email': b'robot@softwareheritage.org', }, 'date': { - 'timestamp': 1437047495.0, + 'timestamp': {'seconds': 1437047495}, 'offset': 0, 'negative_utc': False, }, @@ -349,7 +359,7 @@ dg1KdHOa34shrKDaOVzW 'fullname': b'Jiang Xin <worldhello.net@gmail.com>', }, 'date': { - 'timestamp': '1428538899', + 'timestamp': 1428538899, 'offset': 480, }, 'committer': { @@ -357,7 +367,7 @@ dg1KdHOa34shrKDaOVzW 'email': b'worldhello.net@gmail.com', }, 'committer_date': { - 'timestamp': '1428538899', + 'timestamp': 1428538899, 'offset': 480, }, 'metadata': { @@ -383,7 +393,7 @@ dg1KdHOa34shrKDaOVzW 'fullname': b'Jiang Xin <worldhello.net@gmail.com>', }, 'date': { - 'timestamp': '1428538899', + 'timestamp': 1428538899, 'offset': 480, }, 'committer': { @@ -391,7 +401,7 @@ dg1KdHOa34shrKDaOVzW 'email': b'worldhello.net@gmail.com', }, 'committer_date': { - 'timestamp': '1428538899', + 'timestamp': 1428538899, 'offset': 480, }, 'message': None, @@ -408,7 +418,7 @@ dg1KdHOa34shrKDaOVzW 'fullname': b'Jiang Xin <worldhello.net@gmail.com>', }, 'date': { - 'timestamp': '1428538899', + 'timestamp': 1428538899, 'offset': 480, }, 'committer': { @@ -416,7 +426,7 @@ dg1KdHOa34shrKDaOVzW 'email': b'worldhello.net@gmail.com', }, 'committer_date': { - 'timestamp': '1428538899', + 'timestamp': 1428538899, 'offset': 480, }, 'message': b'', @@ -592,7 +602,7 @@ o6X/3T+vm8K3bf3driRr34c= 'target': '54e9abca4c77421e2921f5f156c9fe4a9f7441c7', 'target_type': 'revision', 'date': { - 'timestamp': 1225281976.0, + 'timestamp': {'seconds': 1225281976}, 'offset': 0, 'negative_utc': True, },