Recent

Author Topic: FileAge: milliseconds is always 000 [SOLVED by rvk]  (Read 15676 times)

rvk

  • Hero Member
  • *****
  • Posts: 6922
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #15 on: October 08, 2016, 06:08:36 pm »
Mmmm, yes, the problem could also be in FileAge and especially WinToDosTime. The FileAge uses WinToDosTime to get the time in a Longint (32bit) while ftLastWriteTime is two DWORDs (so 64bit). And according to the FileTimeToDosDateTime which WinToDosTime uses, only seconds are used (to put into that Longint).

So... don't use FileAge if you are interested in the milliseconds. Just read out ftLastWriteTime and do a direct translation to TDateTime with FileTimeToDateTime().

totya

  • Hero Member
  • *****
  • Posts: 722
Re: FileAge: milliseconds is always 000
« Reply #16 on: October 08, 2016, 06:32:58 pm »
Rounded up... hmz, that's even stranger. Did you use FileDateToDateTime for that or another function ?

For example:

Code: Pascal  [Select][+][-]
  1. // rvk Master code, see: http://forum.lazarus.freepascal.org/index.php/topic,34329.msg224718.html#msg224718
  2. function FileTimeToDateTime(const FileTime: TFileTime): TDateTime;
  3. const
  4.   FileTimeBase      = -109205.0;
  5.   FileTimeStep: Extended = 24.0 * 60.0 * 60.0 * 1000.0 * 1000.0 * 10.0; // 100 nSek per Day
  6. begin
  7.   Result := Int64(FileTime) / FileTimeStep;
  8.   Result := Result + FileTimeBase;
  9. end;
  10.  
  11. procedure TForm1.FormCreate(Sender: TObject);
  12. const
  13.   SampleFile= 'sample.txt';
  14. var
  15.   FileDateTime: TDateTime;
  16.   F: TSearchRec;
  17.   Local: TFileTime;
  18. begin
  19.   Memo1.Clear;
  20.   Memo1.Lines.Add(SampleFile);
  21.   Memo1.Lines.Add(FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', FileDateToDateTime(FileAge(SampleFile))));
  22.  
  23.   // rvk Master code, see: http://forum.lazarus.freepascal.org/index.php/topic,34329.msg224718.html#msg224718
  24.   if SysUtils.FindFirst(SampleFile, faAnyFile and not faDirectory, F) = 0 then
  25.   try
  26.     FileTimeToLocalFileTime(F.FindData.ftCreationTime, Local);
  27.     memo1.lines.add('Created: ' + FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', FileTimeToDateTime(Local)));
  28.     FileTimeToLocalFileTime(F.FindData.ftLastAccessTime, Local);
  29.     memo1.lines.add('Last access: ' + FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', FileTimeToDateTime(Local)));
  30.     FileTimeToLocalFileTime(F.FindData.ftLastWriteTime, Local);
  31.     memo1.lines.add('Last write: ' + FormatDateTime('dd.mm.yyyy hh:nn:ss.zzz', FileTimeToDateTime(Local)));
  32.   finally
  33.     SysUtils.FindClose(F);
  34.   end;
  35. end;

The result:

Quote
sample.txt
08.10.2016 18:25:10.000
Created: 08.10.2016 18:17:39.538
Last access: 08.10.2016 18:25:05.468
Last write: 08.10.2016 18:25:08.916

One second difference(!)
Total Commader says: 18:25:08

and we are talking about milisec?  :o
« Last Edit: October 08, 2016, 06:47:03 pm by totya »

totya

  • Hero Member
  • *****
  • Posts: 722
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #17 on: October 08, 2016, 08:11:54 pm »
Okay, final code for example:

Code: Pascal  [Select][+][-]
  1. uses Windows;

Code: Pascal  [Select][+][-]
  1. // see: http://forum.lazarus.freepascal.org/index.php/topic,34329.msg224718.html#msg224718
  2. function FileTimeToDateTime(const FileTime: TFileTime): TDateTime;
  3. const
  4.   FileTimeBase = -109205.0;
  5.   FileTimeStep: Extended = 24.0 * 60.0 * 60.0 * 1000.0 * 1000.0 * 10.0; // 100 nSek per Day
  6. begin
  7.   Result := Int64(FileTime) / FileTimeStep;
  8.   Result := Result + FileTimeBase;
  9. end;
  10.  
  11. // see: http://forum.lazarus.freepascal.org/index.php/topic,34329.msg224718.html#msg224718
  12. function WindowsFileAge (const FileName: string): TDateTime;
  13. var
  14.   SearchRec: TSearchRec;
  15.   LocalFileTime: TFileTime;
  16. begin
  17.   if SysUtils.FindFirst(FileName, faAnyFile and not faDirectory, SearchRec) = 0
  18.   then
  19.     try
  20.       FileTimeToLocalFileTime(SearchRec.FindData.ftLastWriteTime, LocalFileTime);
  21.       Result:= FileTimeToDateTime(LocalFileTime);
  22.     finally
  23.       SysUtils.FindClose(SearchRec);
  24.     end
  25.   else raise EInOutError.Create('This file: "'+FileName+'" doesn''t exist');
  26. end;

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #18 on: October 08, 2016, 08:50:40 pm »
Nice totya !  :)

I'm personally more in favourite of copying and change its roots:
Code: Pascal  [Select][+][-]
  1. Function FileAgeDT (Const FileName : UnicodeString): TDateTime;
  2. var
  3.   Handle: THandle;
  4.   FindData: TWin32FindDataW;
  5.   lft: TFileTime;
  6.   sft: Windows.SYSTEMTIME;
  7. begin
  8.   Handle := FindFirstFileW(Pwidechar(FileName), FindData);
  9.   if Handle <> INVALID_HANDLE_VALUE then
  10.     begin
  11.       Windows.FindClose(Handle);
  12.       if (FindData.dwFileAttributes and FILE_ATTRIBUTE_DIRECTORY) = 0 then
  13.         if FileTimeToLocalFileTime(FindData.ftLastWriteTime, lft) then
  14.           if FileTimeToSystemTime(lft, sft) then
  15.             exit(EncodeDateTime(sft.Year, sft.Month, sft.Day, sft.Hour, sft.Minute, sft.Second, sft.MilliSecond));
  16.     end;
  17.   Result := -1.0;
  18. end;
  19.  
  20.  
  21. function FileAgeDT(Const FileName : RawByteString): TDateTime;
  22. begin
  23.   Result:= FileAgeDT(UnicodeString(FileName));
  24. end;
  25.  

Which shows how awkward the result value is, which on its turn explains the other implementation of FileAge as mentioned by marcov (except for that one doing it inaccurate also).
« Last Edit: October 08, 2016, 09:02:54 pm by molly »

rvk

  • Hero Member
  • *****
  • Posts: 6922
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #19 on: October 10, 2016, 02:23:22 pm »
Delphi uses the same FileTimeToDosDateTime in FileAge().
So the current version of FPC is compatible with Delphi (and strips the milliseconds).

The above version of molly is indeed the most elegant way to retrieve the filedatetime with milliseconds. It is closest to the original FileAge() source, only it doesn't call FileTimeToDosDateTime() which strips those milliseconds.

molly

  • Hero Member
  • *****
  • Posts: 2330
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #20 on: October 10, 2016, 03:03:14 pm »
Ah yes. I forgot about this already  :-[

I wanted to apologize.

I figured out what is causing all this (e.g. why it cannot be changed even if we wanted to) , which is the findfirst/findnext legacy. The version that marcov referred to shows it better as the platform specific addition field in the findrecord is specifically _not_ retrieved there.

The other version in the windows implementation does get the information from the extra (platform specific) record field. I guess the new implementation causes/forces all platforms to line up again to 'old' dos standards.

Browsing through the code of the rtl can be very interesting and rewarding sometimes  :D

Thank you rvk and others.

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1570
    • Lebeau Software
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #21 on: October 11, 2016, 08:09:13 pm »
Delphi uses the same FileTimeToDosDateTime in FileAge().

Only in the deprecated version of FileAge().  It was replaced over a decade ago with a newer overloaded version that outputs a full TDateTime without truncating off the milliseconds.

Code: [Select]
{ FileAge retrieves the date-and-time stamp of the specified file as a
  TDateTime. This version supports all valid NTFS date-and-time stamps
  and returns a boolean value that indicates whether the specified file
  exists. If the specified file is a symlink the function is performed on
  the target file. If FollowLink is false then the date-and-time of the
  symlink file is returned. }

function FileAge(const FileName: string; out FileDateTime: TDateTime;
  FollowLink: Boolean = True): Boolean; overload;
« Last Edit: October 11, 2016, 08:10:56 pm by Remy Lebeau »
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

rvk

  • Hero Member
  • *****
  • Posts: 6922
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #22 on: October 12, 2016, 11:16:29 am »
Delphi uses the same FileTimeToDosDateTime in FileAge().
Only in the deprecated version of FileAge().  It was replaced over a decade ago with a newer overloaded version that outputs a full TDateTime without truncating off the milliseconds.
Argh, You are correct. The FileAge(Filename) with one parameter is deprecated. FileAge(FileName, DateTime): Boolean is the new one. And that returns the correct milliseconds in Delphi. But in FPC that overloaded version uses the FileTimeToDateTime internally again, which strips the milliseconds. Delphi uses TrySystemTimeToDateTime() to convert the FileTime to DateTime (which does support milliseconds).

That would be easily fixed in FPC to use SystemTimeToDateTime() too but the next problem is that FPC uses the old FindFirst() to get the time information (which isn't accurate to the millisecond).

And to top it off.... internally that same FindFirst in FPC uses the the more accurate FindFirstFileW it also uses in the FileAge(Filename) but it converts the ftLastWriteTime back to WinToDosTime() for the time in the SearchRec.

Yeah... this is really a mess :)

So Delphi:
FileAge(Filename) -> uses GetFileAttributesEx() to get ftLastWriteTime but after that FileTimeToDosDateTime() which cuts ms
FileAge(Filename, TDateTime) -> uses GetFileAttributesEx() to get ftLastWriteTime which is converted with TrySystemTimeToDateTime() which does support ms

And FPC:
FileAge(Filename) -> uses FindFirstFileW() to get ftLastWriteTime but uses WinToDosTime() which cuts milliseconds (no problem, compatibility)
FileAge(Filename, TDateTime) -> uses FindFirst -> which uses FindFirstFileW() to get ftLastWriteTime but ms is cut in FindFirst by WinToDosTime() (incompatible)

(note ftLastWriteTime supports milliseconds)

So in FPC the FileAge(Filename, TDateTime) should be
FileAge(Filename, TDateTime) -> uses FindFirstFileW() to get ftLastWriteTime which is converted with SystemTimeToDateTime() which does support ms
(so with FindFirstFileW() and not via FindFirst() which cuts the ms)

Remy Lebeau

  • Hero Member
  • *****
  • Posts: 1570
    • Lebeau Software
Re: FileAge: milliseconds is always 000 [SOLVED by rvk]
« Reply #23 on: October 12, 2016, 07:49:57 pm »
That would be easily fixed in FPC to use SystemTimeToDateTime() too but the next problem is that FPC uses the old FindFirst() to get the time information (which isn't accurate to the millisecond).

Delphi uses GetFileAttributesEx(), and if that fails with a lock/sharing violation then it uses FindFirstFile() (which FindFirst() wraps).  They are both limited to file system resolutions, though:

Quote
Not all file systems can record creation and last access time, and not all file systems record them in the same manner. For example, on the FAT file system, create time has a resolution of 10 milliseconds, write time has a resolution of 2 seconds, and access time has a resolution of 1 day. On the NTFS file system, access time has a resolution of 1 hour. For more information, see File Times.

FileAge() uses the write time.

And to top it off.... internally that same FindFirst in FPC uses the the more accurate FindFirstFileW it also uses in the FileAge(Filename) but it converts the ftLastWriteTime back to WinToDosTime() for the time in the SearchRec.

So does Delphi.  But only for the TSearchRec.Time field, which is marked as deprecated on Windows.  The original time data is available in the TSearchRec.FindData field.

Also, in modern Delphi versions, TSearchRec has a TimeStamp property that returns a TDateTime converted from the TSearchRec.FindData field instead of the TSearchRec.Time field.  So milliseconds are preserved.
Remy Lebeau
Lebeau Software - Owner, Developer
Internet Direct (Indy) - Admin, Developer (Support forum)

 

TinyPortal © 2005-2018