diff --git a/swh/model/model.py b/swh/model/model.py --- a/swh/model/model.py +++ b/swh/model/model.py @@ -257,6 +257,23 @@ def from_datetime(cls, dt: datetime.datetime): return cls.from_dict(dt) + def to_datetime(self) -> datetime.datetime: + """Convert to a datetime (with a timezone set to the recorded fixed UTC offset) + + Beware that this conversion can be lossy: the negative_utc flag is not + taken into consideration (since it cannot be represented in a + datetime). Also note that it may fail due to type overflow. + + """ + timestamp = datetime.datetime.fromtimestamp( + self.timestamp.seconds, + datetime.timezone(datetime.timedelta(minutes=self.offset)), + ) + timestamp = timestamp.replace(microsecond=self.timestamp.microseconds) + if self.timestamp.seconds < 0 and self.timestamp.microseconds: + timestamp -= datetime.timedelta(seconds=1) + return timestamp + @classmethod def from_iso8601(cls, s): """Builds a TimestampWithTimezone from an ISO8601-formatted string. diff --git a/swh/model/tests/test_model.py b/swh/model/tests/test_model.py --- a/swh/model/tests/test_model.py +++ b/swh/model/tests/test_model.py @@ -329,6 +329,29 @@ ) +tzs = [ + datetime.timezone(datetime.timedelta(minutes=+60)), + datetime.timezone.utc, + datetime.timezone(datetime.timedelta(hours=-1)), +] +dates = [ + datetime.datetime(2020, 2, 27, 14, 39, 19), + datetime.datetime(2120, 12, 31, 23, 59, 59), + datetime.datetime(1610, 5, 14, 15, 43, 0), +] + + +@pytest.mark.parametrize("date", dates) +@pytest.mark.parametrize("tz", tzs) +@pytest.mark.parametrize("microsecond", [0, 1, 10, 100, 1000, 999999]) +def test_timestampwithtimezone_to_datetime(date, tz, microsecond): + date = date.replace(tzinfo=tz, microsecond=microsecond) + tstz = TimestampWithTimezone.from_datetime(date) + + assert tstz.to_datetime() == date + assert tstz.to_datetime().utcoffset() == date.utcoffset() + + def test_person_from_fullname(): """The author should have name, email and fullname filled.