@@ -1263,19 +1263,122 @@ PyTime_PerfCounterRaw(PyTime_t *result)
12631263 return PyTime_MonotonicRaw (result );
12641264}
12651265
1266+ #ifdef MS_WINDOWS
1267+ static int _cumulativeDaysInMonth [12 ] = {
1268+ 0 , 31 , 59 , 90 , 120 , 151 , 181 , 212 , 243 , 273 , 304 , 334
1269+ };
1270+ static int _cumulativeDaysInMonthLeap [12 ] = {
1271+ 0 , 31 , 60 , 91 , 121 , 152 , 182 , 213 , 244 , 274 , 305 , 335
1272+ };
1273+
1274+ time_t FILETIME_diff_seconds (FILETIME t1 , FILETIME t2 ) {
1275+ ULARGE_INTEGER t1_int ;
1276+ t1_int .LowPart = t1 .dwLowDateTime ;
1277+ t1_int .HighPart = t1 .dwHighDateTime ;
1278+
1279+ ULARGE_INTEGER t2_int ;
1280+ t2_int .LowPart = t2 .dwLowDateTime ;
1281+ t2_int .HighPart = t2 .dwHighDateTime ;
1282+
1283+ ULARGE_INTEGER difference ;
1284+ difference .QuadPart = t1_int .QuadPart - t2_int .QuadPart ;
1285+ // Convert from hundreds of NS to seconds.
1286+ return (time_t ) difference .QuadPart / (10 * SEC_TO_US );
1287+ }
1288+ #endif
12661289
12671290int
12681291_PyTime_localtime (time_t t , struct tm * tm )
12691292{
12701293#ifdef MS_WINDOWS
1271- int error ;
1294+ // While Windows does have unix-like localtime functions in the CRT, they
1295+ // more constrained than a usual libc on a unix system. In particular, they
1296+ // don't support negative numbers.
1297+ if (t >= 0 ) {
1298+ int error ;
12721299
1273- error = localtime_s (tm , & t );
1274- if (error != 0 ) {
1275- errno = error ;
1276- PyErr_SetFromErrno (PyExc_OSError );
1300+ error = localtime_s (tm , & t );
1301+ if (error != 0 ) {
1302+ errno = error ;
1303+ PyErr_SetFromErrno (PyExc_OSError );
1304+ return -1 ;
1305+ }
1306+ return 0 ;
1307+ }
1308+ // For negative numbers, we use the equivalent win32 APIs, which involves
1309+ // converting our time_t to a win32 FILETIME.
1310+ // Windows doesn't provide an API to do this from C, only in the C++ WinRT
1311+ // * https://devblogs.microsoft.com/oldnewthing/20220602-00/?p=106706
1312+ // * https://learn.microsoft.com/en-us/windows/win32/sysinfo/converting-a-time-t-value-to-a-file-time
1313+ // We perform that conversion here. Firstly, Windows FILETIME uses hundredths
1314+ // of nanoseconds instead of seconds.
1315+ #define HUNDREDTH_NANOSECONDS_IN_SECONDS 10000000LL
1316+ // Secondly, the Windows epoch is 1601 instead of 1970, so this represents
1317+ // the number of hundredths of nanoseconds between those years:
1318+ // * https://www.wolframalpha.com/input?i=January+1%2C+1970+-+%28116444736000000000+*+100%29+nanoseconds
1319+ #define UNIX_EPOCH_TO_WINDOWS_HUNDREDTH_NS 116444736000000000LL;
1320+ ULARGE_INTEGER time_value ;
1321+ time_value .QuadPart = (t * HUNDREDTH_NANOSECONDS_IN_SECONDS ) + UNIX_EPOCH_TO_WINDOWS_HUNDREDTH_NS ;
1322+
1323+ FILETIME tAsFiletime ;
1324+ tAsFiletime .dwLowDateTime = time_value .LowPart ;
1325+ tAsFiletime .dwHighDateTime = time_value .HighPart ;
1326+
1327+ SYSTEMTIME utcSystemTime ;
1328+ if (!FileTimeToSystemTime (& tAsFiletime , & utcSystemTime )) {
1329+ PyErr_SetFromWindowsErr (GetLastError ());
12771330 return -1 ;
12781331 }
1332+
1333+ SYSTEMTIME localTime ;
1334+ if (!SystemTimeToTzSpecificLocalTimeEx (NULL , & utcSystemTime , & localTime )) {
1335+ PyErr_SetFromWindowsErr (GetLastError ());
1336+ return -1 ;
1337+ }
1338+
1339+ // SYSTEMTIME just has the year number, `struct tm` is years since 1900.
1340+ tm -> tm_year = localTime .wYear - 1900 ;
1341+ // SYSTEMTIME uses 1-indexed months, `struct tm` is 0-indexed.
1342+ tm -> tm_mon = localTime .wMonth - 1 ;
1343+ tm -> tm_wday = localTime .wDayOfWeek ;
1344+ tm -> tm_mday = localTime .wDay ;
1345+ tm -> tm_hour = localTime .wHour ;
1346+ tm -> tm_min = localTime .wMinute ;
1347+ tm -> tm_sec = localTime .wSecond ;
1348+
1349+ // We have two remaining fields in the `tm` struct to fill:
1350+ // `tm_yday` is the day in the current year, this is a function of whether
1351+ // we are in a leap year or not.
1352+ if (false /*is_leap_year*/ ) {
1353+ tm -> tm_yday = _cumulativeDaysInMonthLeap [tm -> tm_mon ];
1354+ } else {
1355+ tm -> tm_yday = _cumulativeDaysInMonth [tm -> tm_mon ];
1356+ }
1357+ tm -> tm_yday += (localTime .wDay - 1 );
1358+
1359+ // `tm_isdst` says whether or not DST was in effect locally at this time.
1360+ // This is a little trickier.
1361+ tm -> tm_isdst = -1 ;
1362+ /*
1363+ FILETIME localTimeAsFiletime;
1364+ if (!SystemTimeToFileTime(&localTime, &localTimeAsFiletime)) {
1365+ PyErr_SetFromWindowsErr(GetLastError());
1366+ return -1;
1367+ }
1368+
1369+ time_t utc_diff = FILETIME_diff_seconds(tAsFiletime, localTimeAsFiletime);
1370+
1371+ TIME_ZONE_INFORMATION tzInfo;
1372+ if (!GetTimeZoneInformationForYear(localTime.wYear, NULL, &tzInfo)) {
1373+ PyErr_SetFromWindowsErr(GetLastError());
1374+ return -1;
1375+ }
1376+
1377+ tm->tm_isdst = 0;
1378+ if (tzInfo.Bias + tzInfo.StandardBias + tzInfo.DaylightBias == utc_diff / 60) {
1379+ tm->tm_isdst = 1;
1380+ }*/
1381+
12791382 return 0 ;
12801383#else /* !MS_WINDOWS */
12811384
0 commit comments