Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Elapsed/time interval in Delphi?

I need to calculate the elapsed time (nicely formatted) between now and a file's last modification date/time, ie. something like this, only in my case, the difference can be in days, months or even years.

I tried this:

var
   TimeDiff : Double;
begin
     TimeDiff := Now - FileAgeEx('C:\my-file.txt');
     if (TimeDiff >= 1) then
        Caption  := FormatDateTime('dd hh:nn:ss', TimeDiff)
     else
         Caption := FormatDateTime('hh:nn:ss', TimeDiff);
end;

But (1) it doesn't work and (2) I'd like a better formatting.

Ultimately my goal is to have something like this:

  • Time Diff < 1 day ==> display this: 12:00:01
  • Time Diff >= 1 day ==> display this: 25 days, 12:00:01
  • Time Diff >= 1 year ==> display this: 2 years, 3 months, 10 days, 12:00:01

Anyone knows how can I do that?

Thanks!

like image 372
TheDude Avatar asked Dec 27 '22 01:12

TheDude


1 Answers

The main problem would appear to be getting hold of the last modified time of the file. I use the following code:

function LastWriteTime(const FileName: string): TFileTime;
var
  AttributeData: TWin32FileAttributeData;
begin
  if not GetFileAttributesEx(PChar(FileName), GetFileExInfoStandard, @AttributeData) then
    RaiseLastOSError;
  Result := AttributeData.ftLastWriteTime;
end;

function UTCFileTimeToSystemTime(const FileTime: TFileTime): TSystemTime;
//returns equivalent time in current locality, taking account of daylight saving
var
  LocalFileTime: Windows.TFileTime;
begin
  Windows.FileTimeToLocalFileTime(FileTime, LocalFileTime);
  Windows.FileTimeToSystemTime(LocalFileTime, Result);
end;

function UTCFileTimeToDateTime(const FileTime: TFileTime): TDateTime;
begin
  Result := SystemTimeToDateTime(UTCFileTimeToSystemTime(FileTime));
end;

You call LastWriteTime to get the last modified time in file time format. Then call UTCFileTimeToDateTime to convert into TDateTime accounting for the prevailing local time zone of the machine. You can then compare that value with Now.

As regards the formatting, you already appear to know how to do that. You basic approach will work and you just need to flesh out the details.


In the comments you say that

FormatDateTime('dd hh:nn:ss', 2.9);

shows a 1 for the day when you would expect a 2. The problem is that this function formats dates rather than time intervals. The value 2.9 is not treated as an elapsed time, rather it is treated as an absolute date/time, 2.9 days after the Delphi epoch. I would use Trunc and Frac to obtain number of days, and the part of days respectively, and work from there.

Days := Trunc(TimeDiff);
Time := Frac(TimeDiff);

The following code, extracted directly from my codebase, may give you some pointers. Note that its input is in seconds, but it should set you on the right path.

function CorrectPlural(const s: string; Count: Integer): string;
begin
  Result := IntToStr(Count) + ' ' + s;
  if Count<>1 then begin
    Result := Result + 's';
  end;
end;

function HumanReadableTime(Time: Double): string;
//Time is in seconds
const
  SecondsPerMinute = 60;
  SecondsPerHour = 60*SecondsPerMinute;
  SecondsPerDay = 24*SecondsPerHour;
  SecondsPerWeek = 7*SecondsPerDay;
  SecondsPerYear = 365*SecondsPerDay;

var
  Years, Weeks, Days, Hours, Minutes, Seconds: Int64;

begin
  Try
    Years := Trunc(Time/SecondsPerYear);
    Time := Time - Years*SecondsPerYear;
    Weeks := Trunc(Time/SecondsPerWeek);
    Time := Time - Weeks*SecondsPerWeek;
    Days := Trunc(Time/SecondsPerDay);
    Time := Time - Days*SecondsPerDay;
    Hours := Trunc(Time/SecondsPerHour);
    Time := Time - Hours*SecondsPerHour;
    Minutes := Trunc(Time/SecondsPerMinute);
    Time := Time - Minutes*SecondsPerMinute;
    Seconds := Trunc(Time);

    if Years>5000 then begin
      Result := IntToStr(Round(Years/1000))+' millennia';
    end else if Years>500 then begin
      Result := IntToStr(Round(Years/100))+' centuries';
    end else if Years>0 then begin
      Result := CorrectPlural('year', Years) + ' ' + CorrectPlural('week', Weeks);
    end else if Weeks>0 then begin
      Result := CorrectPlural('week', Weeks) + ' ' + CorrectPlural('day', Days);
    end else if Days>0 then begin
      Result := CorrectPlural('day', Days) + ' ' + CorrectPlural('hour', Hours);
    end else if Hours>0 then begin
      Result := CorrectPlural('hour', Hours) + ' ' + CorrectPlural('minute', Minutes);
    end else if Minutes>0 then begin
      Result := CorrectPlural('minute', Minutes);
    end else begin
      Result := CorrectPlural('second', Seconds);
    end;
  Except
    Result := 'an eternity';
  End;
end;
like image 157
David Heffernan Avatar answered Jan 11 '23 03:01

David Heffernan