Jump to content

Problems and solutions with FileSetTime, FileGetTime and DST (Daylight Saving Time) enabled in Windows 7+.


hifi55
 Share

Recommended Posts

Enabling DST (the summertime and wintertime schedules across the globe) within your time zone might cause an unexpected shift in datetimes by 1 hour when you're trying to set filetimes with FileSetTime or read them with FileGetTime. This behaviour frequently causes bugreports like #3139 at https://www.autoitscript.com/trac/autoit/ticket/3139 'FileGetTime out by exactly one hour.', where it was classified as 'No bug'.
Now this isn't considered a bug when you're referring to the datetime as shown by the command prompt (DOS prompt, even in Windows 7+) or Windows Explorer XP, as these programs use 'old style' DST behaviour. But it definitely can be considered a bug when you're using Windows Explorer 7+, which adopts the 'new style' behaviour.

'Old style' (currently implemented, v3.3.14.2 and previous) calculates datetimes as follows:

    FileSetTime = local time    - time zone - DST currently in effect
    FileGetTime = file datetime + time zone + DST currently in effect
    
whereas 'new style' calculation of datetimes would require:

    FileSetTime = local time    - time zone - DST in effect at the time of the file's datetime (Dynamic Time Zones)
    FileGetTime = file datetime + time zone + DST in effect at the time of the file's datetime (Dynamic Time Zones)

This 'new style' behaviour will leave your timestamps unaltered in Windows Explorer 7+ whenever a transition to summertime or wintertime occurs, but requires detailed records of all DST schedules worldwide. Fortunately all these records are readily available in Windows 7+! You can read more about this in Raymond Chen insider blogs at https://blogs.msdn.microsoft.com/oldnewthing/20130308-00/?p=5023 and https://technet.microsoft.com/en-us/magazine/ff394358.aspx

To have FileSetFile and FileGetFile adopt the 'new style' DST behaviour, I've written FileSetFileExt and FileGetFileExt.
As part of FileSetFileExt, which has an option to recurse into the given path, the function FindFiles was created. It's included separately and you can easily apply it as a stand-alone function to recurse a given path and process all files that match a specified pattern. I think it's a useful function that AutoIt unfortunately still lacks.

FileSetTimeExt (FileSetTime voor DST en Win7+):

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w- 4 -w 5 -w 6 -w- 7

#include <File.au3>
#include <Date.au3>
#include <WinAPIFiles.au3>
#include <WinAPI.au3>

ConsoleWrite("Set Modified datetime of this script to Jan 12th 2016 at 15:16:17" & @CRLF)
FileSetTimeExt(@ScriptFullPath, "20160123151617", 0, 0)

Func FileSetTimeExt($filename, $time, $type = 0, $recurse = 0)
    ; v1.0
    ; Extended FileSetTime function to properly deal with DST (Daylight Saving Time (summertime/wintertime)) on Windows 7+
    ; Usage is identical to the FileSetTime function with full wildcard support, including multiple * and ? usage.
    ;
    ; AutoIt Version: 3.3.14.2
    ; This function requires Windows 7 or later.
    ;
    ; Requirements to include in your main program:
    ; #include <File.au3>
    ; #include <Date.au3>
    ; #include <WinAPIFiles.au3>
    ; #include <WinAPI.au3>
    If $type = Default Then $type = 0
    If $recurse = Default Then $recurse = 0
    If $time = "" Then $time = StringRegExpReplace(_NowCalc(), "[^0-9]", "")
    Local $sDrive = "", $sDir = "", $sFileName = "", $sExtension = "", $ssDir = "", $sDummy1 = "", $sDummy2 = ""
    _PathSplit($filename, $sDrive, $sDir, $sFileName, $sExtension)
    ; extend path to full path if filename has no path or a path relative to ScriptDir
    If $sDrive = "" Then
        _PathSplit(@ScriptFullPath, $sDrive, $ssDir, $sDummy1, $sDummy2)
        $sDir = $ssDir & $sDir
        $filename = _PathMake($sDrive, $sDir, $sFileName, $sExtension)
    EndIf
    ; FileSetTime makes no difference between * and *.*, whereas_WinAPI_IsNameInExpression does, so make adjustments
    If $sExtension = ".*" Then
        $sFileName = $sFileName & "*"
        $sExtension = ""
    EndIf
    Local $hSearch
    $hSearch = FileFindFirstFile(_PathMake($sDrive, $sDir, "*", ".*")) ; searches for every file and directory
    If $hSearch = -1 Then Return 1
    Local $pattern, $eExt, $tSystem, $tFile, $hFile, $pFileName
    ; get correct local time
    $tSystem = _Date_Time_EncodeSystemTime(StringMid($time, 5, 2), StringMid($time, 7, 2), StringLeft($time, 4), _
            StringMid($time, 9, 2), StringMid($time, 11, 2), StringMid($time, 13, 2))
    $tSystem = _Date_Time_TzSpecificLocalTimeToSystemTime($tSystem)
    $tFile = _Date_Time_SystemTimeToFileTime($tSystem)
    $pattern = StringMid(_PathMake("", "", $sFileName, $sExtension), 2) ; StringMid skips leading \
    While 1
        $sFileName = FileFindNextFile($hSearch)
        If @error = 1 Then ; no more files or directories match the pattern
            ExitLoop
        Else
            $eExt = @extended
            $pFileName = _PathMake($sDrive, $sDir, $sFileName, "")
            ; _WinAPI_IsNameInExpression supports full wildcard matching, including multiple * and ? usage.
            If _WinAPI_IsNameInExpression($sFileName, $pattern) Then
                ; set correct local time for matching file or directory
                ; try the usual function first to catch errors beforehand
                If FileSetTime($pFileName, $time, $type) = 0 Then
                    FileClose($hSearch)
                    Return 0 ; abort on error
                EndIf
                ; use _WinAPI_CreateFileEx instead of _WinAPI_CreateFile to include directory renaming without an 'Access is denied' error
                $hFile = _WinAPI_CreateFileEx($pFileName, $OPEN_EXISTING, $GENERIC_WRITE, $FILE_SHARE_WRITE, $FILE_FLAG_BACKUP_SEMANTICS)
                Switch $type
                    Case 0
                        _Date_Time_SetFileTime($hFile, 0, 0, $tFile) ; set time Last modified (= Last written) (default)
                    Case 1
                        _Date_Time_SetFileTime($hFile, $tFile, 0, 0) ; set time Created
                    Case 2
                        _Date_Time_SetFileTime($hFile, 0, $tFile, 0) ; set time Last accessed
                EndSwitch
                _WinAPI_CloseHandle($hFile)
            EndIf
            ; perform a full recursive search if need be
            If $eExt = 1 And $recurse = 1 Then
                If FileSetTimeExt($pFileName & '\' & $pattern, $time, $type, $recurse) = 0 Then
                    FileClose($hSearch)
                    Return 0 ; abort on error
                EndIf
            EndIf
        EndIf
    WEnd
    FileClose($hSearch)
    Return 1 ; no error occurred
EndFunc   ;==>FileSetTimeExt

FileGetTimeExt (FileGetTime voor DST en Win7+):

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w- 4 -w 5 -w 6 -w- 7

#include <File.au3>
#include <Date.au3>
#include <WinAPIFiles.au3>
#include <WinAPI.au3>

ConsoleWrite("Modified datetime of this script is " & FileGetTimeExt(@ScriptFullPath, 0, 1) & @CRLF)
ConsoleWrite("Created datetime of this script is " & FileGetTimeExt(@ScriptFullPath, 1, 1) & @CRLF)
ConsoleWrite("Last accessed datetime of this script is " & FileGetTimeExt(@ScriptFullPath, 2, 1) & @CRLF)

Func FileGetTimeExt($filename, $option = 0, $format = 0)
    ; v1.0
    ; Extended FileGetTime function to properly deal with DST (Daylight Saving Time (summertime/wintertime)) on Windows 7+
    ; Usage is identical to the FileGetTime.
    ;
    ; AutoIt Version: 3.3.14.2
    ; This function requires Windows 7 or later.
    ;
    ; Requirements to include in your main program:
    ; #include <File.au3>
    ; #include <Date.au3>
    ; #include <WinAPIFiles.au3>
    ; #include <WinAPI.au3>
    If $option = Default Then $option = 0
    If $format = Default Then $format = 0
    Local $sDrive = "", $sDir = "", $sFileName = "", $sExtension = "", $ssDir = "", $sDummy1 = "", $sDummy2 = ""
    _PathSplit($filename, $sDrive, $sDir, $sFileName, $sExtension)
    ; extend path to full path if filename has no path or a path relative to ScriptDir
    If $sDrive = "" Then
        _PathSplit(@ScriptFullPath, $sDrive, $ssDir, $sDummy1, $sDummy2)
        $sDir = $ssDir & $sDir
        $filename = _PathMake($sDrive, $sDir, $sFileName, $sExtension)
    EndIf
    Local $aTime, $tDate, $tOut[6], $hFile, $tSystem, $tLocal, $tFile
    FileGetTime($filename, $option, $format) ; test if no error occurs
    If @error Then Return SetError(@error, 0, "")
    ; use _WinAPI_CreateFileEx instead of _WinAPI_CreateFile to include directory access without an 'Access is denied' error
    $hFile = _WinAPI_CreateFileEx($filename, $OPEN_EXISTING, $GENERIC_READ, $FILE_SHARE_READ, $FILE_FLAG_BACKUP_SEMANTICS)
    Switch $option ; convert FileGetTime option 0 to _Date_Time_GetFileTime option 2, 1 to 0 and 2 to 1
        Case 0
            $option = 2 ; Last modified (default)
        Case 1
            $option = 0 ; Created
        Case 2
            $option = 1 ; Last accessed
    EndSwitch
    $aTime = _Date_Time_GetFileTime($hFile)
    _WinAPI_CloseHandle($hFile)
    $aTime = $aTime[$option]
    $tSystem = _Date_Time_FileTimeToSystemTime($aTime)
    $tLocal = _Date_Time_SystemTimeToTzSpecificLocalTime($tSystem)
    $tFile = _Date_Time_SystemTimeToFileTime($tLocal)
    $tDate = _Date_Time_FileTimeToArray($tFile)
    $tOut[0] = StringFormat("%04i", $tDate[2]) ; Year
    $tOut[1] = StringFormat("%02i", $tDate[0]) ; Month
    $tOut[2] = StringFormat("%02i", $tDate[1]) ; Day
    $tOut[3] = StringFormat("%02i", $tDate[3]) ; Hour
    $tOut[4] = StringFormat("%02i", $tDate[4]) ; Minutes
    $tOut[5] = StringFormat("%02i", $tDate[5]) ; Seconds
    If $format = 0 Then Return SetError(0, 0, $tOut)
    Return SetError(0, 0, $tOut[0] & $tOut[1] & $tOut[2] & $tOut[3] & $tOut[4] & $tOut[5])
EndFunc   ;==>FileGetTimeExt

FindFiles (full recursive file search voor Win7+):

#AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w- 4 -w 5 -w 6 -w- 7

#include <File.au3>
#include <Date.au3>
#include <WinAPIShPath.au3>

; Find all files (with or without an extension) in ScriptDir with recursion
FindFiles("*", 1, False)

Func FindFiles($filename, $recurse = 0, $CaseSensitive = False);
    ; v1.0
    ; Find all files matching $filename with full wildcard support, including multiple * and ? usage.
    ; Set $recurse to 1 to include searching all subdirectories.
    ; Set $CaseSensitive to True to include case sensitive searching.
    ; Filenames without extension demand a $filename without an extension,
    ; e.g. abc* matches abc.txt and abc, whereas abc*.* matches abc.txt but not abc.
    ;
    ; Use Func FoundFileProcessing($filename) to process a single found file.
    ; FoundFileProcessing($filename) receives filenames with full path.
    ;
    ; AutoIt Version: 3.3.14.2
    ; This function requires Windows 7 or later.
    ;
    ; Requirements to include in your main program:
    ; #include <File.au3>
    ; #include <Date.au3>
    ; #include <WinAPIShPath.au3>
    If $recurse = Default Then $recurse = 0
    If $CaseSensitive = Default Then $CaseSensitive = False
    Local $sDrive = "", $sDir = "", $sFileName = "", $sExtension = "", $ssDir = "", $sDummy1 = "", $sDummy2 = ""
    _PathSplit($filename, $sDrive, $sDir, $sFileName, $sExtension)
    ; extend path to full path if filename has no path or a path relative to ScriptDir
    If $sDrive = "" Then
        _PathSplit(@ScriptFullPath, $sDrive, $ssDir, $sDummy1, $sDummy2)
        $sDir = $ssDir & $sDir
        $filename = _PathMake($sDrive, $sDir, $sFileName, $sExtension)
    EndIf
    Local $hSearch
    $hSearch = FileFindFirstFile(_PathMake($sDrive, $sDir, "*", ".*")) ; searches for every file and directory
    If $hSearch = -1 Then Return 1
    Local $pattern, $eExt, $pFileName
    $pattern = StringMid(_PathMake("", "", $sFileName, $sExtension), 2) ; StringMid skips leading \
    While 1
        $sFileName = FileFindNextFile($hSearch)
        If @error = 1 Then ; no more files or directories match the pattern
            ExitLoop
        Else
            $eExt = @extended
            $pFileName = _PathMake($sDrive, $sDir, $sFileName, "")
            ; _WinAPI_IsNameInExpression supports full wildcard matching, including multiple * and ? usage.
            If _WinAPI_IsNameInExpression($sFileName, $pattern, $CaseSensitive) Then FoundFileProcessing($pFileName)
            If $eExt = 1 And $recurse = 1 Then FindFiles($pFileName & '\' & $pattern, $recurse, $CaseSensitive)
        EndIf
    WEnd
    FileClose($hSearch)
    Return 1 ; no error
EndFunc   ;==>FindFiles

Func FoundFileProcessing($filename)
    ConsoleWrite('processing ' & $filename & @CRLF)
EndFunc   ;==>FoundFileProcessing

 

FileSetTimeEx (FileSetTime voor DST en Win7+).au3

FileGetTimeEx (FileGetTime voor DST en Win7+).au3

FindFiles (full recursive file search voor Win7+).au3

Edited by hifi55
Link to comment
Share on other sites

  • 4 years later...

Thanks man,

i've got the issue with FileSetTime on Win10 in UTC+1 with DST on:

if i use FileSetTime($sFileName, 20190203203030, $FT_MODIFIED)  what i got from windows explorer file properties is that the file date is set to 2019/02/03 19:30:30 instead of 20:30:30 as it should.

BUT

if i manually switch off from my windows control panel the daylight saving time (changing windows clock back 1 hour), [windows explorer file properties keeps showing 19:30:30]

and relunching FileSetTime($sFileName, 20190203203030, $FT_MODIFIED) [same as before] , this time i got from windows explorer file properties the file date set to 2019/02/03 20:30:30 as it should.

With your FileSetTimeExt function, even if i'm on DST (DST on) the FileSetTimeExt($sFileName, 20190203203030, $FT_MODIFIED) set the proper file time !!!

 

THANKS !!!!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...