I have a need to store the Last Write Access time of a file on Windows as a string. Need to avoid any Daylight Savings Time or users in a different time zone issues. I think I have a solution, but it seems there are many issues with dates.
I don't have any need to compare a date to previously stored date. Only need to know if it changed.
Storing the raw TFileTime record (represented as an Int64) seemed best as this is what is actually used by Windows as 2 DWORDS. Delphi seems to want to use TDateTime (FileAge) or an integer (FileSetDate). Both of these seem to translate to local times and only use 32 bits vs. 64 bits.
I do have a need to display a "user friendly" string and did a UTC display string to double check stored values. For these, I did use a TDateTime to translate out of the TFileTime format.
The helper unit looks like this:
unit FileTimeHelperUnt;
interface
uses
Winapi.Windows, System.SysUtils;
type
TFileTimeHelper = record helper for TFileTime
function ToString: String; //Use to export TFileTime as Int64 String.
function FromString( AString: String ): Boolean; //Use to restore
TFileTime from Int64 String
function GetLastWriteTime( AFilePathStr: String ): Boolean;
function SetLastWriteTime( AFilePathStr: String ): Boolean;
function UTCString: String;
function UserFriendlyString: String; //Like Windows Explorer and Local.
end;
implementation
{ TFileTimeHelper }
function TFileTimeHelper.ToString: String;
var
TmpInt64: Int64 absolute Self;
begin
Result := TmpInt64.ToString;
end;
function TFileTimeHelper.FromString(AString: String): Boolean;
begin
Result := False;
try
Int64(Self) := StrToInt64( AString );
Result := True;
except on E: Exception do
end;
end;
function TFileTimeHelper.GetLastWriteTime(AFilePathStr: String): Boolean;
var
TmpSearchRec: TSearchRec;
begin
Result := False;
if FileExists( AFilePathStr )=False then
Exit;
if FindFirst( AFilePathStr, faAnyFile, TmpSearchRec )=0 then
begin
Self := TmpSearchRec.FindData.ftLastWriteTime;
Result := True;
end;
end;
function TFileTimeHelper.SetLastWriteTime(AFilePathStr: String): Boolean;
var
TmpHandle: THandle;
begin
Result := False;
if FileExists( AFilePathStr )=False then
Exit;
try
TmpHandle := FileOpen(AFilePathStr, fmOpenWrite);
if TmpHandle = THandle(-1) then
Exit;
try
SetFileTime(TmpHandle, nil, nil, @Self);
Result := (GetLastError=0);
finally
FileClose( TmpHandle );
end;
except on E: Exception do
end;
end;
function TFileTimeHelper.UTCString: String;
var
TmpSystemTime: TSystemTime;
TmpDateTime: TDateTime;
begin
FileTimeToSystemTime( Self, TmpSystemTime );
TmpDateTime := SystemTimeToDateTime(TmpSystemTime);
Result := DateTimeToStr( TmpDateTime );
end;
function TFileTimeHelper.UserFriendlyString: String;
var
TmpSystemTime: TSystemTime;
TmpLocalLastWriteFileTime: TFileTime;
TmpDateTime: TDateTime;
begin
FileTimeToLocalFileTime( Self, TmpLocalLastWriteFileTime );
FileTimeToSystemTime( TmpLocalLastWriteFileTime, TmpSystemTime );
TmpDateTime := SystemTimeToDateTime(TmpSystemTime);
Result := FormatDateTime( 'm/d/yyyy h:nn ampm', TmpDateTime );
end;
end.
The calling unit looks like this:
procedure TForm12.btnGetFileDate2Click(Sender: TObject);
var
TmpFileTime: TFileTime;
begin
TmpFileTime.GetLastWriteTime( 'File.txt' );
edtFileDateTime.Text := TmpFileTime.ToString;
edtLocalFileDateTime.Text := TmpFileTime.UserFriendlyString;
edtUTCDateTime.Text := TmpFileTime.UTCString;
end;
procedure TForm12.btnSetFileDate2Click(Sender: TObject);
var
TmpFileTime: TFileTime;
begin
TmpFileTime.FromString( edtFileDateTime.Text );
TmpFileTime.SetLastWriteTime( 'File.txt' );
end;
Everything seems to work well. I'm not worried at this point about TFileTime being changed from 64-bits. Hoping I didn't miss any scenarios which could cause problems.
Also, hopefully someone else may find this useful if there aren't many problems.
The question is: is this code going to run into any time zone or daylight savings time issues? I think this code should avoid a "save now and load after daylight savings time changes" problem. Or a "Save in my timezone and then gets loaded by someone else in another time zone" problem. The TFileTime structure should stay the same and my program will recognize it didn't change. Not certain I've got all of the potential problems listed. Basically, is there any case where storing the string and loading later or in a different place will make my program think there is a change?
Thanks.
ftLastWriteTime
value provided byFindFirst()
is already in UTC. – Remy LebeauftLastWriteTime
is already in UTC was very helpful. I suspected it, but couldn't find any documentation explicitly stating it. On the ISO-8601 standard, I have no idea how to convert to that. I am comfortable with a nonstandard format mainly because it translates in and out of the TFileTime structure and produces the samedwLowDateTime
anddwHighDateTime
. – DelphiGuy