summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNgalim Siregar <ngalim.siregar@gmail.com>2019-08-09 14:22:16 (GMT)
committerPaul Ganssle <pganssle@users.noreply.github.com>2019-08-09 14:22:16 (GMT)
commit92c7e30adf5c81a54d6e5e555a6bdfaa60157a0d (patch)
treef7076888f795a4e3198ff4ca3ef189551891725b
parented70a344b5fbddea85726ebc1964ee0cfdef9c40 (diff)
downloadcpython-92c7e30adf5c81a54d6e5e555a6bdfaa60157a0d.zip
cpython-92c7e30adf5c81a54d6e5e555a6bdfaa60157a0d.tar.gz
cpython-92c7e30adf5c81a54d6e5e555a6bdfaa60157a0d.tar.bz2
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
-rw-r--r--Lib/datetime.py9
-rw-r--r--Lib/test/datetimetester.py25
-rw-r--r--Misc/ACKS1
-rw-r--r--Misc/NEWS.d/next/Library/2019-07-21-20-59-31.bpo-37642.L61Bvy.rst3
-rw-r--r--Modules/_datetimemodule.c11
5 files changed, 44 insertions, 5 deletions
diff --git a/Lib/datetime.py b/Lib/datetime.py
index d4c7a1f..0adf1dd 100644
--- a/Lib/datetime.py
+++ b/Lib/datetime.py
@@ -2269,7 +2269,7 @@ class timezone(tzinfo):
raise TypeError("fromutc() argument must be a datetime instance"
" or None")
- _maxoffset = timedelta(hours=23, minutes=59)
+ _maxoffset = timedelta(hours=24, microseconds=-1)
_minoffset = -_maxoffset
@staticmethod
@@ -2293,8 +2293,11 @@ class timezone(tzinfo):
return f'UTC{sign}{hours:02d}:{minutes:02d}'
timezone.utc = timezone._create(timedelta(0))
-timezone.min = timezone._create(timezone._minoffset)
-timezone.max = timezone._create(timezone._maxoffset)
+# 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.
+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)
# Some time zone algebra. For a datetime x, let
diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py
index 99b620c..d0101c9 100644
--- a/Lib/test/datetimetester.py
+++ b/Lib/test/datetimetester.py
@@ -388,6 +388,31 @@ class TestTimeZone(unittest.TestCase):
tz_copy = copy.deepcopy(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
diff --git a/Misc/ACKS b/Misc/ACKS
index 8c834b4..3b4cf85 100644
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1881,3 +1881,4 @@ Aleksandr Balezin
Robert Leenders
Tim Hopper
Dan Lidral-Porter
+Ngalim Siregar
diff --git a/Misc/NEWS.d/next/Library/2019-07-21-20-59-31.bpo-37642.L61Bvy.rst b/Misc/NEWS.d/next/Library/2019-07-21-20-59-31.bpo-37642.L61Bvy.rst
new file mode 100644
index 0000000..09ff257
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-07-21-20-59-31.bpo-37642.L61Bvy.rst
@@ -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
diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c
index b55922c..6d28b3e 100644
--- a/Modules/_datetimemodule.c
+++ b/Modules/_datetimemodule.c
@@ -1099,7 +1099,9 @@ new_timezone(PyObject *offset, PyObject *name)
Py_INCREF(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) {
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
" 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)
return 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) {
Py_DECREF(offset);
PyErr_Format(PyExc_ValueError, "offset must be a timedelta"
@@ -6481,6 +6485,9 @@ PyInit__datetime(void)
PyDateTime_TimeZone_UTC = x;
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 */
if (delta == NULL)
return NULL;