bpo-37642: Update acceptable offsets in timezone (GH-14878)
This fixes an inconsistency between the Python and C implementations of the datetime module. The pure python version of the code was not accepting offsets greater than 23:59 but less than 24:00. This is an accidental legacy of the original implementation, which was put in place before tzinfo allowed sub-minute time zone offsets. GH-14878
This commit is contained in:
parent
ed70a344b5
commit
92c7e30adf
|
@ -2269,7 +2269,7 @@ class timezone(tzinfo):
|
||||||
raise TypeError("fromutc() argument must be a datetime instance"
|
raise TypeError("fromutc() argument must be a datetime instance"
|
||||||
" or None")
|
" or None")
|
||||||
|
|
||||||
_maxoffset = timedelta(hours=23, minutes=59)
|
_maxoffset = timedelta(hours=24, microseconds=-1)
|
||||||
_minoffset = -_maxoffset
|
_minoffset = -_maxoffset
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
@ -2293,8 +2293,11 @@ class timezone(tzinfo):
|
||||||
return f'UTC{sign}{hours:02d}:{minutes:02d}'
|
return f'UTC{sign}{hours:02d}:{minutes:02d}'
|
||||||
|
|
||||||
timezone.utc = timezone._create(timedelta(0))
|
timezone.utc = timezone._create(timedelta(0))
|
||||||
timezone.min = timezone._create(timezone._minoffset)
|
# bpo-37642: These attributes are rounded to the nearest minute for backwards
|
||||||
timezone.max = timezone._create(timezone._maxoffset)
|
# compatibility, even though the constructor will accept a wider range of
|
||||||
|
# values. This may change in the future.
|
||||||
|
timezone.min = timezone._create(-timedelta(hours=23, minutes=59))
|
||||||
|
timezone.max = timezone._create(timedelta(hours=23, minutes=59))
|
||||||
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
|
||||||
|
|
||||||
# Some time zone algebra. For a datetime x, let
|
# Some time zone algebra. For a datetime x, let
|
||||||
|
|
|
@ -388,6 +388,31 @@ class TestTimeZone(unittest.TestCase):
|
||||||
tz_copy = copy.deepcopy(tz)
|
tz_copy = copy.deepcopy(tz)
|
||||||
self.assertIs(tz_copy, tz)
|
self.assertIs(tz_copy, tz)
|
||||||
|
|
||||||
|
def test_offset_boundaries(self):
|
||||||
|
# Test timedeltas close to the boundaries
|
||||||
|
time_deltas = [
|
||||||
|
timedelta(hours=23, minutes=59),
|
||||||
|
timedelta(hours=23, minutes=59, seconds=59),
|
||||||
|
timedelta(hours=23, minutes=59, seconds=59, microseconds=999999),
|
||||||
|
]
|
||||||
|
time_deltas.extend([-delta for delta in time_deltas])
|
||||||
|
|
||||||
|
for delta in time_deltas:
|
||||||
|
with self.subTest(test_type='good', delta=delta):
|
||||||
|
timezone(delta)
|
||||||
|
|
||||||
|
# Test timedeltas on and outside the boundaries
|
||||||
|
bad_time_deltas = [
|
||||||
|
timedelta(hours=24),
|
||||||
|
timedelta(hours=24, microseconds=1),
|
||||||
|
]
|
||||||
|
bad_time_deltas.extend([-delta for delta in bad_time_deltas])
|
||||||
|
|
||||||
|
for delta in bad_time_deltas:
|
||||||
|
with self.subTest(test_type='bad', delta=delta):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
timezone(delta)
|
||||||
|
|
||||||
|
|
||||||
#############################################################################
|
#############################################################################
|
||||||
# Base class for testing a particular aspect of timedelta, time, date and
|
# Base class for testing a particular aspect of timedelta, time, date and
|
||||||
|
|
|
@ -1881,3 +1881,4 @@ Aleksandr Balezin
|
||||||
Robert Leenders
|
Robert Leenders
|
||||||
Tim Hopper
|
Tim Hopper
|
||||||
Dan Lidral-Porter
|
Dan Lidral-Porter
|
||||||
|
Ngalim Siregar
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
Allowed the pure Python implementation of :class:`datetime.timezone` to represent
|
||||||
|
sub-minute offsets close to minimum and maximum boundaries, specifically in the
|
||||||
|
ranges (23:59, 24:00) and (-23:59, 24:00). Patch by Ngalim Siregar
|
|
@ -1099,7 +1099,9 @@ new_timezone(PyObject *offset, PyObject *name)
|
||||||
Py_INCREF(PyDateTime_TimeZone_UTC);
|
Py_INCREF(PyDateTime_TimeZone_UTC);
|
||||||
return PyDateTime_TimeZone_UTC;
|
return PyDateTime_TimeZone_UTC;
|
||||||
}
|
}
|
||||||
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
|
if ((GET_TD_DAYS(offset) == -1 &&
|
||||||
|
GET_TD_SECONDS(offset) == 0 &&
|
||||||
|
GET_TD_MICROSECONDS(offset) < 1) ||
|
||||||
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
|
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
|
||||||
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
|
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
|
||||||
" strictly between -timedelta(hours=24) and"
|
" strictly between -timedelta(hours=24) and"
|
||||||
|
@ -1169,7 +1171,9 @@ call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg)
|
||||||
if (offset == Py_None || offset == NULL)
|
if (offset == Py_None || offset == NULL)
|
||||||
return offset;
|
return offset;
|
||||||
if (PyDelta_Check(offset)) {
|
if (PyDelta_Check(offset)) {
|
||||||
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
|
if ((GET_TD_DAYS(offset) == -1 &&
|
||||||
|
GET_TD_SECONDS(offset) == 0 &&
|
||||||
|
GET_TD_MICROSECONDS(offset) < 1) ||
|
||||||
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
|
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
|
||||||
Py_DECREF(offset);
|
Py_DECREF(offset);
|
||||||
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
|
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
|
||||||
|
@ -6481,6 +6485,9 @@ PyInit__datetime(void)
|
||||||
PyDateTime_TimeZone_UTC = x;
|
PyDateTime_TimeZone_UTC = x;
|
||||||
CAPI.TimeZone_UTC = PyDateTime_TimeZone_UTC;
|
CAPI.TimeZone_UTC = PyDateTime_TimeZone_UTC;
|
||||||
|
|
||||||
|
/* bpo-37642: These attributes are rounded to the nearest minute for backwards
|
||||||
|
* compatibility, even though the constructor will accept a wider range of
|
||||||
|
* values. This may change in the future.*/
|
||||||
delta = new_delta(-1, 60, 0, 1); /* -23:59 */
|
delta = new_delta(-1, 60, 0, 1); /* -23:59 */
|
||||||
if (delta == NULL)
|
if (delta == NULL)
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
Loading…
Reference in New Issue