-
-
Notifications
You must be signed in to change notification settings - Fork 34.5k
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
gh-80620: Support negative timestamps on windows in time.gmtime, time.localtime, and datetime module
#143463
Changes from 4 commits
c8eab29
8ec0eca
3d9f1a2
64c70c3
4f8c2ae
d58444c
97c1bfb
2224f68
09a3dac
d711f28
a34079a
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 various :mod:`datetime` functions. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 */ | ||
|
gesslerpd marked this conversation as resolved.
Outdated
|
||
| #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. */ | ||
|
gesslerpd marked this conversation as resolved.
Outdated
|
||
| // 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; | ||
|
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. Please remove commented/dead code.
Contributor
Author
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. Do you think that
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. If possible, it would be better to fill tm_yday as well.
Contributor
Author
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. Restored |
||
|
|
||
| // 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 */ | ||
|
gesslerpd marked this conversation as resolved.
Outdated
|
||
| 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 */ | ||
|
gesslerpd marked this conversation as resolved.
Outdated
|
||
| 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.