Delphi以字符串形式存储和检索TFileTime,没有夏令时,时区问题

时间:2018-08-30 16:53:12

标签: datetime delphi winapi

我需要将Windows上文件的上次写访问时间存储为字符串。需要避免任何夏令时或用户在不同时区的问题。我认为我有一个解决方案,但是日期似乎有很多问题。

我不需要将日期与以前存储的日期进行比较。只需要知道它是否改变。

存储原始TFileTime记录(表示为Int64)似乎是最好的,因为这实际上是Windows实际使用的2个DWORDS。 Delphi似乎要使用TDateTime(FileAge)或整数(FileSetDate)。这两个似乎都转换为本地时间,并且仅使用32位和64位。

我确实需要显示“用户友好”字符串,并做了UTC显示字符串来仔细检查存储的值。对于这些,我确实使用了TDateTime来转换TFileTime格式的 out

帮助器单元如下:

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.

呼叫方如下:

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;

一切似乎都正常。我现在不担心TFileTime从64位更改。希望我不会错过任何可能引起问题的情况。

此外,希望如果问题不多,其他人可能会觉得有用。

问题是:该代码是否会遇到任何时区或夏令时问题?我认为这段代码应避免出现“现在保存并在夏令时更改后加载”的问题。或出现“保存在我的时区,然后在另一个时区被其他人加载”的问题。 TFileTime结构应保持不变,并且我的程序将识别出它没有变化。不确定我是否列出了所有潜在问题。基本上,在任何情况下存储字符串并在以后或在其他位置加载都会使我的程序认为有更改吗?

谢谢。

1 个答案:

答案 0 :(得分:0)

无论出于何种原因,我最初的研究都没有发表这篇文章:MS FileTime Structure

2件事:

  1. 对于使用单个64位整数也太不客气,由于字节对齐问题,特别建议不要使用它。从来没有问题,但由于建议而仍然更改。
  2. 未考虑NTFS和FAT文件时间的差异。我现在只检查文件时间,直到符合我的目的为止。

为了完整起见,这是最终的生产代码:

unit FileTimeHelperUnt;

interface

uses
  Winapi.Windows, System.SysUtils;

type
  TFileTimeCompare = ( ftNewer, ftOlder, ftEqual );

  TFileTimeHelper = record helper for TFileTime
    function ToString: String;
    function FromString( AString: String ): Boolean;
    function GetLastWriteTime( AFilePathStr: String ): Boolean;
    function SetLastWriteTime( AFilePathStr: String ): Boolean;
    function Compare( AFileTime: TFileTime ): TFileTimeCompare;
    function UTCString: String;
    function UserFriendlyString: String; //Like Windows Explorer and Local.
  end;

implementation

{ TFileTimeHelper }
function TFileTimeHelper.ToString: String;
begin
  Result := IntToStr( Integer(Self.dwLowDateTime) ) + ',' + IntToStr( Integer(Self.dwHighDateTime) );
end;

function TFileTimeHelper.FromString( AString: String ): Boolean;
var
  TmpLowDateTimeStr: String;
  TmpHighDateTimeStr: String;
  TmpPos: Integer;
begin
  Result := False;
  try
    if AString.IsEmpty then
    begin
      Exit;
    end;

    TmpPos := Pos( ',', AString );

    if TmpPos=0 then
    begin
      Exit;
    end;

    TmpLowDateTimeStr := Copy( AString, 1, Pos( ',', AString )-1 );
    TmpHighDateTimeStr := Copy( AString, Pos( ',', AString )+1, MaxInt );

    Self.dwLowDateTime := DWORD( StrToInt( TmpLowDateTimeStr ) );
    Self.dwHighDateTime := DWORD( StrToInt( TmpHighDateTimeStr ) );

    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
    try
      Self.dwLowDateTime := TmpSearchRec.FindData.ftLastWriteTime.dwLowDateTime;
      Self.dwHighDateTime := TmpSearchRec.FindData.ftLastWriteTime.dwHighDateTime;
      Result := True;
    finally
      FindClose( TmpSearchRec );
    end;
  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;

//Given the imprecision of certain file systems, only compare to the minute.
function TFileTimeHelper.Compare( AFileTime: TFileTime ): TFileTimeCompare;
var
  TmpSelfSystemTime: TSystemTime;
  TmpArgSystemTime: TSystemTime;

  function CompareWord( A, B: Word ): TFileTimeCompare;
  begin
    if A = B then
      Result := ftEqual
    else if A < B then
      Result := ftNewer
    else
      Result := ftOlder;
  end;
begin
  Result := ftEqual;

  FileTimeToSystemTime( Self, TmpSelfSystemTime );
  FileTimeToSystemTime( AFileTime, TmpArgSystemTime );

  //Compare Year
  case CompareWord( TmpSelfSystemTime.wYear, TmpArgSystemTime.wYear ) of
    ftNewer: Exit( ftNewer );
    ftOlder: Exit( ftOlder );
  end;

  //Compare Month
  case CompareWord( TmpSelfSystemTime.wMonth, TmpArgSystemTime.wMonth ) of
    ftNewer: Exit( ftNewer );
    ftOlder: Exit( ftOlder );
  end;

  //Compare Day
  case CompareWord( TmpSelfSystemTime.wMonth, TmpArgSystemTime.wMonth ) of
    ftNewer: Exit( ftNewer );
    ftOlder: Exit( ftOlder );
  end;

  //Compare Hour
  case CompareWord( TmpSelfSystemTime.wHour, TmpArgSystemTime.wHour ) of
    ftNewer: Exit( ftNewer );
    ftOlder: Exit( ftOlder );
  end;

  //Compare Minute
  case CompareWord( TmpSelfSystemTime.wMinute, TmpArgSystemTime.wMinute ) of
    ftNewer: Exit( ftNewer );
    ftOlder: Exit( ftOlder );
  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
  try
    FileTimeToLocalFileTime( Self, TmpLocalLastWriteFileTime );
    FileTimeToSystemTime( TmpLocalLastWriteFileTime, TmpSystemTime );
    TmpDateTime := SystemTimeToDateTime(TmpSystemTime);
    Result := FormatDateTime( 'm/d/yyyy h:nn ampm', TmpDateTime );
  except on E: Exception do
    Result := 'Unknown.';
  end;
end;

end.

感谢您的帮助。我将其作为答案进行核对,主要是因为这是所使用的解决方案。