An open API service indexing awesome lists of open source software.

https://github.com/ammaraskar/windows-mktime-issue

Recreation and description of a bug in window's implementation of mktime
https://github.com/ammaraskar/windows-mktime-issue

Last synced: 12 months ago
JSON representation

Recreation and description of a bug in window's implementation of mktime

Awesome Lists containing this project

README

          

# Windows Time Issue

Recreation for an issue with window's `mktime` and Daylight Savings Time
handling.

Root cause is that `mktime` with `tm.tm_isdst = -1` gives answers that are
inconsistent and wrong compared to the equivalent win32 APIs because it does
not handle DST dynamically. It just uses the current DST cutover point for all
years.

Cutting to the chase, here is a basic recreation for a date in 2003 running on
Eastern Time.

```c
#include
#include

int main() {
struct tm tm = {};
tm.tm_year = 2003 - 1900;
tm.tm_mon = 3 - 1;
tm.tm_mday = 22;
tm.tm_hour = 18;
tm.tm_min = 3;
tm.tm_sec = 5;
tm.tm_isdst = -1;
time_t local_timestamp = mktime(&tm);

printf("time: %ld\n", local_timestamp);
printf(" tm_isdst: %d\n", tm.tm_isdst);
return 0;
}
```

### Linux

```
time: 1048374185
tm_isdst: 0
```

### Windows

```
time: 1048370585
tm_isdst: 1
```

That is a difference of 3600 seconds, or, 1 hour.

## Context

Since 2007, DST in the United States has started on the second Sunday of March.

From 1987 to 2006 however, DST would begin at the first Sunday of April.

Here is a calendar of March and April of 2003:

---
### March 2003

| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| | | | | | | 1 |
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| **9** | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 | | | | | |

### April 2003

| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
| | | 1 | 2 | 3 | 4 | 5 |
| **6** | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 | | | |
---

## Workaround

There are win32 APIs that correctly figure out DST that seem to not have this
issue. If we take the recreation above and do:

```c
SYSTEMTIME localTime;
localTime.wYear = 2003;
localTime.wMonth = 2;
localTime.wDay = 22;
localTime.wHour = 18;
localTime.wMinute = 3;
localTime.wSecond = 5;
localTime.wMilliseconds = 0;

SYSTEMTIME systemTime;
TzSpecificLocalTimeToSystemTime(NULL, &localTime, &systemTime);

FILETIME reLocal;
SystemTimeToFileTime(&systemTime, &reLocal);

time_t unix_time_seconds = winrt::clock::to_time_t(winrt::clock::from_file_time(reLocal));
std::cout << "win32: " << unix_time_seconds;
```

Using this, we can get the correct timestamp out of Windows as well.

```
win32: 1048374185
```

This seems to work because `TzSpecificLocalTimeToSystemTime` actually looks at the
dynamic DST information available:

![syscall Trace of TzSpecificLocalTimeToSystemTime](images/TZSpecificLocalTimeTrace.png)

As opposed to `mktime`, which eventually calls into the CRT's `_isindst_nolock`,
at the time of writing it does:

```c
static void __cdecl tzset_from_system_nolock() throw() {
...
if (GetTimeZoneInformation(&tz_info) != 0xFFFFFFFF) {
// ...
}
}

static int __cdecl _isindst_nolock(tm* const tb) throw() {
...
// Convert the start of daylight savings time to dststart:
if (tz_info.DaylightDate.wYear == 0)
{
cvtdate(
transition_type::start_of_dst,
date_type::day_in_month,
tb->tm_year,
tz_info.DaylightDate.wMonth,
tz_info.DaylightDate.wDay,
tz_info.DaylightDate.wDayOfWeek,
0,
tz_info.DaylightDate.wHour,
tz_info.DaylightDate.wMinute,
tz_info.DaylightDate.wSecond,
tz_info.DaylightDate.wMilliseconds);
}
...
}
```