diff --git a/swh/model/hypothesis_strategies.py b/swh/model/hypothesis_strategies.py --- a/swh/model/hypothesis_strategies.py +++ b/swh/model/hypothesis_strategies.py @@ -6,8 +6,9 @@ import attr import datetime +from hypothesis import assume from hypothesis.strategies import ( - binary, builds, characters, composite, dictionaries, + binary, booleans, builds, characters, composite, dictionaries, from_regex, integers, just, lists, none, one_of, sampled_from, sets, text, tuples, ) @@ -65,11 +66,17 @@ microseconds=integers(0, 1000000)) -def timestamps_with_timezone(): - return builds( - TimestampWithTimezone, +@composite +def timestamps_with_timezone( + draw, timestamp=timestamps(), - offset=integers(min_value=-14*60, max_value=14*60)) + offset=integers(min_value=-14*60, max_value=14*60), + negative_utc=booleans()): + assume(negative_utc and not offset) + return TimestampWithTimezone( + timestamp=timestamp, + offset=offset, + negative_utc=negative_utc) def origins(): diff --git a/swh/model/model.py b/swh/model/model.py --- a/swh/model/model.py +++ b/swh/model/model.py @@ -176,6 +176,11 @@ # you'll find in the wild... raise ValueError('offset too large: %d minutes' % value) + @negative_utc.validator + def check_negative_utc(self, attribute, value): + if self.offset and value: + raise ValueError("negative_utc can only be True is offset=0") + @classmethod def from_dict(cls, obj: Union[Dict, datetime.datetime, int]): """Builds a TimestampWithTimezone from any of the formats 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 @@ -90,6 +90,69 @@ Timestamp(seconds=0, microseconds=-1) +def test_timestampwithtimezone(): + ts = Timestamp(seconds=0, microseconds=0) + tstz = TimestampWithTimezone( + timestamp=ts, + offset=0, + negative_utc=False) + attr.validate(tstz) + assert tstz.negative_utc is False + + attr.validate(TimestampWithTimezone( + timestamp=ts, + offset=10, + negative_utc=False)) + + attr.validate(TimestampWithTimezone( + timestamp=ts, + offset=-10, + negative_utc=False)) + + tstz = TimestampWithTimezone( + timestamp=ts, + offset=0, + negative_utc=True) + attr.validate(tstz) + assert tstz.negative_utc is True + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=datetime.datetime.now(), + offset=0, + negative_utc=False) + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=ts, + offset='0', + negative_utc=False) + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=ts, + offset=1.0, + negative_utc=False) + + with pytest.raises(AttributeTypeError): + TimestampWithTimezone( + timestamp=ts, + offset=1, + negative_utc=0) + + with pytest.raises(ValueError): + TimestampWithTimezone( + timestamp=ts, + offset=1, + negative_utc=True) + + with pytest.raises(ValueError): + TimestampWithTimezone( + timestamp=ts, + offset=-1, + negative_utc=True) + + def test_timestampwithtimezone_from_datetime(): tz = datetime.timezone(datetime.timedelta(minutes=+60)) date = datetime.datetime(