Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 92c7e30

Browse files
nsiregarpganssle
authored andcommitted
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
1 parent ed70a34 commit 92c7e30

File tree

5 files changed

+44
-5
lines changed

5 files changed

+44
-5
lines changed

Lib/datetime.py

+6-3
Original file line numberDiff line numberDiff line change
@@ -2269,7 +2269,7 @@ def fromutc(self, dt):
22692269
raise TypeError("fromutc() argument must be a datetime instance"
22702270
" or None")
22712271

2272-
_maxoffset = timedelta(hours=23, minutes=59)
2272+
_maxoffset = timedelta(hours=24, microseconds=-1)
22732273
_minoffset = -_maxoffset
22742274

22752275
@staticmethod
@@ -2293,8 +2293,11 @@ def _name_from_offset(delta):
22932293
return f'UTC{sign}{hours:02d}:{minutes:02d}'
22942294

22952295
timezone.utc = timezone._create(timedelta(0))
2296-
timezone.min = timezone._create(timezone._minoffset)
2297-
timezone.max = timezone._create(timezone._maxoffset)
2296+
# bpo-37642: These attributes are rounded to the nearest minute for backwards
2297+
# compatibility, even though the constructor will accept a wider range of
2298+
# values. This may change in the future.
2299+
timezone.min = timezone._create(-timedelta(hours=23, minutes=59))
2300+
timezone.max = timezone._create(timedelta(hours=23, minutes=59))
22982301
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
22992302

23002303
# Some time zone algebra. For a datetime x, let

Lib/test/datetimetester.py

+25
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,31 @@ def test_deepcopy(self):
388388
tz_copy = copy.deepcopy(tz)
389389
self.assertIs(tz_copy, tz)
390390

391+
def test_offset_boundaries(self):
392+
# Test timedeltas close to the boundaries
393+
time_deltas = [
394+
timedelta(hours=23, minutes=59),
395+
timedelta(hours=23, minutes=59, seconds=59),
396+
timedelta(hours=23, minutes=59, seconds=59, microseconds=999999),
397+
]
398+
time_deltas.extend([-delta for delta in time_deltas])
399+
400+
for delta in time_deltas:
401+
with self.subTest(test_type='good', delta=delta):
402+
timezone(delta)
403+
404+
# Test timedeltas on and outside the boundaries
405+
bad_time_deltas = [
406+
timedelta(hours=24),
407+
timedelta(hours=24, microseconds=1),
408+
]
409+
bad_time_deltas.extend([-delta for delta in bad_time_deltas])
410+
411+
for delta in bad_time_deltas:
412+
with self.subTest(test_type='bad', delta=delta):
413+
with self.assertRaises(ValueError):
414+
timezone(delta)
415+
391416

392417
#############################################################################
393418
# Base class for testing a particular aspect of timedelta, time, date and

Misc/ACKS

+1
Original file line numberDiff line numberDiff line change
@@ -1881,3 +1881,4 @@ Aleksandr Balezin
18811881
Robert Leenders
18821882
Tim Hopper
18831883
Dan Lidral-Porter
1884+
Ngalim Siregar
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
Allowed the pure Python implementation of :class:`datetime.timezone` to represent
2+
sub-minute offsets close to minimum and maximum boundaries, specifically in the
3+
ranges (23:59, 24:00) and (-23:59, 24:00). Patch by Ngalim Siregar

Modules/_datetimemodule.c

+9-2
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,9 @@ new_timezone(PyObject *offset, PyObject *name)
10991099
Py_INCREF(PyDateTime_TimeZone_UTC);
11001100
return PyDateTime_TimeZone_UTC;
11011101
}
1102-
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
1102+
if ((GET_TD_DAYS(offset) == -1 &&
1103+
GET_TD_SECONDS(offset) == 0 &&
1104+
GET_TD_MICROSECONDS(offset) < 1) ||
11031105
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
11041106
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
11051107
" strictly between -timedelta(hours=24) and"
@@ -1169,7 +1171,9 @@ call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg)
11691171
if (offset == Py_None || offset == NULL)
11701172
return offset;
11711173
if (PyDelta_Check(offset)) {
1172-
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
1174+
if ((GET_TD_DAYS(offset) == -1 &&
1175+
GET_TD_SECONDS(offset) == 0 &&
1176+
GET_TD_MICROSECONDS(offset) < 1) ||
11731177
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
11741178
Py_DECREF(offset);
11751179
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
@@ -6481,6 +6485,9 @@ PyInit__datetime(void)
64816485
PyDateTime_TimeZone_UTC = x;
64826486
CAPI.TimeZone_UTC = PyDateTime_TimeZone_UTC;
64836487

6488+
/* bpo-37642: These attributes are rounded to the nearest minute for backwards
6489+
* compatibility, even though the constructor will accept a wider range of
6490+
* values. This may change in the future.*/
64846491
delta = new_delta(-1, 60, 0, 1); /* -23:59 */
64856492
if (delta == NULL)
64866493
return NULL;

0 commit comments

Comments
 (0)