From 8f163d3b24f76cd861a2c98014b291a10021923b Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Sat, 24 Jun 2017 15:49:58 -0700 Subject: [PATCH 1/7] Forego fold detection for timestamps in the range [0, 86399] on Windows. On Windows, passing a negative value to local results in an OSError because localtime_s on Windows does not support negative timestamps. Unfortunately this means that fold detection for timestamps between 0 and max_fold_seconds will result in this OSError since we subtract max_fold_seconds from the timestamp to detect a fold. However, since we know there haven't been any folds in the interval [0, max_fold_seconds) in any timezone, we can hackily just forego fold detection for this time range on Windows. --- Modules/_datetimemodule.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index cc7eee6fd7f7d8..c762787087e962 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4625,7 +4625,21 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, second = Py_MIN(59, tm.tm_sec); /* local timezone requires to compute fold */ - if (tzinfo == Py_None && f == _PyTime_localtime) { + if (tzinfo == Py_None && f == _PyTime_localtime + /* On Windows, passing a negative value to local results + * in an OSError because localtime_s on Windows does + * not support negative timestamps. Unfortunately this + * means that fold detection for time values between + * 0 and max_fold_seconds will result in an identical + * error since we subtract max_fold_seconds to detect a + * fold. However, since we know there haven't been any + * folds in the interval [0, max_fold_seconds) in any + * timezone, we can hackily just forego fold detection + * for this time range on Windows. */ +#ifdef MS_WINDOWS + && (timet - max_fold_seconds > 0) +#endif + ) { long long probe_seconds, result_seconds, transition; result_seconds = utc_to_seconds(year, month, day, From 488bc8b7b02f2ff45f1b51bcf1d003f55572707e Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Sat, 24 Jun 2017 16:00:12 -0700 Subject: [PATCH 2/7] Add test to ensure there is no OSError for a timestamp of 0 --- Lib/test/datetimetester.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 436cbea9669e62..b92c241334e5f1 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -4944,6 +4944,13 @@ def test_fromtimestamp_lord_howe(self): self.assertEqual(t0.fold, 0) self.assertEqual(t1.fold, 1) + + def test_fromtimestamp_low_fold_detection(self): + # Ensure that fold detection doesn't cause an + # OSError for really low values, see bpo-29097 + self.assertEqual(datetime.fromtimestamp(0).fold, 0) + + @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') def test_timestamp(self): dt0 = datetime(2014, 11, 2, 1, 30) From 008778faf8d44e6f46c262f5ad207c098aacda96 Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Wed, 16 May 2018 11:39:24 -0700 Subject: [PATCH 3/7] Add news entry, fix style nits --- Lib/test/datetimetester.py | 2 -- .../2018-05-16-11-31-17.bpo-29097.9mqEuI.rst | 2 ++ Modules/_datetimemodule.c | 19 ++++++++++--------- 3 files changed, 12 insertions(+), 11 deletions(-) create mode 100644 Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index b92c241334e5f1..2c92bc3752e5f3 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -4944,13 +4944,11 @@ def test_fromtimestamp_lord_howe(self): self.assertEqual(t0.fold, 0) self.assertEqual(t1.fold, 1) - def test_fromtimestamp_low_fold_detection(self): # Ensure that fold detection doesn't cause an # OSError for really low values, see bpo-29097 self.assertEqual(datetime.fromtimestamp(0).fold, 0) - @support.run_with_tz('EST+05EDT,M3.2.0,M11.1.0') def test_timestamp(self): dt0 = datetime(2014, 11, 2, 1, 30) diff --git a/Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst b/Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst new file mode 100644 index 00000000000000..dc715814c38bfd --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst @@ -0,0 +1,2 @@ +Fix bug where ``datetime.fromtimestamp`` erronously throws an OSError on +Windows for values between 0 and 86400 diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index c762787087e962..332bc3cbeab83f 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4627,15 +4627,16 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, /* local timezone requires to compute fold */ if (tzinfo == Py_None && f == _PyTime_localtime /* On Windows, passing a negative value to local results - * in an OSError because localtime_s on Windows does - * not support negative timestamps. Unfortunately this - * means that fold detection for time values between - * 0 and max_fold_seconds will result in an identical - * error since we subtract max_fold_seconds to detect a - * fold. However, since we know there haven't been any - * folds in the interval [0, max_fold_seconds) in any - * timezone, we can hackily just forego fold detection - * for this time range on Windows. */ + * in an OSError because localtime_s on Windows does + * not support negative timestamps. Unfortunately this + * means that fold detection for time values between + * 0 and max_fold_seconds will result in an identical + * error since we subtract max_fold_seconds to detect a + * fold. However, since we know there haven't been any + * folds in the interval [0, max_fold_seconds) in any + * timezone, we can hackily just forego fold detection + * for this time range. + */ #ifdef MS_WINDOWS && (timet - max_fold_seconds > 0) #endif From 636ec5d402f18b9fc0c2616bcf4134a9006b5016 Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Wed, 16 May 2018 13:29:04 -0700 Subject: [PATCH 4/7] Add fold skipping in pure python datetime --- Lib/datetime.py | 11 +++++++++++ Modules/_datetimemodule.c | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index 8fa18a78932c04..bd60747c441635 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1572,6 +1572,17 @@ def _fromtimestamp(cls, t, utc, tz): # 23 hours at 1969-09-30 13:00:00 in Kwajalein. # Let's probe 24 hours in the past to detect a transition: max_fold_seconds = 24 * 3600 + + # On Windows localtime_s throws an OSError for negative values, + # thus we can't perform fold detection for values of time less + # than the max time fold. See comments in _datetimemodule's + # version of this method for more details. + try: + converter(-1) + except OSError: + if t < max_fold_seconds: + return result + y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6] probe1 = cls(y, m, d, hh, mm, ss, us, tz) trans = result - probe1 - timedelta(0, max_fold_seconds) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 332bc3cbeab83f..f00061f6fbe50b 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -4640,7 +4640,7 @@ datetime_from_timet_and_us(PyObject *cls, TM_FUNC f, time_t timet, int us, #ifdef MS_WINDOWS && (timet - max_fold_seconds > 0) #endif - ) { + ) { long long probe_seconds, result_seconds, transition; result_seconds = utc_to_seconds(year, month, day, From 9202447e7b45988c9b2a1320bfbaa686b9d4efdd Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Thu, 5 Jul 2018 21:01:02 -0400 Subject: [PATCH 5/7] Make Lib/datetime.py use a platform check --- Lib/datetime.py | 8 +++----- Lib/test/datetimetester.py | 2 +- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index bd60747c441635..42ee1e656f6a7c 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -6,6 +6,7 @@ import time as _time import math as _math +import sys def _cmp(x, y): return 0 if x == y else 1 if x > y else -1 @@ -1577,11 +1578,8 @@ def _fromtimestamp(cls, t, utc, tz): # thus we can't perform fold detection for values of time less # than the max time fold. See comments in _datetimemodule's # version of this method for more details. - try: - converter(-1) - except OSError: - if t < max_fold_seconds: - return result + if sys.platform.startswith("win") and t < max_fold_seconds: + return result y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6] probe1 = cls(y, m, d, hh, mm, ss, us, tz) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index 2c92bc3752e5f3..ff51a284cbcfa1 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -70,7 +70,7 @@ def test_name_cleanup(self): if not name.startswith('__') and not name.endswith('__')) allowed = set(['MAXYEAR', 'MINYEAR', 'date', 'datetime', 'datetime_CAPI', 'time', 'timedelta', 'timezone', - 'tzinfo']) + 'tzinfo', 'sys']) self.assertEqual(names - allowed, set([])) def test_divide_and_round(self): From 7241000499d9c891ea89d5a6f6ad39a89306c48d Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Fri, 6 Jul 2018 04:51:31 -0400 Subject: [PATCH 6/7] Adjust blurb message --- .../next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst b/Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst index dc715814c38bfd..a59efc737f533a 100644 --- a/Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst +++ b/Misc/NEWS.d/next/Windows/2018-05-16-11-31-17.bpo-29097.9mqEuI.rst @@ -1,2 +1,3 @@ -Fix bug where ``datetime.fromtimestamp`` erronously throws an OSError on -Windows for values between 0 and 86400 +Fix bug where :meth:`datetime.fromtimestamp` erronously throws an +:exc:`OSError` on Windows for values between 0 and 86400. +Patch by Ammar Askar. From 64ae51a162c217350bb7dfdf914777b0d3a0014a Mon Sep 17 00:00:00 2001 From: Ammar Askar Date: Tue, 24 Jul 2018 18:32:32 -0700 Subject: [PATCH 7/7] Short cut windows check --- Lib/datetime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/datetime.py b/Lib/datetime.py index 42ee1e656f6a7c..6272caf38ac550 100644 --- a/Lib/datetime.py +++ b/Lib/datetime.py @@ -1578,7 +1578,7 @@ def _fromtimestamp(cls, t, utc, tz): # thus we can't perform fold detection for values of time less # than the max time fold. See comments in _datetimemodule's # version of this method for more details. - if sys.platform.startswith("win") and t < max_fold_seconds: + if t < max_fold_seconds and sys.platform.startswith("win"): return result y, m, d, hh, mm, ss = converter(t - max_fold_seconds)[:6]