可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
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;
回答1:
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:
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.
回答2:
The problem is that when you subtract Now
from fDestDT
you expect to get difference between two dates, but you actually get another datetime value. As the values youre using are nearly the same, you get the "zero date" of the Delphi's datetime system, the 30. dets 1899. Thats why you get "30 Days" for FormatDateTime('d', DT)+' Days'
.
Since the smallest amount youre intrested in is second I suggest you use SecondsBetween to get the difference between two timestamps and then divide it into parts like
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';
回答3:
If you are using Delphi 2010 (I believe) or above, you can likely simplify your code and make it more clear by using the TimeSpan.pas
unit, which contains a record that you can use to break out the amount of time in a given span of time.
回答4:
I needed something more flexible that covers different 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 LEADING zero-valued parts
tdfAllNonZeros
: excludes ALL zero-valued parts
tdfXNonZeros
: includes only first X non-zero valued 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