8000 bpo-37642: Update acceptable offsets in timezone (GH-14878) (#15226) · python/cpython@ed44b84 · GitHub
[go: up one dir, main page]

Skip to content

Commit ed44b84

Browse files
authored
bpo-37642: Update acceptable offsets in timezone (GH-14878) (#15226)
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 (cherry picked from commit 92c7e30)
1 parent 6ac851f commit ed44b84

File tree

5 files changed

+44
-5
lines changed

5 files changed

+44
-5
lines changed

Lib/datetime.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2226,7 +2226,7 @@ def fromutc(self, dt):
22262226
raise TypeError("fromutc() argument must be a datetime instance"
22272227
" or None")
22282228

2229-
_maxoffset = timedelta(hours=23, minutes=59)
2229+
_maxoffset = timedelta(hours=24, microseconds=-1)
22302230
_minoffset = -_maxoffset
22312231

22322232
@staticmethod
@@ -2250,8 +2250,11 @@ def _name_from_offset(delta):
22502250
return f'UTC{sign}{hours:02d}:{minutes:02d}'
22512251

22522252
timezone.utc = timezone._create(timedelta(0))
2253-
timezone.min = timezone._create(timezone._minoffset)
2254-
timezone.max = timezone._create(timezone._maxoffset)
2253+
# bpo-37642: These attributes are rounded to the nearest minute for backwards
2254+
# compatibility, even though the constructor will accept a wider range of
2255+
# values. This may change in the future.
2256+
timezone.min = timezone._create(-timedelta(hours=23, minutes=59))
2257+
timezone.max = timezone._create(timedelta(hours=23, minutes=59))
22552258
_EPOCH = datetime(1970, 1, 1, tzinfo=timezone.utc)
22562259

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

Lib/test/datetimetester.py

Lines changed: 25 additions & 0 deletions
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< 8000 /td>+
# 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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1836,3 +1836,4 @@ Doug Zongker
18361836
Peter Åstrand
18371837
Zheao Li
18381838
Geoff Shannon
1839+
Ngalim Siregar
Lines changed: 3 additions & 0 deletions
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

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1085,7 +1085,9 @@ new_timezone(PyObject *offset, PyObject *name)
10851085
Py_INCREF(PyDateTime_TimeZone_UTC);
10861086
return PyDateTime_TimeZone_UTC;
10871087
}
1088-
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
1088+
if ((GET_TD_DAYS(offset) == -1 &&
1089+
GET_TD_SECONDS(offset) == 0 &&
1090+
GET_TD_MICROSECONDS(offset) < 1) ||
10891091
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
10901092
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
10911093
" strictly between -timedelta(hours=24) and"
@@ -1155,7 +1157,9 @@ call_tzinfo_method(PyObject *tzinfo, const char *name, PyObject *tzinfoarg)
11551157
if (offset == Py_None || offset == NULL)
11561158
return offset;
11571159
if (PyDelta_Check(offset)) {
1158-
if ((GET_TD_DAYS(offset) == -1 && GET_TD_SECONDS(offset) == 0) ||
1160+
if ((GET_TD_DAYS(offset) == -1 &&
1161+
GET_TD_SECONDS(offset) == 0 &&
1162+
GET_TD_MICROSECONDS(offset) < 1) ||
11591163
GET_TD_DAYS(offset) < -1 || GET_TD_DAYS(offset) >= 1) {
11601164
Py_DECREF(offset);
11611165
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
@@ -6383,6 +6387,9 @@ PyInit__datetime(void)
63836387
PyDateTime_TimeZone_UTC = x;
63846388
CAPI.TimeZone_UTC = PyDateTime_TimeZone_UTC;
63856389

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

0 commit comments

Comments
 (0)
0