diff --git a/swh/model/model.py b/swh/model/model.py --- a/swh/model/model.py +++ b/swh/model/model.py @@ -452,14 +452,54 @@ self._check_offsets_match() @staticmethod - def _parse_offset_bytes(offset_bytes: bytes): + def _parse_offset_bytes(offset_bytes: bytes) -> int: + """Parses an ``offset_bytes`` value (in Git's ``[+-]HHMM`` format), + and returns the corresponding numeric values (in number of minutes). + + Tries to account for some mistakes in the format, to support incorrect + Git implementations. + + >>> TimestampWithTimezone._parse_offset_bytes(b"+0000") + 0 + >>> TimestampWithTimezone._parse_offset_bytes(b"-0000") + 0 + >>> TimestampWithTimezone._parse_offset_bytes(b"+0200") + 120 + >>> TimestampWithTimezone._parse_offset_bytes(b"-0200") + -120 + >>> TimestampWithTimezone._parse_offset_bytes(b"+200") + 120 + >>> TimestampWithTimezone._parse_offset_bytes(b"-200") + -120 + >>> TimestampWithTimezone._parse_offset_bytes(b"+02") + 120 + >>> TimestampWithTimezone._parse_offset_bytes(b"-02") + -120 + >>> TimestampWithTimezone._parse_offset_bytes(b"+0010") + 10 + >>> TimestampWithTimezone._parse_offset_bytes(b"-0010") + -10 + >>> TimestampWithTimezone._parse_offset_bytes(b"+200000000000000000") + 0 + >>> TimestampWithTimezone._parse_offset_bytes(b"+0160") # 60 minutes... + 0 + """ offset_str = offset_bytes.decode() assert offset_str[0] in "+-" sign = int(offset_str[0] + "1") - hours = int(offset_str[1:-2]) - minutes = int(offset_str[-2:]) + if len(offset_str) <= 3: + hours = int(offset_str[1:]) + minutes = 0 + else: + hours = int(offset_str[1:-2]) + minutes = int(offset_str[-2:]) + offset = sign * (hours * 60 + minutes) - return offset + if (0 <= minutes <= 59) and (-(2 ** 15) <= offset < 2 ** 15): + return offset + else: + # can't parse it to a reasonable value; give up and pretend it's UTC. + return 0 def _check_offsets_match(self): offset = self._parse_offset_bytes(self.offset_bytes) 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 @@ -480,6 +480,21 @@ assert tstz.offset == 800 assert tstz.offset_bytes == b"+1320" + tstz = TimestampWithTimezone(timestamp=ts, offset_bytes=b"+200") + attr.validate(tstz) + assert tstz.offset == 120 + assert tstz.offset_bytes == b"+200" + + tstz = TimestampWithTimezone(timestamp=ts, offset_bytes=b"+02") + attr.validate(tstz) + assert tstz.offset == 120 + assert tstz.offset_bytes == b"+02" + + tstz = TimestampWithTimezone(timestamp=ts, offset_bytes=b"+2000000000") + attr.validate(tstz) + assert tstz.offset == 0 + assert tstz.offset_bytes == b"+2000000000" + with pytest.raises(AttributeTypeError): TimestampWithTimezone(timestamp=datetime.datetime.now(), offset_bytes=b"+0000")