Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Date/Time manipulation - friendly countdown string

I'm building something which has a countdown to a certain date/time. I have it working - at least the Hours, Minutes, and Seconds work fine. My problem is when I try to implement Days, it does not give the correct result. I know about the DateUtils unit, but there's so much stuff there and I don't know how to do this, especially since I'm horrible at math.

I have a timer with interval at 100. Then I have a global fDestDT for the destination date/time to base the countdown off of. In the timer, I have a local TDateTime called DT. I then break it into multiple strings and put them back together into 1 'friendly' string...

procedure TForm1.TmrTimer(Sender: TObject);
var
  DT: TDateTime;
  D, H, N, S: String;
  Str: String;
begin
  DT:= fDestDT - Now; //fDest = destination date/time of countdown
  //Need to format only plural numbers with 's'
  D:= FormatDateTime('d', DT)+' Days';    //Get number of days
  H:= FormatDateTime('h', DT)+' Hours';   //Get number of hours
  N:= FormatDateTime('n', DT)+' Minutes'; //Get number of minutes
  S:= FormatDateTime('s', DT)+' Seconds'; //Get number of seconds
  Str:= D+', '+H+', '+N+', '+S;           //Build friendly string
  if lblTitle.Caption <> Str then
    lblTitle.Caption:= Str;               //Update caption only if it's changed
end;

It should come out something like...

0 Days, 3 Hours, 1 Minute, 12 Seconds

But instead the days are showing wrong, when the Date/Time of the countdown is on today's date, it is showing 30 Days...

30 Days, 3 Hours, 1 Minute, 12 Seconds

I presume that if I were to put it more than 1 month in advance, it would also not show correctly either. How do I get the number of days properly? And is there anything in the DateUtils unit that can automate most of this work better than I already am?

EDIT: FIXED! The problem was I was stupidly subtracting with DT:= fDestDT - Now; which was correct in my first code snippet, but after converting to use DateUtils.DaysBetween instead, I needed to remove that subtraction, and just set DT:= Now;.

Working code:

procedure TForm1.TmrTimer(Sender: TObject);
var           
  DT: TDateTime;
  Days, Hours, Mins, Secs: Word;
  SDays, SHours, SMins, SSecs: String;
  Str: String;
begin     
  DT:= Now;
  Days:= DaysBetween(DT, fDestDT);
  Hours:= HoursBetween(fDestDT, DT) mod 24; // Remove total days
  Mins:= MinutesBetween(DT, fDestDT) mod 60;
  Secs := SecondsBetween(DT, fDestDT) mod 60;
  if Days =  1  then SDays:=  'Day'    else SDays:=  'Days';
  if Hours = 1  then SHours:= 'Hour'   else SHours:= 'Hours';
  if Mins =  1  then SMins:=  'Minute' else SMins:=  'Minutes';
  if Secs =  1  then SSecs:=  'Second' else SSecs:=  'Seconds';
  Str:= Format('%d '+SDays+' %d '+SHours+' %d '+SMins+' %d '+SSecs,
    [Days, Hours, Mins, Secs]);
  if lblTime.Caption <> Str then
    lblTime.Caption:= Str;
end;
like image 754
Jerry Dodge Avatar asked Dec 07 '22 18:12

Jerry Dodge


1 Answers

See DaysBetween, HoursBetween, MinutesBetween, and SecondsBetween in DateUtils. You have to do some minor math. :)

Here's a sample console app to demonstrate:

program Project2;

{$APPTYPE CONSOLE}

uses
  SysUtils, DateUtils;

procedure ShowTimeDiff(const StartDate, OldDate: TDateTime);
var
  Days, Hours, Mins, Secs: Word;
  OutputText: string;
begin
  Writeln(Format('Start: %s, Old: %s',
      [FormatDateTime('mm/dd/yyyy hh:nn:ss', StartDate),
      FormatDateTime('mm/dd/yyyy hh:nn:ss', OldDate)]));
  Days := DaysBetween(StartDate, OldDate);
  Hours := HoursBetween(OldDate, StartDate) mod 24; // Remove total days
  Mins := MinutesBetween(StartDate, OldDate) mod 60;
  Secs  := SecondsBetween(StartDate, OldDate) mod 60;
  OutputText := Format('  %d days, %d hours, %d min, %d secs',
                       [Days, Hours, Mins, Secs]);
  WriteLn(OutputText);

end;

var
  BeginDate, EndDate: TDateTime;
begin
  BeginDate := Now;
  EndDate := BeginDate - 0.5;   // about 12 hours earlier
  ShowTimeDiff(BeginDate, EndDate);

  EndDate := BeginDate - 2.53724;  // Create date about 2 1/2 days earlier
  ShowTimeDiff(EndDate, BeginDate);

  EndDate := BeginDate - 5.75724;  // Create date about 5 3/4 days earlier
  ShowTimeDiff(BeginDate, EndDate);
  ReadLn;
end.

Produces the following output:

Time differences

Note that the reversal of parameter order between DaysBetween and HoursBetween is intentional to demonstrate that the functions always return positive values, so the order of the parameters isn't important. This is mentioned in the documentation.

like image 118
Ken White Avatar answered Dec 23 '22 16:12

Ken White