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!
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;
Related
I am trying to make a countdown timer, the idea is to set the time in text edit property and after i click set timer(button), that time to be sent to Label, which will then start the countdown to 0. I have gotten to this part, but i cant figure out a way to make seconds countdown, If any of you guys can help I would appreciate it.
I tried this from an example I found online but it didnt work because this is Firemonkey application.
dec(TotalTime); {decrement the total time counter}
// Timer code..
procedure TForm1.ButtonSetTimerClick(Sender: TObject);
var
GetTime : TDateTime;
begin
Timer3.Enabled := True;
Label11.Text := Edit1.Text;
ButtonSetTimer.Enabled := False;
Edit1.Enabled := False;
GetTime := StrToTime(Edit1.Text);
end;
procedure TForm1.ButtonStopTimerClick(Sender: TObject);
begin
Timer3.Enabled := False;
ButtonSetTimer.Enabled := True;
Edit1.Enabled := True;
end;
procedure TForm1.Timer3Timer(Sender: TObject);
var
GetTime : TDateTime;
Hour, Min, Sec, MSec: Word;
begin
DecodeTime(GetTime, Hour, Min, Sec, Msec);
Label11.Text := TimeToStr(GetTime);
Label11.Text := IntToStr(Hour) + ':'+ IntToStr(Min) + ':'+ IntToStr(Sec);
Label11.Text := Format('%2.2u:%2.2u:%2.2u',[Hour,Min,Sec]);
end;
Cheers.
You did not say how (in which format) the time is to be entered in the TEdit, so here are three alternative time entry possibilities. The output is anyway formatted as H:M:S.
I modified the code from yesterday to use TryStrToInt / TryStrToTime to catch errors. Also, a Seconds counter together with OnTimer event as in my previous example has a poor accuracy and can drift several seconds within 5 minutes. Edijs solution to compare Now with a calculated end time is insensitive to the inaccuracy of OnTimer events, so I adopted that too.
var
TimeOut: TDateTime;
function SecsToHmsStr(ASecs: integer):string;
begin
Result := Format('%2d:%2.2d:%2.2d',
[ASecs div 3600, ASecs mod 3600 div 60, ASecs mod 3600 mod 60]);
;end;
procedure TForm6.Timer1Timer(Sender: TObject);
begin
Label1.Caption := SecsToHmsStr(SecondsBetween(Now, TimeOut));
if Now > Timeout then Timer1.Enabled := False;
end;
Time entry alternative one, Timeout after a given number of seconds
// Timeout after a given number of seconds
procedure TForm6.Button1Click(Sender: TObject);
var
Seconds: integer;
begin
if TryStrToInt(Edit1.Text, Seconds) then
begin
TimeOut := IncSecond(Now, Seconds);
Timer1.Enabled := True;
Label1.Caption := SecsToHmsStr(SecondsBetween(Now, TimeOut));
end
else
ShowMessage('Error in number of seconds');
end;
Time entry alternative two, Timeout after a given number of hours, minutes and seconds
// Timeout after a given number of hours, minutes and seconds
procedure TForm6.Button2Click(Sender: TObject);
begin
if TryStrToTime(Edit1.Text, TimeOut) then
begin
TimeOut := Now + TimeOut;
Timer1.Enabled := True;
Label1.Caption := SecsToHmsStr(SecondsBetween(Now, TimeOut));
end
else
ShowMessage('Error in time format');
end;
Time entry alternative three, Timeout at a given time within 24 hours
// Timeout at a given time within 24 hours
procedure TForm6.Button3Click(Sender: TObject);
begin
if TryStrToTime(Edit1.Text, TimeOut) then
begin
if TimeOut <= Time then
TimeOut := Tomorrow + TimeOut
else
TimeOut := Today + TimeOut;
Timer1.Enabled := True;
Label1.Caption := SecsToHmsStr(SecondsBetween(Now, TimeOut));
end
else
ShowMessage('Error in time format');
end;
This should do it:
Uses
System.DateUtils;
type
..
private
FDateTimeTo: TDateTime;
end;
function IntToTimeStr(const ASeconds: Int64): string;
begin
Result := Format('%2d:%2.2d:%2.2d', [ASeconds div 3600, ASeconds mod 3600 div 60,
ASeconds mod 3600 mod 60]);
end;
procedure TForm1.BitBtn1Click(Sender: TObject);
begin
FDateTimeTo := StrToDateTime(FormatDateTime('yyyy' + FormatSettings.DateSeparator + 'mm' +
FormatSettings.DateSeparator + 'dd 00:00:00', Now)) + StrToTime(Edit1.Text);
if CompareDateTime(Now, FDateTimeTo) = 1 then
FDateTimeTo := IncDay(FDateTimeTo);
end;
procedure TfrmMain.Timer1Timer(Sender: TObject);
begin
Label1.Caption := IntToTimeStr(SecondsBetween(Now, FDateTimeTo));
end;
I'm a noob so please don't assume I know much. Feel free to let me know if I use incorrect termanology.
I have a function in a PAL script (based on Pascal / Delphi) in SAM broadcaster, radio automation software.
The function returns the time in milliseconds of Cue Point 1 in a database record related to a music file.
I wish to call this function's output in the body of my script as you might a variable. But it needs to be expressed as a hh:mm:ss timestamp.
Here is the function, which might have an output of 20000, for 20 seconds.
var CP : Integer = 0;
function ExtractCP(Song : TSongInfo):Integer;
var
P : Integer;
XFade : String;
begin
Result := -1;
XFade := Trim(Song['xfade']);
WriteLn('Decoding XFade string');
WriteLn('XFade: '+XFade);
if XFade = '' then
Result := -1
else
begin
P := Pos('ct0=',XFade); {Where 0 is the Custom Cue Point Number}
if (P > 0) then
begin
Delete(XFade,1,P+2);
P := Pos('&',XFade);
if (P>0) then
Delete(XFade,P,Length(XFade));
Result := StrToIntDef(XFade,-1);
WriteLn('CP time detected: '+XFade);
end;
end;
end;
Here is the implementation component.
while (Song['songtype']='S') and (not Skip) do
begin
VAR DT : DateTime;
VAR frac : Float;
VAR hours, minutes, seconds, milliseconds : Integer;
hours := 24;
minutes := 60;
seconds := 60;
milliseconds := 1000;
// 1 millisecond as fractional part of a day
frac := 1.0 / hours / minutes / seconds / milliseconds;
frac := frac * cp;
dt := Now + DateTime (frac); {Wait for Cue Point 0}
WriteLn(DateTimeToStr(dt));
PAL.WaitForTime(DT);
Skip := True;
end;
I guess my question is a simple one.
Calculated variable 'cp' is not being imported, how to I correctly call the result of the function in the line...
frac := frac * cp;
I have had help here (for full background, including timestamp calculation methodology and entire script)...
http://support.spacialaudio.com/forums/viewtopic.php?f=23&t=40795&start=15
Assuming XFade contains a String like 'ABC ct0=1234&'
Delete(XFade,1,P+2); will deliver '=1234&'
P := Pos('&',XFade);
if (P>0) then
Delete(XFade,P,Length(XFade));
will deliver '=1234' which can not be convertet to an integer
So at least you will have to change Delete(XFade,1,P+2); to Delete(XFade,1,P+3);
the generation of dt can be shortened to
{ if not defined
Const
MSecsPerDay= 24*60*60*1000;
}
cp := ExtractCP(Song);
if cp>-1 then
begin
dt := Now + CP / MSecsPerDay;
.....
end;
Can anyone help me how to format an int variable in delphi into a minute:seconds??
sample:
myVar := 19;
my label caption should display 00:19
any idea anyone? thanks
This will avoid any errors for seconds values that overflow into hours.
var
secs: integer;
str: string;
begin
secs := 236;
// SecsPerDay comes from the SysUtils unit.
str := FormatDateTime('nn:ss', secs / SecsPerDay));
// If you need hours, too, just add "hh:" to the formatting string
secs := 32236;
str := FormatDateTime('hh:nn:ss', secs / SecsPerDay));
end;
Assuming the myVar contains number of seconds:
label1.Caption := Format('%.2d:%.2d', [myVar div 60, myVar mod 60]);
You should use FormatDateTime method like this:
procedure TForm1.FormCreate(Sender: TObject);
const MyConst: Integer = 19;
begin
Caption:=FormatDateTime('nn:ss', EncodeTime(0, MyConst div 60, MyConst mod 60, 0));
end;
Expanding onto Brad's answer, I've wrapped this into a function which detects if the time is over an hour, and automatically shows hours if so. Otherwise, if it's less than an hour, it doesn't show the hours. It also has an optional parameter to define whether to show a leading zero on the hours and minutes, depending on your preference (i.e. 03:06:32 vs 3:6:32). This makes it a little more human-readable.
function SecsToTimeStr(const Secs: Integer; const LeadingZero: Boolean = False): String;
begin
if Secs >= SecsPerHour then begin
if LeadingZero then
Result := FormatDateTime('hh:nn:ss', Secs / SecsPerDay)
else
Result := FormatDateTime('h:n:ss', Secs / SecsPerDay)
end else begin
if LeadingZero then
Result := FormatDateTime('nn:ss', Secs / SecsPerDay)
else
Result := FormatDateTime('n:ss', Secs / SecsPerDay)
end;
end;
However, there are many different possible preferences with displaying a time period, which is up to you to decide. I won't cover all those possible ways here.
If you are sure you only want minutes and seconds - a quick solution could be:
Format('%d:%d',[(myVar div 60), (myVar mod 60)]);
Same solution as already proposed ... :-)
Using Delphi to read Outlook appointments via COM...
Code is working fine, with the exception of Recurring appointments.
Everything I read says that I need to using RecurrencePattern and GetOccurrence and determine where the next appointment should be, and then try to get it, and see if it fails... This seems like a really "kludged" way of doing it.
Has anyone already written something like this? Apparently, there is some code on experts-exchange, but I don't have a subscription there... Can't find anything else.
IDEALLY (and I will take what I can get), I would like a routine that says.. this appointment has 6 occurrences, and here is an array of all the TDateTimes of each occurrence.
Note that all of this code works fine. I just need help filling out the BOTTOM section of code to build recurrance patterns.
CODE FRAGMENTS --- Not all code is shown---... as per request...
Access Outlook...
try
Outlook := GetActiveOleObject('outlook.application');
Form1.SB1.SimpleText := 'Outlook already started';
except
try
Outlook := CreateOleObject('outlook.application');
Created := True;
Form1.SB1.SimpleText := 'Outlook not running. Starting Outlook API';
except
// Unable to access or start OUTLOOK
MessageDlg(
'Unable to start or access Outlook. Possibilities include: permission problems, server down, or VPN not enabled. Exiting...', mtError, [mbOK], 0);
exit;
end;
end;
... Get the Calendar of my recipient...
// Now get the calendar entry
Calendar := Namespace.GetSharedDefaultFolder(Recip, 9);
Now set the filter to restrict appoints to be within a date range, and include recurrences.
// If here, everything is good so far...
// user name, email, and Calendar is accessible
MyItems := Calendar.Items;
MyItems.Sort('[Start]', False);
MyItems.IncludeRecurrences := True;
// Set the filter dates... SECONDS can NOT be shown...
FilterStartDate := FormatDateTime('mmmm dd, yyyy', StartDate);
FilterStartDate := FilterStartDate + ' 12:00 AM';
FilterEndDate := FormatDateTime('mmmm dd, yyyy', EndDate);
FilterEndDate := FilterEndDate + ' 11:59 PM';
RestrictDateFilter := ('[Start]>' + CHR(34) + FilterStartDate + CHR(34) + 'and ' + '[Start]<' + CHR(34)
+ FilterEndDate + CHR(34));
DebugIt('RestrictFilter:', RestrictDateFilter);
Application.ProcessMessages;
ItemCollection := MyItems.Restrict(RestrictDateFilter);
ItemCollection.Sort('[Start]', False);
Read my first appointment
// Try to read the first appoint, or error message if no appointments
try
Appointment := ItemCollection.GetFirst;
except
DebugIt('No appointments found', '');
MessageDlg('Unable to retrieve any appointments in this time frame.', mtError, [mbOK], 0);
exit;
end;
While looping through all the appointments...
if Appointment.IsRecurring = True then
begin
// Recurring Appointment, in a Valid RANGE
DebugIt('Repeating appointment starting on ' + DateToStr(Appointment.Start), '');
// If yearly repeating, we want to ignore
RP := Appointment.GetRecurrencePattern;
DebugIt('Determining appointment recurrence pattern', '');
if ((RP.RecurrenceType = olRecursYearly) or (RP.RecurrenceType = olRecursYearNth)) then
begin
// ignore these appointments
end
else
begin
// HERE IS WHERE I NEED HELP
// How do I determine all of the appointments based on the recurrences?
end;
end;
Thanks
GS
Figured out an answer....
Here is a routine I wrote which will go from MinDate to MaxDate, and test to see if the appointment exists on that date. This was the only way that I could get recurrences to work...
procedure IdentifyOutlookRecurrences(Appt: Variant; EmailID: Integer; MinDateAllowed, MaxDateAllowed: TDateTime);
var
recurStart, recurEnd: TDateTime;
RP: Variant;
dt: TDate;
PatternEndDate: TDate;
TestAppt: Variant;
year, month, day, hour, minute, second, ms: Word;
CheckDateTime: TDateTime;
OccurrenceEndDate: TDateTime;
OccurrenceNumber: Integer;
begin
if Appt.IsRecurring then
begin
RP := Appt.GetRecurrencePattern;
DebugIt('Recurring Appt:', Appt.Subject);
// Get the date range for our occurrences
recurStart := RP.PatternStartDate;
recurEnd := RP.PatternEndDate;
DebugIt('Recur Start:End', DateToStr(recurStart) + ':' + DateToStr(recurEnd));
DebugIt('RecurPattern Start Time', DateTimeToStr(RP.StartTime));
// Identify the end point for looping...
if recurEnd < MaxDateAllowed then
PatternEndDate := recurEnd
else
PatternEndDate := MaxDateAllowed;
// Get the minimum date allowed...
dt := trunc(MinDateAllowed);
DecodeDate(dt, year, month, day);
DecodeTime(RP.StartTime, hour, minute, second, ms);
OccurrenceNumber := 0;
repeat
DecodeDate(dt, year, month, day);
CheckDateTime := EncodeDateTime(year, month, day, hour, minute, second, 0);
DebugIt('Check for recurrance', DateTimeToStr(CheckDateTime));
// Now check it the appointment exists.
try
TestAppt := RP.GetOccurrence(CheckDateTime);
OccurrenceEndDate := CheckDateTime + (RP.Duration / 1440);
DebugIt('Appt Recurrence *** IS *** found', DateTimeToStr(CheckDateTime));
// Now write it to the database
InsertApptIntoDB(Appt, EmailID, OccurrenceNumber, CheckDateTime, OccurrenceEndDate);
Inc(OccurrenceNumber);
except
DebugIt('Appt Recurrence *** NOT *** found', DateTimeToStr(CheckDateTime));
end;
// Increment our date
dt := dt + 1;
until dt > PatternEndDate;
end;
end;
DebugIt is just a logging routine I use...
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;
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.
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';
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.
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