3
votes

I'm attempting to modify the creation dates of files to the date they were released. I'm first converting a string such as "2 April 2005" into a std::tm. I then create a SYSTEMTIME as follows:

std::tm dt = from_string("2 April 2005");
SYSTEMTIME st { 0 };
st.wYear = dt.tm_year + 1900; // dt is years from 1900
st.wMonth = dt.tm_mon + 1; // dt is month index 0
st.wDay = dt.tm_mday;
st.wHour = 6; // FILETIME is based on UTC, which is 6 hours ahead

Afterwards I convert the SYSTEMTIME to a FILETIME and use that to apply the changes.

This sets the file time to 2 April 2005 12:00:00 AM which is correct. However, videos after April 2nd were being set to 1:00:00 AM and sure enough, daylight savings happened on April 3rd 2005.

How can I determine if a certain date is before or after daylight savings so I can adjust st.wHour accordingly? The goal is to have all times set to 12:00:00 AM. Preferably this would work on dates dating back to the 60s as well as current.

I tried using TIME_ZONE_INFORMATION and GetTimeZoneInformation but I only get back TIME_ZONE_ID_STANDARD.

1
What function are you using to convert SYSTEMTIME to FILETIME? The docs I'm reading say that SYSTEMTIME can represent UTC or local time, depending on the function being called.Howard Hinnant
I'm using SystemTimeToFileTime.anon
The docs say SystemTimeToFileTime assumes the SYSTEMTIME is already in UTC. Therefore I don't understand how st.wHour = 6 is mapping to midnight UTC.Howard Hinnant
Then perhaps the issue is downstream? I'm using SetFileTime and passing the FILETIME{3921264640, 29701910} which yields 6 hours prior on my machine.anon
I think the OS will show you local time both before and after 2007. And I also suspect that it will use the 2007 (and forward) time zone rules for pre-2007 dates, but I could be wrong about that aspect. If I'm right, dates prior to 2007 between 2nd Sunday in March and the first Sunday in April will likely be off by 1h.Howard Hinnant

1 Answers

2
votes

A few things:

  • SYSTEMTIME is just a plain structure. It has separate fields for year, month, day-of-week, day (of month), hour, minute, second and millisecond. It is not either UTC or local time, or anything else by itself. That doesn't come into consideration until you pass it into a function.

  • FILETIME is another plain structure. It represents the number of 100-nanosecond intervals since midnight of 1601-01-01. Much of the docs would make you think it is always in UTC, but there are functions like FileTimeToLocalFileTime that disprove that. Thus, like SYSTEMTIME, it is up to each individual function to decide how to interpret it.

  • The SystemTimeToFileTime function accepts a pointer to a SYSTEMTIME that is interpreted as UTC, and returns a pointer to a FILETIME that is also in terms of UTC. There is no local time zone involved.

  • Don't try to adjust for local time yourself (st.wHour = 6 in your code). The hour will need to vary depending both on time zone and on DST.

  • You didn't see any response from GetTimeZoneInformation other than TIME_ZONE_STANDARD, because that tells you what is currently in effect - which is disconnected from the dates you might be working with.

  • You shouldn't be trying to figure out how to adjust for DST on your own. DST is not universally applicable, and it's not always one hour offset. Instead, use a function that converts from the time zone you care about to UTC.

Ultimately it sounds like you are asking about how to set the file time to midnight of the local time zone on a particular date. Thus, I suggest you go through these steps:

  • Construct a SYSTEMTIME with the date you care about and the time components set to zeros (the default).

  • Get the local time zone using the GetDynamicTimeZoneInformation function. You want the "dynamic" version such that any historical differences in rules for standard time and DST that Windows knows about are taken into consideration, rather than just the current set of rules.

  • Pass those two values to the TzSpecificLocalTimeToSystemTimeEx function. It will interpret the input time as being in the input time zone (which was the system's local time zone). The result is a FILETIME that is in terms of UTC.

  • Pass that value to SetFileTime, which expects the input in terms of UTC.

Also, keep in mind that not all file systems track file times in the same way:

  • NTFS stores the actual UTC time, so you can move files between computers and the timestamp represents the same point in Universal Time even if the computers have different time zone settings.

  • FAT and its variants store local time. So when you call SetFileTime, Windows translates from UTC to the local time zone and writes the result. If you then open the file on a system with a different time zone, the date will be interpreted in that time zone, resulting in a different UTC time. (This is often seen on USB sticks, memory cards, etc. when moving files from cameras to computers.)

Lastly, you said:

... Preferably this would work on dates dating back to the 60s as well as current.

Unfortunately, Windows time zones do not track historical dates that far. Microsoft's time zone policy is to track time zone and DST rules for 2010 and forward for all populated places on Earth. Although, several time zones track some historical changes from before 2010 as artifacts from before this policy was formalized. (They are accurate within a given zone, just not uniform in starting years across all zones).

If historical dates are significant to your application, you'll need a very different approach - one that doesn't use the Windows time zone data, but instead uses the IANA time zone database. (More on these in the timezone tag wiki.) Here are some ideas you could explore:

  • The ICU project has time zone support and a C implementation. It's a bit heavy for this one purpose, but good if you are using it for other localization aspects of an application.

  • Howard Hinnant (who commented on the question above) has an excellent Date library with IANA time zone support.

  • It might be possible to get what you need from the Windows.Globalization.Calendar UWP class. I haven't tested to see if it will use historical rules from IANA data or if it uses the Windows data. (If I get a chance to check, I'll come back and update this answer.)

Keep in mind that the IANA database only guarantees from 1970 forward. You said you needed from 1960, and though there are some zones with data for that (and some much older), there is no guarantee of correctness in that period.