DST—Maybe It’s Not Just Me?

Daylight Savings Time.

It seems that every time we pass in or out of this period, I have some kind of hassle. On previous occasions I would simply slap myself on the forehead, recompile and upload my website and move on, making a mental note to "watch out" for time zone issues in the future.

Not this time. This time I decided to find out why this keeps happening. And to my surprise, I think it may not be entirely my fault!

Stat!

Like a lot of software, my HTML editor checks file modification times to determine when pages need rebuilding. Unfortunately it seems that some incorrect documentation has led me to intepret the values returned by a certain low level system call incorrectly.

The documentation for the _stat(…) function implies that the returned st_mtime member contains a UTC time value, with the sample code using a ctime(…) call to convert it to a string representation, and ctime(…) is designed to take a UTC value as input.

From my own testing however [on an NTFS file system] it seems that st_mtime is pre-adjusted to the local timezone, meaning that passing it to ctime(…) will result in an incorrect time value, representing neither UTC or local time, because it will be performing the adjustment twice.

Returning such an adjusted value in st_mtime seems particularly inappropriate since the raw value will acually vary depending on the current time zone setting of the local machine as well as the Daylight Savings status! This seems to be the root cause of my files getting out of sync when DST changes occur.

I am now in the process of fixing my code so that I get UTC values instead of "adjusted" time when querying file properties… hopefully this will improve some of the issues I’ve been having.

9 Responses to “DST—Maybe It’s Not Just Me?”

  1. BC Says:

    Found this post via google because I had the same problem. _stat returns local time. ctime doesn’t appear to make any adjustments. Thus, this code will return a string with the correct local time:

    struct _stat FileInfoBuffer;
    int FileInfoResult = _tstat( “c:\\temp.txt”, &FileInfoBuffer );
    char* pLocaltimeString = ctime( &FileInfoBuffer.st_mtime );

    For the benefit of other websurfers looking for a solution, I offer this (Windows C++) code, which converts the _tstat()’s localtime to UTC and then outputs it:

    struct _stat FileInfoBuffer;
    int FileInfoResult = _tstat( “c:\\temp.txt”, &FileInfoBuffer );
    TIME_ZONE_INFORMATION TimeZoneInformation;
    GetTimeZoneInformation( &TimeZoneInformation );
    FileInfoBuffer.st_mtime += 60*( TimeZoneInformation.Bias + TimeZoneInformation.DaylightBias + TimeZoneInformation.StandardBias );
    char* pLocaltimeString = ctime( &FileInfoBuffer.st_mtime );

  2. mark Says:

    actually I think that all you need there is:
    FileInfoBuffer.st_mtime += 60 * TimeZoneInformation.Bias;

    see ms docs:
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/time_zone_information_str.asp

  3. BC Says:

    Well, yes, according to the MSDN documentation. If you turn Daylight Savings Time adjustments on and off, _stat() returns different values, but TimeZoneInformation.Bias doesn’t change (hence, TimeZoneInformation.Bias alone cannot get you back to UTC). You’ll note, however, that TimeZoneInformation.DaylightBias changes between 0 and -60 as Daylight Savings is turned on and off. Using both values in your calculation allows you to get back to UTC.

    To confirm this, open up your computer’s Date/Time properties and toggle between GMT-07:00 Mountain Time and GMT-07:00 Arizona. Arizona doesn’t use daylight savings time, so, if you do this test between Spring and Fall, you’ll see the difference. Or, toggle, “Automatically adjust clock for daylight savings changes”. The only way you can change time zone / daylight savings values and get FileInfoBuffer.st_mtime to remain constant is to use TimeZoneInformation.Bias AND TimeZoneInformation.DaylightBias in your calculation.

    ( Side note: I included TimeZoneInformation.StandardBias in my calculation. I’m unsure what this is for. In all my tests it was equal to 0, so it didn’t affect the calculation. Since TimeZoneInformation.DaylightBias was necessary, I thought maybe there might be a case where TimeZoneInformation.StandardBias might be necessary, too. Although I haven’t confirmed this. )

  4. BC Says:

    Correction, the correct algorithm is:

    TIME_ZONE_INFORMATION TimeZoneInformation;
    DWORD dwTimeState = GetTimeZoneInformation( &TimeZoneInformation );
    if( dwTimeState == TIME_ZONE_ID_STANDARD || dwTimeState == TIME_ZONE_ID_UNKNOWN )
    {
    FileInfoBuffer.st_mtime += 60*TimeZoneInformation.Bias;
    }
    else
    {
    FileInfoBuffer.st_mtime += 60*( TimeZoneInformation.Bias + TimeZoneInformation.DaylightBias + TimeZoneInformation.StandardBias );
    }

    The return value of GetTimeZoneInformation tells you whether the OS is currently inside Standard time or Daylight Savings time. TimeZoneInformation.DaylightBias should only be included in the calculation if you are currently inside Daylight Savings time. This algorithm causes FileInfoBuffer.st_mtime to remain constant across different time zones, inside and outside timezones that observe Daylight Savings time zones, and whether or not your timezone is currently observing daylight savings (i.e. between Spring and Fall).

  5. BC Says:

    The MSDN documentation revealed the use of StandardBias. It’s the additional time adjustment you make to Bias IF you are in standard time. Similarly, DaylightBias is the additional adjustment you make to Bias IF you are in Daylight Savings time.
    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/sysinfo/base/settimezoneinformation.asp

    The correct algorithm is as follows:

    TIME_ZONE_INFORMATION TimeZoneInformation;
    DWORD dwTimeState = GetTimeZoneInformation( &TimeZoneInformation );
    FileInfoBuffer.st_mtime += 60*TimeZoneInformation.Bias;
    if( dwTimeState == TIME_ZONE_ID_STANDARD )
    {
    FileInfoBuffer.st_mtime += 60*TimeZoneInformation.StandardBias;
    }
    else if( dwTimeState == TIME_ZONE_ID_DAYLIGHT )
    {
    FileInfoBuffer.st_mtime += 60*TimeZoneInformation.DaylightBias;
    }

    ( Hopefully my last post. )

  6. mark Says:

    I agree this time ;)

  7. Sebastian Says:

    This seems to happen only on NTFS, and only if “Automatically adjust clock for daylight saving changes” is selected in the Date/Time Properties dialog box for the system clock, see http://support.microsoft.com/default.aspx?scid=kb;en-us;190315

    How can crap like this be “behavior by design”?

  8. Sebastian Says:

    For a (seemingly) complete solution, refer to http://search.cpan.org/~shay/Win32-UTCFileTime-1.42/lib/Win32/UTCFileTime.pm

  9. mark Says:

    Thanks Sebastian, a look at that second article provides a lot of great info [and makes my head spin with how complicated such a simple problem can become]. When I get around to fixing the problem in my own software [soon soon] I will use it for reference.

Leave a Reply

You must be logged in to post a comment.