]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
bpo-37642: Update acceptable offsets in timezone (GH-14878) (#15226)
authorPaul Ganssle <pganssle@users.noreply.github.com>
Thu, 15 Aug 2019 19:09:37 +0000 (15:09 -0400)
committerGitHub <noreply@github.com>
Thu, 15 Aug 2019 19:09:37 +0000 (15:09 -0400)
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 92c7e30adf5c81a54d6e5e555a6bdfaa60157a0d)

Lib/datetime.py
Lib/test/datetimetester.py
Misc/ACKS
Misc/NEWS.d/next/Library/2019-07-21-20-59-31.bpo-37642.L61Bvy.rst [new file with mode: 0644]
Modules/_datetimemodule.c

index 0485b0523e8d538a59cb3afc5ae4d310c804bc6c..5a2ee6a2035a0720fe22b6813123c99c3b3eadb1 100644 (file)
@@ -2226,7 +2226,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
@@ -2250,8 +2250,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
index 8a571697971691c0ac47f0c0c5095ef814c7bdb4..025e71de78727f1aab2a2e6c7481fb8500d256ae 100644 (file)
@@ -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
index 4d8f96acfe301e6e7111ec40f49431856f8b981c..0283c85b174e8c53c28996f6daec5afde824234f 100644 (file)
--- a/Misc/ACKS
+++ b/Misc/ACKS
@@ -1836,3 +1836,4 @@ Doug Zongker
 Peter Åstrand
 Zheao Li
 Geoff Shannon
+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 (file)
index 0000000..09ff257
--- /dev/null
@@ -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
index aa759b115f0efbfa4de470e631239dfcd3ddf3a1..655be364d9cbec5f99e7cbfd6b2f7eb0f07ffcad 100644 (file)
@@ -1085,7 +1085,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"
@@ -1155,7 +1157,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"
@@ -6383,6 +6387,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;