-
-
Notifications
You must be signed in to change notification settings - Fork 33.9k
gh-80620: Support negative timestamps on windows in time.gmtime, time.localtime, and datetime module
#143463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
gh-80620: Support negative timestamps on windows in time.gmtime, time.localtime, and datetime module
#143463
Changes from all commits
c8eab29
8ec0eca
3d9f1a2
64c70c3
4f8c2ae
d58444c
97c1bfb
2224f68
09a3dac
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| Support negative timestamps in :func:`time.gmtime`, :func:`time.localtime`, and various :mod:`datetime` functions. |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -273,6 +273,89 @@ _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 | ||||||
|
|
||||||
| // 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. | ||||||
| // 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 */ | ||||||
|
|
||||||
| // `time.gmtime` and `time.localtime` will return `struct_time` containing this | ||||||
| // not currently used by `datetime` module | ||||||
|
Comment on lines
+350
to
+351
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This function is for the time module. I don't think that this comment about the datetime module is relevant, you can remove it. |
||||||
| 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; | ||||||
|
|
||||||
| return 0; | ||||||
| } | ||||||
| #endif | ||||||
|
|
||||||
|
|
||||||
|
|
@@ -882,10 +965,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 +1323,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 */ | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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 +1366,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 */ | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| 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 | ||||||
|
|
||||||
Uh oh!
There was an error while loading. Please reload this page.