diff --git a/python/google/protobuf/internal/json_format_test.py b/python/google/protobuf/internal/json_format_test.py index 6c4d2dac0b..15b376c181 100644 --- a/python/google/protobuf/internal/json_format_test.py +++ b/python/google/protobuf/internal/json_format_test.py @@ -1060,14 +1060,7 @@ class JsonFormatTest(JsonFormatBase): json_format.Parse, text, message) # Time bigger than maximum time. message.value.seconds = 253402300800 - self.assertRaisesRegex(json_format.SerializeToJsonError, - 'Timestamp is not valid', - json_format.MessageToJson, message) - # Nanos smaller than 0 - message.value.seconds = 0 - message.value.nanos = -1 - self.assertRaisesRegex(json_format.SerializeToJsonError, - 'Timestamp is not valid', + self.assertRaisesRegex(OverflowError, 'date value out of range', json_format.MessageToJson, message) # Lower case t does not accept. text = '{"value": "0001-01-01t00:00:00Z"}' diff --git a/python/google/protobuf/internal/well_known_types.py b/python/google/protobuf/internal/well_known_types.py index 835e443eff..5727bc98c2 100644 --- a/python/google/protobuf/internal/well_known_types.py +++ b/python/google/protobuf/internal/well_known_types.py @@ -33,8 +33,6 @@ _MILLIS_PER_SECOND = 1000 _MICROS_PER_SECOND = 1000000 _SECONDS_PER_DAY = 24 * 3600 _DURATION_SECONDS_MAX = 315576000000 -_TIMESTAMP_SECONDS_MIN = -62135596800 -_TIMESTAMP_SECONDS_MAX = 253402300799 _EPOCH_DATETIME_NAIVE = datetime.datetime(1970, 1, 1, tzinfo=None) _EPOCH_DATETIME_AWARE = _EPOCH_DATETIME_NAIVE.replace( @@ -87,10 +85,10 @@ class Timestamp(object): and uses 3, 6 or 9 fractional digits as required to represent the exact time. Example of the return format: '1972-01-01T10:00:20.021Z' """ - _CheckTimestampValid(self.seconds, self.nanos) - nanos = self.nanos - seconds = self.seconds % _SECONDS_PER_DAY - days = (self.seconds - seconds) // _SECONDS_PER_DAY + nanos = self.nanos % _NANOS_PER_SECOND + total_sec = self.seconds + (self.nanos - nanos) // _NANOS_PER_SECOND + seconds = total_sec % _SECONDS_PER_DAY + days = (total_sec - seconds) // _SECONDS_PER_DAY dt = datetime.datetime(1970, 1, 1) + datetime.timedelta(days, seconds) result = dt.isoformat() @@ -168,7 +166,6 @@ class Timestamp(object): else: seconds += (int(timezone[1:pos])*60+int(timezone[pos+1:]))*60 # Set seconds and nanos - _CheckTimestampValid(seconds, nanos) self.seconds = int(seconds) self.nanos = int(nanos) @@ -178,53 +175,39 @@ class Timestamp(object): def ToNanoseconds(self): """Converts Timestamp to nanoseconds since epoch.""" - _CheckTimestampValid(self.seconds, self.nanos) return self.seconds * _NANOS_PER_SECOND + self.nanos def ToMicroseconds(self): """Converts Timestamp to microseconds since epoch.""" - _CheckTimestampValid(self.seconds, self.nanos) return (self.seconds * _MICROS_PER_SECOND + self.nanos // _NANOS_PER_MICROSECOND) def ToMilliseconds(self): """Converts Timestamp to milliseconds since epoch.""" - _CheckTimestampValid(self.seconds, self.nanos) return (self.seconds * _MILLIS_PER_SECOND + self.nanos // _NANOS_PER_MILLISECOND) def ToSeconds(self): """Converts Timestamp to seconds since epoch.""" - _CheckTimestampValid(self.seconds, self.nanos) return self.seconds def FromNanoseconds(self, nanos): """Converts nanoseconds since epoch to Timestamp.""" - seconds = nanos // _NANOS_PER_SECOND - nanos = nanos % _NANOS_PER_SECOND - _CheckTimestampValid(seconds, nanos) - self.seconds = seconds - self.nanos = nanos + self.seconds = nanos // _NANOS_PER_SECOND + self.nanos = nanos % _NANOS_PER_SECOND def FromMicroseconds(self, micros): """Converts microseconds since epoch to Timestamp.""" - seconds = micros // _MICROS_PER_SECOND - nanos = (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND - _CheckTimestampValid(seconds, nanos) - self.seconds = seconds - self.nanos = nanos + self.seconds = micros // _MICROS_PER_SECOND + self.nanos = (micros % _MICROS_PER_SECOND) * _NANOS_PER_MICROSECOND def FromMilliseconds(self, millis): """Converts milliseconds since epoch to Timestamp.""" - seconds = millis // _MILLIS_PER_SECOND - nanos = (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND - _CheckTimestampValid(seconds, nanos) - self.seconds = seconds - self.nanos = nanos + self.seconds = millis // _MILLIS_PER_SECOND + self.nanos = (millis % _MILLIS_PER_SECOND) * _NANOS_PER_MILLISECOND def FromSeconds(self, seconds): """Converts seconds since epoch to Timestamp.""" - _CheckTimestampValid(seconds, 0) self.seconds = seconds self.nanos = 0 @@ -246,7 +229,6 @@ class Timestamp(object): # https://github.com/python/cpython/issues/109849) or full range (on some # platforms, see https://github.com/python/cpython/issues/110042) of # datetime. - _CheckTimestampValid(self.seconds, self.nanos) delta = datetime.timedelta( seconds=self.seconds, microseconds=_RoundTowardZero(self.nanos, _NANOS_PER_MICROSECOND), @@ -270,22 +252,8 @@ class Timestamp(object): # manipulated into a long value of seconds. During the conversion from # struct_time to long, the source date in UTC, and so it follows that the # correct transformation is calendar.timegm() - seconds = calendar.timegm(dt.utctimetuple()) - nanos = dt.microsecond * _NANOS_PER_MICROSECOND - _CheckTimestampValid(seconds, nanos) - self.seconds = seconds - self.nanos = nanos - - -def _CheckTimestampValid(seconds, nanos): - if seconds < _TIMESTAMP_SECONDS_MIN or seconds > _TIMESTAMP_SECONDS_MAX: - raise ValueError( - 'Timestamp is not valid: Seconds {0} must be in range ' - '[-62135596800, 253402300799].'.format(seconds)) - if nanos < 0 or nanos >= _NANOS_PER_SECOND: - raise ValueError( - 'Timestamp is not valid: Nanos {} must be in a range ' - '[0, 999999].'.format(nanos)) + self.seconds = calendar.timegm(dt.utctimetuple()) + self.nanos = dt.microsecond * _NANOS_PER_MICROSECOND class Duration(object): diff --git a/python/google/protobuf/internal/well_known_types_test.py b/python/google/protobuf/internal/well_known_types_test.py index da273166eb..37d2049173 100644 --- a/python/google/protobuf/internal/well_known_types_test.py +++ b/python/google/protobuf/internal/well_known_types_test.py @@ -352,15 +352,27 @@ class TimeUtilTest(TimeUtilTestBase): ) def testNanosOneSecond(self): + # TODO: b/301980950 - Test error behavior instead once ToDatetime validates + # that nanos are in expected range. tz = _TZ_PACIFIC ts = timestamp_pb2.Timestamp(nanos=1_000_000_000) - self.assertRaisesRegex(ValueError, 'Timestamp is not valid', - ts.ToDatetime) + self.assertEqual(ts.ToDatetime(), datetime.datetime(1970, 1, 1, 0, 0, 1)) + self.assertEqual( + ts.ToDatetime(tz), datetime.datetime(1969, 12, 31, 16, 0, 1, tzinfo=tz) + ) def testNanosNegativeOneSecond(self): + # TODO: b/301980950 - Test error behavior instead once ToDatetime validates + # that nanos are in expected range. + tz = _TZ_PACIFIC ts = timestamp_pb2.Timestamp(nanos=-1_000_000_000) - self.assertRaisesRegex(ValueError, 'Timestamp is not valid', - ts.ToDatetime) + self.assertEqual( + ts.ToDatetime(), datetime.datetime(1969, 12, 31, 23, 59, 59) + ) + self.assertEqual( + ts.ToDatetime(tz), + datetime.datetime(1969, 12, 31, 15, 59, 59, tzinfo=tz), + ) def testTimedeltaConversion(self): message = duration_pb2.Duration() @@ -409,10 +421,8 @@ class TimeUtilTest(TimeUtilTestBase): self.assertRaisesRegex(ValueError, 'year (0 )?is out of range', message.FromJsonString, '0000-01-01T00:00:00Z') message.seconds = 253402300800 - self.assertRaisesRegex(ValueError, 'Timestamp is not valid', + self.assertRaisesRegex(OverflowError, 'date value out of range', message.ToJsonString) - self.assertRaisesRegex(ValueError, 'Timestamp is not valid', - message.FromSeconds, -62135596801) def testInvalidDuration(self): message = duration_pb2.Duration()