From c8eab295fd2ee6eaec7e5e1390905b829f910c0d Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Mon, 5 Jan 2026 21:10:27 -0600 Subject: [PATCH 01/10] init --- Python/pytime.c | 126 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 106 insertions(+), 20 deletions(-) diff --git a/Python/pytime.c b/Python/pytime.c index 2f3d854428b4bf..97d9f74c78e32a 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -273,6 +273,86 @@ _PyTime_AsCLong(PyTime_t t, long *t2) *t2 = (long)t; return 0; } + +// 369 years + 89 leap days +#define SECS_BETWEEN_EPOCHS 11644473600LL /* Seconds between 1601-01-01 and 1970-01-01 */ +#define HUNDRED_NS_PER_SEC 10000000LL + +// Convert time_t to struct tm using Windows FILETIME API. +// If is_local is true, convert to local time. */ +// Fallback for negative timestamps that localtime_s/gmtime_s cannot handle. +// Return 0 on success. Return -1 on error. +static int +_PyTime_windows_filetime(time_t timer, struct tm *tm, int is_local) +{ + /* Check for underflow - FILETIME epoch is 1601-01-01 */ + if (timer < -SECS_BETWEEN_EPOCHS) { + PyErr_SetString(PyExc_OverflowError, "timestamp out of range for Windows FILETIME"); + return -1; + } + + /* Convert time_t to FILETIME (100-nanosecond intervals since 1601-01-01) */ + ULONGLONG ticks = ((ULONGLONG)timer + SECS_BETWEEN_EPOCHS) * HUNDRED_NS_PER_SEC; + FILETIME ft; + ft.dwLowDateTime = (DWORD)(ticks); // cast to DWORD truncates to low 32 bits + ft.dwHighDateTime = (DWORD)(ticks >> 32); + + /* Convert FILETIME to SYSTEMTIME */ + SYSTEMTIME st_result; + if (is_local) { + /* Convert to local time */ + FILETIME ft_local; + if (!FileTimeToLocalFileTime(&ft, &ft_local) || + !FileTimeToSystemTime(&ft_local, &st_result)) { + PyErr_SetFromWindowsErr(0); + return -1; + } + } + else { + /* Convert to UTC */ + if (!FileTimeToSystemTime(&ft, &st_result)) { + PyErr_SetFromWindowsErr(0); + return -1; + } + } + + /* Convert SYSTEMTIME to struct tm */ + tm->tm_year = st_result.wYear - 1900; + tm->tm_mon = st_result.wMonth - 1; /* SYSTEMTIME: 1-12, tm: 0-11 */ + tm->tm_mday = st_result.wDay; + tm->tm_hour = st_result.wHour; + tm->tm_min = st_result.wMinute; + tm->tm_sec = st_result.wSecond; + tm->tm_wday = st_result.wDayOfWeek; /* 0=Sunday */ + + /* Calculate day of year using Windows FILETIME difference */ + // SYSTEMTIME st_jan1 = {st_result.wYear, 1, 0, 1, 0, 0, 0, 0}; + // FILETIME ft_jan1, ft_date; + // if (!SystemTimeToFileTime(&st_jan1, &ft_jan1) || + // !SystemTimeToFileTime(&st_result, &ft_date)) { + // PyErr_SetFromWindowsErr(0); + // return -1; + // } + // ULARGE_INTEGER jan1, date; + // jan1.LowPart = ft_jan1.dwLowDateTime; + // jan1.HighPart = ft_jan1.dwHighDateTime; + // date.LowPart = ft_date.dwLowDateTime; + // date.HighPart = ft_date.dwHighDateTime; + // /* Convert 100-nanosecond intervals to days */ + // LONGLONG days_diff = (date.QuadPart - jan1.QuadPart) / (24LL * 60 * 60 * HUNDRED_NS_PER_SEC); + + // tm->tm_yday = (int)days_diff; + + // datetime doesn't rely on tm_yday, so set invalid value and skip calculation + // time.gmtime / time.localtime will return struct_time with out of range tm_yday + // time.mktime doesn't support pre-epoch struct_time on windows anyway + tm->tm_yday = -1; + + /* DST flag: -1 (unknown) for local time on historical dates, 0 for UTC */ + tm->tm_isdst = is_local ? -1 : 0; + + return 0; +} #endif @@ -882,10 +962,8 @@ py_get_system_clock(PyTime_t *tp, _Py_clock_info_t *info, int raise_exc) GetSystemTimePreciseAsFileTime(&system_time); large.u.LowPart = system_time.dwLowDateTime; large.u.HighPart = system_time.dwHighDateTime; - /* 11,644,473,600,000,000,000: number of nanoseconds between - the 1st january 1601 and the 1st january 1970 (369 years + 89 leap - days). */ - PyTime_t ns = (large.QuadPart - 116444736000000000) * 100; + + PyTime_t ns = (large.QuadPart - SECS_BETWEEN_EPOCHS * HUNDRED_NS_PER_SEC) * 100; *tp = ns; if (info) { // GetSystemTimePreciseAsFileTime() is implemented using @@ -1242,15 +1320,19 @@ int _PyTime_localtime(time_t t, struct tm *tm) { #ifdef MS_WINDOWS - int error; - - error = localtime_s(tm, &t); - if (error != 0) { - errno = error; - PyErr_SetFromErrno(PyExc_OSError); - return -1; + if (t >= 0) { + /* For non-negative timestamps, use standard conversion */ + int error = localtime_s(tm, &t); + if (error != 0) { + errno = error; + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + return 0; } - return 0; + + /* For negative timestamps, use FILETIME-based conversion */ + return _PyTime_windows_filetime(t, tm, 1); #else /* !MS_WINDOWS */ #if defined(_AIX) && (SIZEOF_TIME_T < 8) @@ -1281,15 +1363,19 @@ int _PyTime_gmtime(time_t t, struct tm *tm) { #ifdef MS_WINDOWS - int error; - - error = gmtime_s(tm, &t); - if (error != 0) { - errno = error; - PyErr_SetFromErrno(PyExc_OSError); - return -1; + /* For non-negative timestamps, use standard conversion */ + if (t >= 0) { + int error = gmtime_s(tm, &t); + if (error != 0) { + errno = error; + PyErr_SetFromErrno(PyExc_OSError); + return -1; + } + return 0; } - return 0; + + /* For negative timestamps, use FILETIME-based conversion */ + return _PyTime_windows_filetime(t, tm, 0); #else /* !MS_WINDOWS */ if (gmtime_r(&t, tm) == NULL) { #ifdef EINVAL From 8ec0eca473f0c12efd3211a604d0dc00f0f1f611 Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Mon, 5 Jan 2026 21:22:21 -0600 Subject: [PATCH 02/10] remove hacks for previous windows negative timestamp behavior --- Modules/_datetimemodule.c | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/Modules/_datetimemodule.c b/Modules/_datetimemodule.c index 46c4f57984b0df..8f64e572bd6086 100644 --- a/Modules/_datetimemodule.c +++ b/Modules/_datetimemodule.c @@ -5584,22 +5584,7 @@ datetime_from_timet_and_us(PyTypeObject *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 - /* 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. - */ -#ifdef MS_WINDOWS - && (timet - max_fold_seconds > 0) -#endif - ) { + if (tzinfo == Py_None && f == _PyTime_localtime) { long long probe_seconds, result_seconds, transition; result_seconds = utc_to_seconds(year, month, day, From 3d9f1a2ae51b1430b05dee174596c5664156867f Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Mon, 5 Jan 2026 21:24:19 -0600 Subject: [PATCH 03/10] update datetime tests --- Lib/test/datetimetester.py | 88 ++++++++++++++++---------------------- Lib/test/test_time.py | 3 +- 2 files changed, 40 insertions(+), 51 deletions(-) diff --git a/Lib/test/datetimetester.py b/Lib/test/datetimetester.py index ace56aab7aceba..8d39299b3ff442 100644 --- a/Lib/test/datetimetester.py +++ b/Lib/test/datetimetester.py @@ -2706,24 +2706,20 @@ def utcfromtimestamp(*args, **kwargs): self.assertEqual(zero.second, 0) self.assertEqual(zero.microsecond, 0) one = fts(1e-6) - try: - minus_one = fts(-1e-6) - except OSError: - # localtime(-1) and gmtime(-1) is not supported on Windows - pass - else: - self.assertEqual(minus_one.second, 59) - self.assertEqual(minus_one.microsecond, 999999) - - t = fts(-1e-8) - self.assertEqual(t, zero) - t = fts(-9e-7) - self.assertEqual(t, minus_one) - t = fts(-1e-7) - self.assertEqual(t, zero) - t = fts(-1/2**7) - self.assertEqual(t.second, 59) - self.assertEqual(t.microsecond, 992188) + minus_one = fts(-1e-6) + + self.assertEqual(minus_one.second, 59) + self.assertEqual(minus_one.microsecond, 999999) + + t = fts(-1e-8) + self.assertEqual(t, zero) + t = fts(-9e-7) + self.assertEqual(t, minus_one) + t = fts(-1e-7) + self.assertEqual(t, zero) + t = fts(-1/2**7) + self.assertEqual(t.second, 59) + self.assertEqual(t.microsecond, 992188) t = fts(1e-7) self.assertEqual(t, zero) @@ -2752,22 +2748,18 @@ def utcfromtimestamp(*args, **kwargs): self.assertEqual(zero.second, 0) self.assertEqual(zero.microsecond, 0) one = fts(D('0.000_001')) - try: - minus_one = fts(D('-0.000_001')) - except OSError: - # localtime(-1) and gmtime(-1) is not supported on Windows - pass - else: - self.assertEqual(minus_one.second, 59) - self.assertEqual(minus_one.microsecond, 999_999) + minus_one = fts(D('-0.000_001')) + + self.assertEqual(minus_one.second, 59) + self.assertEqual(minus_one.microsecond, 999_999) - t = fts(D('-0.000_000_1')) - self.assertEqual(t, zero) - t = fts(D('-0.000_000_9')) - self.assertEqual(t, minus_one) - t = fts(D(-1)/2**7) - self.assertEqual(t.second, 59) - self.assertEqual(t.microsecond, 992188) + t = fts(D('-0.000_000_1')) + self.assertEqual(t, zero) + t = fts(D('-0.000_000_9')) + self.assertEqual(t, minus_one) + t = fts(D(-1)/2**7) + self.assertEqual(t.second, 59) + self.assertEqual(t.microsecond, 992188) t = fts(D('0.000_000_1')) self.assertEqual(t, zero) @@ -2803,22 +2795,18 @@ def utcfromtimestamp(*args, **kwargs): self.assertEqual(zero.second, 0) self.assertEqual(zero.microsecond, 0) one = fts(F(1, 1_000_000)) - try: - minus_one = fts(F(-1, 1_000_000)) - except OSError: - # localtime(-1) and gmtime(-1) is not supported on Windows - pass - else: - self.assertEqual(minus_one.second, 59) - self.assertEqual(minus_one.microsecond, 999_999) + minus_one = fts(F(-1, 1_000_000)) - t = fts(F(-1, 10_000_000)) - self.assertEqual(t, zero) - t = fts(F(-9, 10_000_000)) - self.assertEqual(t, minus_one) - t = fts(F(-1, 2**7)) - self.assertEqual(t.second, 59) - self.assertEqual(t.microsecond, 992188) + self.assertEqual(minus_one.second, 59) + self.assertEqual(minus_one.microsecond, 999_999) + + t = fts(F(-1, 10_000_000)) + self.assertEqual(t, zero) + t = fts(F(-9, 10_000_000)) + self.assertEqual(t, minus_one) + t = fts(F(-1, 2**7)) + self.assertEqual(t.second, 59) + self.assertEqual(t.microsecond, 992188) t = fts(F(1, 10_000_000)) self.assertEqual(t, zero) @@ -2860,6 +2848,7 @@ def test_timestamp_limits(self): # If that assumption changes, this value can change as well self.assertEqual(max_ts, 253402300799.0) + @unittest.skipIf(sys.platform == "win32", "Windows doesn't support min timestamp") def test_fromtimestamp_limits(self): try: self.theclass.fromtimestamp(-2**32 - 1) @@ -2899,6 +2888,7 @@ def test_fromtimestamp_limits(self): # OverflowError, especially on 32-bit platforms. self.theclass.fromtimestamp(ts) + @unittest.skipIf(sys.platform == "win32", "Windows doesn't support min timestamp") def test_utcfromtimestamp_limits(self): with self.assertWarns(DeprecationWarning): try: @@ -2960,13 +2950,11 @@ def test_insane_utcfromtimestamp(self): self.assertRaises(OverflowError, self.theclass.utcfromtimestamp, insane) - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") def test_negative_float_fromtimestamp(self): # The result is tz-dependent; at least test that this doesn't # fail (like it did before bug 1646728 was fixed). self.theclass.fromtimestamp(-1.05) - @unittest.skipIf(sys.platform == "win32", "Windows doesn't accept negative timestamps") def test_negative_float_utcfromtimestamp(self): with self.assertWarns(DeprecationWarning): d = self.theclass.utcfromtimestamp(-1.05) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index c7e81fff6f776b..f461ebedfc27c6 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -503,10 +503,11 @@ def test_mktime(self): for t in (-2, -1, 0, 1): try: tt = time.localtime(t) + ts = time.mktime(tt) except (OverflowError, OSError): pass else: - self.assertEqual(time.mktime(tt), t) + self.assertEqual(ts, t) # Issue #13309: passing extreme values to mktime() or localtime() # borks the glibc's internal timezone data. From 64c70c395bbc4d1344a6d70316f582558af793b1 Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Mon, 5 Jan 2026 21:38:11 -0600 Subject: [PATCH 04/10] add blurb --- .../next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst diff --git a/Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst b/Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst new file mode 100644 index 00000000000000..e573d41ab41697 --- /dev/null +++ b/Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst @@ -0,0 +1 @@ +Support negative timestamps in various :mod:`datetime` functions. From 4f8c2aeb25a86f23b5fff30af66279c25235b1d7 Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Thu, 8 Jan 2026 17:14:12 -0600 Subject: [PATCH 05/10] restore `tm_yday` calculation, improve `time` test --- Lib/test/test_time.py | 6 +++--- Python/pytime.c | 47 +++++++++++++++++++++++-------------------- 2 files changed, 28 insertions(+), 25 deletions(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index f461ebedfc27c6..a877c5de3be713 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -501,13 +501,13 @@ def test_localtime_without_arg(self): def test_mktime(self): # Issue #1726687 for t in (-2, -1, 0, 1): + t_struct = time.localtime(t) try: - tt = time.localtime(t) - ts = time.mktime(tt) + t1 = time.mktime(t_struct) except (OverflowError, OSError): pass else: - self.assertEqual(ts, t) + self.assertEqual(t1, t) # Issue #13309: passing extreme values to mktime() or localtime() # borks the glibc's internal timezone data. diff --git a/Python/pytime.c b/Python/pytime.c index 97d9f74c78e32a..3531351afc5209 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -278,6 +278,28 @@ _PyTime_AsCLong(PyTime_t t, long *t2) #define SECS_BETWEEN_EPOCHS 11644473600LL /* Seconds between 1601-01-01 and 1970-01-01 */ #define HUNDRED_NS_PER_SEC 10000000LL +// Calculate day of year (0-365) from SYSTEMTIME +static int +_PyTime_calc_yday(const SYSTEMTIME *st) +{ + // Cumulative days before each month (non-leap year) + static const int days_before_month[] = { + 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 + }; + int yday = days_before_month[st->wMonth - 1] + st->wDay - 1; + // Account for leap day if we're past February in a leap year. + if (st->wMonth > 2) { + // Leap year rules (Gregorian calendar): + // - Years divisible by 4 are leap years + // - EXCEPT years divisible by 100 are NOT leap years + // - EXCEPT years divisible by 400 ARE leap years + int year = st->wYear; + int is_leap = (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0); + yday += is_leap; + } + return yday; +} + // Convert time_t to struct tm using Windows FILETIME API. // If is_local is true, convert to local time. */ // Fallback for negative timestamps that localtime_s/gmtime_s cannot handle. @@ -325,28 +347,9 @@ _PyTime_windows_filetime(time_t timer, struct tm *tm, int is_local) tm->tm_sec = st_result.wSecond; tm->tm_wday = st_result.wDayOfWeek; /* 0=Sunday */ - /* Calculate day of year using Windows FILETIME difference */ - // SYSTEMTIME st_jan1 = {st_result.wYear, 1, 0, 1, 0, 0, 0, 0}; - // FILETIME ft_jan1, ft_date; - // if (!SystemTimeToFileTime(&st_jan1, &ft_jan1) || - // !SystemTimeToFileTime(&st_result, &ft_date)) { - // PyErr_SetFromWindowsErr(0); - // return -1; - // } - // ULARGE_INTEGER jan1, date; - // jan1.LowPart = ft_jan1.dwLowDateTime; - // jan1.HighPart = ft_jan1.dwHighDateTime; - // date.LowPart = ft_date.dwLowDateTime; - // date.HighPart = ft_date.dwHighDateTime; - // /* Convert 100-nanosecond intervals to days */ - // LONGLONG days_diff = (date.QuadPart - jan1.QuadPart) / (24LL * 60 * 60 * HUNDRED_NS_PER_SEC); - - // tm->tm_yday = (int)days_diff; - - // datetime doesn't rely on tm_yday, so set invalid value and skip calculation - // time.gmtime / time.localtime will return struct_time with out of range tm_yday - // time.mktime doesn't support pre-epoch struct_time on windows anyway - tm->tm_yday = -1; + // `time.gmtime` and `time.localtime` will return `struct_time` containing this + // not currently used by `datetime` module + tm->tm_yday = _PyTime_calc_yday(&st_result); /* DST flag: -1 (unknown) for local time on historical dates, 0 for UTC */ tm->tm_isdst = is_local ? -1 : 0; From d58444c032295509b52a320c3e287971d884c9dd Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Thu, 8 Jan 2026 17:20:15 -0600 Subject: [PATCH 06/10] update blurb --- .../next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst b/Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst index e573d41ab41697..fb2f500bc45234 100644 --- a/Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst +++ b/Misc/NEWS.d/next/Windows/2026-01-05-21-36-58.gh-issue-80620.p1bD58.rst @@ -1 +1 @@ -Support negative timestamps in various :mod:`datetime` functions. +Support negative timestamps in :func:`time.gmtime`, :func:`time.localtime`, and various :mod:`datetime` functions. From 97c1bfb34f59e5c11409182ce2c408eb9d6f3a26 Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Fri, 9 Jan 2026 12:02:21 -0600 Subject: [PATCH 07/10] add gmtime pre-epoch test --- Lib/test/test_time.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index a877c5de3be713..09fdf991087cb9 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -187,6 +187,24 @@ def test_epoch(self): # Only test the date and time, ignore other gmtime() members self.assertEqual(tuple(epoch)[:6], (1970, 1, 1, 0, 0, 0), epoch) + def test_gmtime(self): + # expected format: + # (tm_year, tm_mon, tm_mday, + # tm_hour, tm_min, tm_sec, + # tm_wday, tm_yday) + for t, expected in ( + (-13262400, (1969, 7, 31, 12, 0, 0, 3, 212)), + (-6177600, (1969, 10, 21, 12, 0, 0, 1, 294)), + # non-leap years (pre epoch) + (-2203891200, (1900, 3, 1, 0, 0, 0, 3, 60)), + (-5359564800, (1800, 3, 1, 0, 0, 0, 3, 60)), + # leap years (pre epoch) + (-2077660800, (1904, 3, 1, 0, 0, 0, 3, 61)), + ): + with self.subTest(t=t, expected=expected): + res = time.gmtime(t) + self.assertEqual(tuple(res)[:8], expected, res) + def test_strftime(self): tt = time.gmtime(self.t) for directive in ('a', 'A', 'b', 'B', 'c', 'd', 'H', 'I', From 2224f6849af7d291492a78ed0338c7e70aa5547a Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Fri, 9 Jan 2026 12:06:28 -0600 Subject: [PATCH 08/10] test yday jumps --- Lib/test/test_time.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 09fdf991087cb9..36da6b216e72e1 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -197,9 +197,12 @@ def test_gmtime(self): (-6177600, (1969, 10, 21, 12, 0, 0, 1, 294)), # non-leap years (pre epoch) (-2203891200, (1900, 3, 1, 0, 0, 0, 3, 60)), + (-2203977600, (1900, 2, 28, 0, 0, 0, 2, 59)), (-5359564800, (1800, 3, 1, 0, 0, 0, 3, 60)), + (-5359651200, (1800, 2, 28, 0, 0, 0, 2, 59)), # leap years (pre epoch) (-2077660800, (1904, 3, 1, 0, 0, 0, 3, 61)), + (-2077833600, (1904, 2, 28, 0, 0, 0, 1, 59)), ): with self.subTest(t=t, expected=expected): res = time.gmtime(t) From 09a3dacdc287c63cf3f6493102da8afec57a6507 Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Fri, 9 Jan 2026 12:17:52 -0600 Subject: [PATCH 09/10] fix tm_wday copy/paste mistake --- Lib/test/test_time.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Lib/test/test_time.py b/Lib/test/test_time.py index 36da6b216e72e1..3f8952ae8c9a5c 100644 --- a/Lib/test/test_time.py +++ b/Lib/test/test_time.py @@ -198,11 +198,11 @@ def test_gmtime(self): # non-leap years (pre epoch) (-2203891200, (1900, 3, 1, 0, 0, 0, 3, 60)), (-2203977600, (1900, 2, 28, 0, 0, 0, 2, 59)), - (-5359564800, (1800, 3, 1, 0, 0, 0, 3, 60)), - (-5359651200, (1800, 2, 28, 0, 0, 0, 2, 59)), + (-5359564800, (1800, 3, 1, 0, 0, 0, 5, 60)), + (-5359651200, (1800, 2, 28, 0, 0, 0, 4, 59)), # leap years (pre epoch) - (-2077660800, (1904, 3, 1, 0, 0, 0, 3, 61)), - (-2077833600, (1904, 2, 28, 0, 0, 0, 1, 59)), + (-2077660800, (1904, 3, 1, 0, 0, 0, 1, 61)), + (-2077833600, (1904, 2, 28, 0, 0, 0, 6, 59)), ): with self.subTest(t=t, expected=expected): res = time.gmtime(t) From d711f282ded2d007713fb78a0fd75ac8c71fd2b8 Mon Sep 17 00:00:00 2001 From: Peter Gessler Date: Mon, 12 Jan 2026 16:53:30 -0600 Subject: [PATCH 10/10] review fixes --- Python/pytime.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Python/pytime.c b/Python/pytime.c index 3531351afc5209..1f48984329a5b8 100644 --- a/Python/pytime.c +++ b/Python/pytime.c @@ -348,7 +348,6 @@ _PyTime_windows_filetime(time_t timer, struct tm *tm, int is_local) tm->tm_wday = st_result.wDayOfWeek; /* 0=Sunday */ // `time.gmtime` and `time.localtime` will return `struct_time` containing this - // not currently used by `datetime` module tm->tm_yday = _PyTime_calc_yday(&st_result); /* DST flag: -1 (unknown) for local time on historical dates, 0 for UTC */ @@ -1324,7 +1323,7 @@ _PyTime_localtime(time_t t, struct tm *tm) { #ifdef MS_WINDOWS if (t >= 0) { - /* For non-negative timestamps, use standard conversion */ + /* For non-negative timestamps, use localtime_s() */ int error = localtime_s(tm, &t); if (error != 0) { errno = error; @@ -1366,7 +1365,7 @@ int _PyTime_gmtime(time_t t, struct tm *tm) { #ifdef MS_WINDOWS - /* For non-negative timestamps, use standard conversion */ + /* For non-negative timestamps, use gmtime_s() */ if (t >= 0) { int error = gmtime_s(tm, &t); if (error != 0) {