Click here to Skip to main content
15,896,606 members
Articles / Programming Languages / Objective C

Beating the Daylight Savings Time Bug and Getting Correct File Modification Times

Rate me:
Please Sign up or sign in to vote.
4.96/5 (28 votes)
28 May 200113 min read 492.2K   1.3K   51  
Windows reports erroneous file modification times, which change according to daylight savings. This article describes why this is so and how to determine correct file modification times and avoid the DST bug.
// test_tz.cpp : Defines the entry point for the console application.
//

//
// Original Author:  Jonathan M. Gilligan
//
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the 
// Free Software Foundation; either version 2 of the License, or (at your
// option) any later version.
// 
// This program is distributed in the hope that it will be useful, but 
// WITHOUT ANY WARRANTY; without even the implied warranty of 
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
// General Public License for more details.
// 
// You should have received a copy of the GNU General Public License along
// with this program; if not, write to the Free Software Foundation, Inc., 
// 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
// 
// Modification History:
// 18 May 2001, JMG -- First version.

#include "stdafx.h"
#include "JmgStat.h"

namespace jmg = Jonathan_M_Gilligan_95724E90_4A88_11d5_80F3_006008C7B14D;

static const SYSTEMTIME newtime = {
    1999,   // wYear
    11,     // wMonth
    2,      // wDayOfWeek
    30,     // wDay
    20,     // wHour
    45,     // wMinute
    49,     // wSecond
    0       // wMilliseconds
    };


// print a report on file modification times computed using
// stat and GetMTime functions.
void Report(LPCTSTR name)
{
	struct _stat s1;
    time_t tm_jmg;

	_tstat(name, &s1);
    jmg::GetUTCFileModTime(name, & tm_jmg);

    struct tm t1, t2, tl1, tl2;
    t1 = * gmtime(&s1.st_mtime);
    t2 = * gmtime(&tm_jmg);
    tl1 = * localtime(&s1.st_mtime);
    tl2 = * localtime(&tm_jmg);

    char tbuf1[256], tbuf2[256],
        tlbuf1[256], tlbuf2[256];
    strcpy(tbuf1, asctime(&t1));
    strcpy(tbuf2, asctime(&t2));
    strcpy(tlbuf1, asctime(&tl1));
    strcpy(tlbuf2, asctime(&tl2));

    _tprintf(_T("File mod times (UTC):\n  stat->\t%s  jmg->\t\t%s.\n"),
        tbuf1, tbuf2);
    _tprintf(_T("File mod times (local):\n  stat->\t%s  jmg->\t\t%s.\n"),
        tlbuf1, tlbuf2);
}

// Run "dir" to get the system's idea of the local file modification time...
void RunSystemDirCommand(LPCTSTR fname)
{
    FILE * dirpipe = NULL;
    _TCHAR dirbuf[128];

    _TCHAR pszCommand[1024];
    _sntprintf(pszCommand, sizeof(pszCommand), _T("dir \"%s\""), fname);
    dirpipe = _tpopen(pszCommand, _T("rt"));
    if (dirpipe) {
        while (!feof(dirpipe)) {
            if (_fgetts(dirbuf, sizeof(dirbuf), dirpipe)) {
                _tprintf(dirbuf);
                }
            }
        _pclose(dirpipe);
        }
}

// get string representation of the local and utc times.
void MakeTimes(LPTSTR local, LPTSTR utc, LPTSTR daylight)
{
    TIME_ZONE_INFORMATION zone;
    DWORD dwZoneStatus;
    _TCHAR * ptr;
    time_t systemTime;
    time(&systemTime);
    _tcscpy(local, _tasctime(localtime(&systemTime)));
    _tcscpy(utc, _tasctime(gmtime(&systemTime)));
    ptr = _tcschr(local,_T('\n'));
    if (ptr)
        *ptr = _T('\0');
    ptr = _tcschr(utc,_T('\n'));
    if (ptr)
        *ptr = _T('\0');
    dwZoneStatus = GetTimeZoneInformation(&zone);
    switch (dwZoneStatus) {
        case TIME_ZONE_ID_STANDARD:
            _tcscpy(daylight, _T("Standard"));
            break;
        case TIME_ZONE_ID_DAYLIGHT:
            _tcscpy(daylight, _T("Daylight"));
            break;
        case TIME_ZONE_ID_UNKNOWN:
        default:
            _tcscpy(daylight, _T("Unknown"));
            break;
        }
}

// Create scratch files which we will use to test file modification times.
void CreateScratch(LPCTSTR fname)
{
FILE *scratch = _tfopen(fname, _T("wt"));
if (scratch) {
    _ftprintf(scratch, _T("this is a test\n"));
    fclose(scratch);
    }
}

// Set the system Time Zone Information to Pacific time.
void SetPacificTime()
{
    LONG result(0);
    HKEY hTheKey(0);
    TIME_ZONE_INFORMATION zone;
    DWORD type(REG_BINARY);
    struct TZI {
        LONG Bias;
        LONG StandardBias;
        LONG DaylightBias;
        SYSTEMTIME StandardDate;
        SYSTEMTIME DaylightDate;
        } tzi;
    DWORD buflen(sizeof(tzi));
#if !defined(_UNICODE)
    char buf[32];
    DWORD xlen(0);
#endif

    result = RegOpenKeyEx(HKEY_LOCAL_MACHINE, 
        _T("Software\\Microsoft\\Windows NT\\CurrentVersion\\Time Zones\\Pacific Standard Time"), 
        0, KEY_READ, &hTheKey);
    if (result == ERROR_SUCCESS) {
        result = RegQueryValueEx(hTheKey, _T("TZI"), NULL, &type, 
                            reinterpret_cast<LPBYTE>(&tzi), &buflen );
        if (result == ERROR_SUCCESS) {
            zone.Bias = tzi.Bias;
            zone.DaylightBias = tzi.DaylightBias;
            zone.StandardBias = tzi.StandardBias;
            zone.DaylightDate = tzi.DaylightDate;
            zone.StandardDate = tzi.StandardDate;

            type = REG_SZ;
#if defined(_UNICODE)
            buflen = sizeof(zone.DaylightName);
            result = RegQueryValueEx(hTheKey, _T("Dlt"), NULL, &type, 
                reinterpret_cast<LPBYTE>(& zone.DaylightName), &buflen);
#else
            buflen = sizeof(buf);
            result = RegQueryValueEx(hTheKey, _T("Dlt"), NULL, &type, 
                reinterpret_cast<LPBYTE>(buf), &buflen);
            if (result == ERROR_SUCCESS) {
                xlen = MultiByteToWideChar(CP_ACP, 0, buf, -1, zone.DaylightName, 32);
                result = buflen != xlen ;
                }
#endif
            if (result != ERROR_SUCCESS) {
                wcscpy(zone.DaylightName, L"xxx");
                }
#if defined(_UNICODE)
            buflen = sizeof(zone.StandardName);
            result = RegQueryValueEx(hTheKey, _T("Std"), NULL, &type, 
                reinterpret_cast<LPBYTE>(zone.StandardName), &buflen);
#else
            buflen = sizeof(buf);
            result = RegQueryValueEx(hTheKey, _T("Std"), NULL, &type, 
                reinterpret_cast<LPBYTE>(buf), &buflen);
            if (result == ERROR_SUCCESS) {
                xlen = MultiByteToWideChar(CP_ACP, 0, buf, -1, zone.StandardName, 32);
                result = buflen != xlen;
                }
#endif
            if (result != ERROR_SUCCESS) {
                wcscpy(zone.DaylightName, L"yyy");
                }
            result = SetTimeZoneInformation(&zone);
            }
        }
    _tzset();
}

// Run file modification time tests on the four files referred to by fnames.
void RunTests(LPCTSTR * fnames)
{
    _TCHAR strLocal[256], strUTC[256], strDaylight[256];
    SYSTEMTIME oldtime;
    TIME_ZONE_INFORMATION zone;
    DWORD dwDaylightStatus;

    MakeTimes(strLocal, strUTC, strDaylight);    
    dwDaylightStatus = GetTimeZoneInformation(&zone);
    _tprintf(_T("\n======================================================================")
        _T("\nCurrent time:\n\t%s local (%s), %s UTC\n"), 
        strLocal, strDaylight, strUTC);
    for (int i = 0; i < 4; ++i) {
        RunSystemDirCommand(fnames[i]);
        Report(fnames[i]);
        }

    GetSystemTime(&oldtime);
    SetSystemTime(&newtime);
    MakeTimes(strLocal, strUTC, strDaylight);    
    _tprintf(_T("\n======================================================================")
        _T("\nCurrent time:\n\t%s local (%s), %s UTC\n"), 
        strLocal, strDaylight, strUTC);
    for (i = 0; i < 4; ++i) {
        RunSystemDirCommand(fnames[i]);
        Report(fnames[i]);
        }

    SetSystemTime(&oldtime);
}

// Create scratch files used for modification time testing.
void CreateFiles(LPCTSTR fname_ntfs_dst, LPCTSTR fname_fat_dst, 
                 LPCTSTR fname_ntfs_nodst, LPCTSTR fname_fat_nodst)
{
    SYSTEMTIME oldtime;

    CreateScratch(fname_ntfs_dst);
    CreateScratch(fname_fat_dst);

    GetSystemTime(&oldtime);
    SetSystemTime(&newtime);

    CreateScratch(fname_ntfs_nodst);
    CreateScratch(fname_fat_nodst);

    SetSystemTime(&oldtime);
}


// delete scratch files whose names are in fnames[0]..fnames[3]
void DeleteFiles(LPCTSTR *fnames)
{
    for (int i = 0; i < 4; ++i) {
        _tunlink(fnames[i]);
        }
}

// Test file modification times for the four files.
void Test(LPCTSTR *fnames)
{
	_tprintf(_T("\n**********************************************************************\n")
        _T("Local timezone information:\n\t%ld, %ld, %d, %s, %s\n"), 
		_timezone, _dstbias, _daylight,
		_tzname[0], _tzname[1]);
    RunTests(fnames);
}

void usage(void)
{
    _tprintf(_T("test_tz [-c[+|-]] [-d[+|-}] [-p[+|-]]\n")
        _T("  -c: create new test files on c:\\ and a:\\ to test NTFS and FAT systems.\n")
        _T("  -d: delete the test files when we're done.\n")
        _T("  -p: set system time zone to pacific time before doing the tests.\n")
        _T("  -h: print this help information\n"));
}

int _tmain(int argc, _TCHAR* argv[])
{
//	LPCTSTR fname = _T("C:/Program Files/Microsoft Visual Studio/vc98/crt/src/ctime.c");

	LPCTSTR fname_ntfs_dst = _T("C:\\ntfs_test_dst.txt");
	LPCTSTR fname_ntfs_nodst = _T("C:\\ntfs_test_no_dst.txt");
	LPCTSTR fname_fat_dst = _T("A:\\fat_test_dst.txt");
	LPCTSTR fname_fat_nodst = _T("A:\\fat_test_no_dst.txt");

    LPCTSTR fnames[] = {
        fname_ntfs_dst,
        fname_ntfs_nodst,
        fname_fat_dst,
        fname_fat_nodst
        };

    bool bCreate(true), bUsePacific(false), bDelete(true);
    int index = 0;

    assert(newtime.wYear == 1999);
    assert(newtime.wMonth == 11);
    assert(newtime.wDayOfWeek == 2);
    assert(newtime.wDay == 30);
    assert(newtime.wHour == 20);
    assert(newtime.wMinute == 45);
    assert(newtime.wSecond == 49);
    assert(newtime.wMilliseconds == 0);

    while(++index < argc) {
        LPCTSTR pArg = argv[index] + _tcsspn(argv[index], _T("-/"));
        bool flag;
        flag = true;
        switch (pArg[1]) {
            case '+':
                flag = true;
                break;
            case '-':
                flag = false;
                break;
            }

        switch (*pArg) {
            case _T('c'):
            case _T('C'):
                bCreate = flag;
                break;
            case _T('d'):
            case _T('D'):
                bDelete = flag;
                break;
            case _T('p'):
            case _T('P'):
                bUsePacific = flag;
                break;
            case _T('h'):
            case _T('H'):
                usage();
                return 0;
            default:
                _tprintf(_T("Illegal command line option '%c'.\n\n"), *pArg);
                usage();
                return 1;
            }
        }

    TIME_ZONE_INFORMATION zone;
    DWORD dwDSTStatus;

    dwDSTStatus = GetTimeZoneInformation(&zone);
    if (bUsePacific) {
        SetPacificTime();
        }
    _tzset();

    if (bCreate) {
        CreateFiles(fname_ntfs_dst, fname_fat_dst, 
            fname_ntfs_nodst, fname_fat_nodst);
        }
    Test(fnames);
    if (bDelete) {
        DeleteFiles(fnames);
        }

    if (bUsePacific) {
        SetTimeZoneInformation(&zone);
        }

    return 0;
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.


Written By
Web Developer
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions