Date / Time Processing - Friendly Countdown String

I am creating something that has a countdown to a specific date / time. It works for me - at least hours, minutes and seconds work fine. My problem is that when I try to implement Days, it does not give the correct result. I know about the DateUtils module, but there are a lot of things, and I donโ€™t know how to do this, especially since I'm terrible in math.

I have a timer with an interval of 100. Then I have a global fDestDT for the fDestDT date / time to base the countdown on. In the timer, I have a local TDateTime called DT . Then I break it into several lines and combine them into one "friendly" line ...

 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 changed end; 

It should be something like ...

0 Days, 3 Hours, 1 Minute, 12 Seconds

But instead, the days show wrong, when the countdown date / time is today, it shows 30 days ...

30 Days, 3 Hours, 1 Minute, 12 Seconds

I suppose that if I had done this more than 1 month in advance, that would not have shown the right thing either. How to get the number of days? And is there anything in the DateUtils module that can automate most of this work better than I already have?

EDIT: FIXED! The problem was that I was stupidly subtracted using DT:= fDestDT - Now; which was correct in my first code snippet, but after the conversion, instead of using DateUtils.DaysBetween I needed to remove this subtraction and just set DT:= Now; .

Work 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; 
+4
source share
4 answers

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

Here's an example console application 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. 

It produces the following output:

Time differences

Note that changing the order of parameters between DaysBetween and HoursBetween deliberately demonstrates that functions always return positive values, so the order of parameters is not important. This is indicated in the documentation.

+7
source

The problem is that when you subtract Now from fDestDT you expect to get the difference between the two dates, but actually get a different datetime value. Since the values โ€‹โ€‹you use are almost the same, you get the โ€œzero dateโ€ of the Delphi datetime system, 30. dets 1899. That's why you get โ€œ30 daysโ€ for FormatDateTime('d', DT)+' Days' .

Since the smallest amount you requested is the second, I suggest you use SecondsBetween to get the difference between the two timestamps, and then split it into parts, such as

 diff := SecondsBetween(Now, fDestDT); S:= IntToStr(diff mod 60)+' Seconds'; diff := diff div 60; N:= IntToStr(diff mod 60)+' Minutes'; diff := diff div 60; H:= IntToStr(diff mod 24)+' Hours'; diff := diff div 24; D:= IntToStr(diff)+' Days'; 
+4
source

If you are using Delphi 2010 (I suppose) or higher, you are more likely to simplify your code and make it more understandable by using the TimeSpan.pas block, which contains an entry that you can use to break down the amount of time over a certain period of time.

+3
source

I need something more flexible, encompassing various formats, so I implemented TTimeDiff as:

 uses SysUtils, DateUtils, StrUtils, Math; type TTimeDiff = record type TTimeDiffFormat = (tdfFull, tdfSignificant, tdfAllNonZeros, tdfXNonZeros); procedure Init(const ANow, AThen: TDateTime); class function TimeDiff(const ANow, AThen: TDateTime): TTimeDiff; static; function ToString(const TimeDiffFormat: TTimeDiffFormat; const Delimiter: string = ', '; const NonZerosCount: Byte = 1): string; case Integer of 0: (Years, Months, Days, Houres, Minutes, Seconds: Word); 1: (Values: array[0..5] of Word); end; { TTimeDiff } class function TTimeDiff.TimeDiff(const ANow, AThen: TDateTime): TTimeDiff; begin Result.Init(ANow, AThen); end; procedure TTimeDiff.Init(const ANow, AThen: TDateTime); begin Years := YearsBetween(ANow, AThen); Months := MonthsBetween(ANow, AThen) mod 12; Days := DaysBetween(IncMonth(Min(ANow, AThen), Years * 12 + Months), Max(ANow, AThen)); Houres := HoursBetween(ANow, AThen) mod 24; Minutes := MinutesBetween(ANow, AThen) mod 60; Seconds := SecondsBetween(ANow, AThen) mod 60; end; function TTimeDiff.ToString(const TimeDiffFormat: TTimeDiffFormat; const Delimiter: string = ', '; const NonZerosCount: Byte = 1): string; const Captions: array [0..5] of string = ('year', 'month', 'day', 'hour', 'minute', 'second'); var I: Integer; VisitedNonZeros: Byte; begin Result := ''; VisitedNonZeros := 0; for I := 0 to 5 do begin if Values[I] > 0 then Inc(VisitedNonZeros); if (TimeDiffFormat = tdfFull) or ((TimeDiffFormat = tdfSignificant) and (VisitedNonZeros > 0)) or ((TimeDiffFormat in [tdfAllNonZeros, tdfXNonZeros]) and (Values[I] > 0)) then begin Result := Result + Format('%d %s%s%s', [Values[I], Captions[I], IfThen(Values[I] = 1, '', 's'), Delimiter]); if (TimeDiffFormat = tdfXNonZeros) and (VisitedNonZeros = NonZerosCount) then Break; end; end; Result := Copy(Result, 1, Length(Result) - Length(Delimiter)); end; 

TTimeDiffFormat explanation:

  • tdfFull : includes all parts regardless of their values โ€‹โ€‹(years, months, days, hours, minutes and seconds, respectively).

  • tdfSignificant : excludes tdfSignificant parts with a null value

  • tdfAllNonZeros : excludes ALL parts with a null value

  • tdfXNonZeros : includes only the first X non-zero parts, where X is set to 1 by default

How to use:

 var ANow, AThen: TDateTime; Diff: TTimeDiff; begin try ANow := DateUtils.EncodeDateTime(1993, 11, 3, 21, 22, 18, 0); AThen := DateUtils.EncodeDateTime(1993, 9, 21, 6, 21, 34, 0); Writeln('Difference between '); Writeln(FormatDateTime('YYYY/MM/DD HH:NN:SS', ANow), ' and'); Writeln(FormatDateTime('YYYY/MM/DD HH:NN:SS', AThen), ' is:'); Writeln(''); Diff.Init(ANow, AThen); with Diff do begin Writeln(ToString(tdfFull)); Writeln(ToString(tdfSignificant, ' and ')); Writeln(TTimeDiff.TimeDiff(Athen, ANow).ToString(tdfSignificant), ' (inverted)'); Writeln(ToString(tdfAllNonZeros)); Writeln(ToString(tdfXNonZeros, ', ', 2)); Writeln(ToString(tdfXNonZeros)); readln; end; except on E: Exception do Writeln(E.ClassName, ': ', E.Message); end; end. 

Results:

 Difference between 1993/11/03 21:22:18 and 1993/09/21 06:21:34 is: 0 years, 1 month, 13 days, 15 hours, 0 minutes, 43 seconds 1 month and 13 days and 15 hours and 0 minutes and 43 seconds 1 month, 13 days, 15 hours, 0 minutes, 43 seconds (inverted) 1 month, 13 days, 15 hours, 43 seconds 1 month, 13 days 1 month 
0
source

Source: https://habr.com/ru/post/1382403/


All Articles