FastReport calculate hours from Date_From and Date_To fields - delphi

I am using FastReport with Delphi. I have following fields:
DATE_FROM (example: 01.05.2015.)
TIME_FROM (example: 13:00)
DATE_TO (example: 02.05.2015.)
TIME_TO (example: 10:00)
I need to calculate how many days and hours have passed between these pairs of dates. In this example it is less than 24 hours... so the result should be:
0 days 21 hours
How to achieve that?

I would install TurboPower Orpheus. Include the OvcDate unit in your uses statement. This example was produced with Berlin 10.1 Architect and is reasonably downward compatible. There are many versions of Orpheus.
procedure TForm2.SpeedButton1Click(Sender: TObject);
var
date1 : TDateTime;
date2 : TDateTime;
rdt1 : TStDateTimeRec;
rdt2 : TStDateTimeRec;
days : Integer;
seconds : Integer;
hrs : Integer;
min : Integer;
sec : Integer;
begin
date1 := StrToDateTime('01/05/2015 13:00');
date2 := StrToDateTime('02/05/2015 10:00');
rdt1.D := DateTimeToStDate(date1);
rdt2.D := DateTimeToStDate(date2);
rdt1.T := DateTimeToStTime(date1);
rdt2.T := DateTimeToStTime(date2);
DateTimeDiff(rdt1, rdt2, days, seconds);
hrs := seconds div SecondsInHour;
min := ((seconds - (hrs * SecondsInHour)) div SecondsInMinute);
sec := (seconds - (hrs * SecondsInHour) + (min * SecondsInMinute));
sec := sec;
// days = 30
// seconds = 75600
// hrs = 21
// min = 0
// sec = 0
end;
The above example establishes proof of concept and shows how the calculation works. Your input was 1.5.2015 and 2.5.2015 for the respective dates and that is what I used.
I would set up the appropriate calculation fields and place this formula in the OnCalc event. Be sure to establish a mechanism to set the start and stop datetime fields. Let the DBMS do the rest. I just happen to be one of the Most Popular Delphi Authors. http://cc.embarcadero.com/PopularAuthors.aspx

Related

Formatted time difference between two database datetime fields

I tried :
procedure TDataModule2.JournalLCalcFields(DataSet: TDataSet);
begin
JOURNAL.FieldByName('TIME').Value:= FormatDateTime('hh:mm:ss', JORNAL.FieldByName('end_date').AsDateTime - ZURNAL.FieldByName('start_date').AsDateTime);
end;
It kind of gives me the right answer at first when I run it but when I test it (change the end_date by a whole day on the sql server) then the result is totally wrong.
Any clues as to why
the oncalculate event fails?
TIME field is text.
FormatDateTime() is meant for formatting a specific date/time value, not a duration between two date/time values.
You can easily write your own code to format a duration, eg:
uses
..., DateUtils, SysUtils;
procedure TDataModule2.JournalLCalcFields(DataSet: TDataSet);
var
duration, hours, minutes, seconds: Int64;
begin
duration := SecondsBetween(JORNAL.FieldByName('end_date').AsDateTime, ZURNAL.FieldByName('start_date').AsDateTime);
hours := duration div 3600;
duration := duration mod 3600;
minutes := duration div 60;
duration := duration mod 60;
seconds := duration;
JOURNAL.FieldByName('TIME').Value := Format('%.2d:%.2d:%.2d', [hours, minutes, seconds]);
end;
Or, you can use the RTL's TTimeSpan type to help you, eg:
uses
..., System.TimeSpan, SysUtils;
procedure TDataModule2.JournalLCalcFields(DataSet: TDataSet);
var
ts: TTimeSpan;
begin
ts := TTimeSpan.Subtract(JORNAL.FieldByName('end_date').AsDateTime, ZURNAL.FieldByName('start_date').AsDateTime);
JOURNAL.FieldByName('TIME').Value := Format('%.2d:%.2d:%.2d', [ts.Hours, ts.Minutes, ts.Seconds]);
end;

convert HH:MM:SS to seconds or minutes with delphi

Can someone advise me on the best way to convert a duration formated HH:MM:SS to seconds or minutes only?
ie. 00:01:49 = 109secs
I´m using this code but not work
var
i: real;
j: integer;
begin
i := frac(real(StrToTime('00:00:01')));
j := trunc(frac(real(StrToTime('01:00:00'))) / i );
memo2.lines.add(inttostr(j));
when I try the code with 00:10:00 return 599
thanks
Using the DateUtils unit:
WriteLn(SecondOfTheDay(StrToTime('00:10:00')));
WriteLn(MinuteOfTheDay(StrToTime('00:10:00')));
Outputs:
600
10
The reason why your code is not working is that floating point values often can not be exactly represented. To avoid all implementation details about how a TDateTime represents the time, use the built in functions in SysUtils and DateUtils, see Date and Time Support.
A very flexibel tool for handling durations is TTimeSpan found in unit System.TimeSpan. You can get the result in different units (second, minutes, hours, ...) and format that to your needs.
var
timeSpan: TTimeSpan;
begin
timeSpan := TTimeSpan.Parse('00:01:49');
memo2.lines.add(Format('%1.1f min', [timeSpan.TotalMinutes]));
end;
Use DecodeTime:
http://docwiki.embarcadero.com/Libraries/XE2/en/System.SysUtils.DecodeTime
So your code should look like this:
DecodeTime(StrToTime('00:00:01'), Hour, Min, Sec, MSec);
A function that returns the seconds should look something like this:
function GetSeconds(ATimeString: string): Integer;
var
Hour, Min, Sec, MSec: Word;
begin
DecodeTime(StrToTime(ATimeString), Hour, Min, Sec, MSec);
Result := Hour * 3600 + Min * 60 + Sec;
end;
This is not a standalone answer but just an extension to Uwe's answer. Upvote his answer.
{ Converts a string formatted like 'hh:mm:ss' to seconds.
Returns -1 is the string does not contain a valid time.
StringToSeconds('00:01:30') // returns 90 (sec)
StringToSeconds('01:30') // returns 5400 (sec)
StringToSeconds('10') // returns 864000 (sec)
StringToSeconds('1.30') // returns -1
StringToSeconds('x') // returns -1 }
function StringToSeconds(CONST s: String): integer;
VAR
TimeSpan: TTimeSpan;
begin
TRY
TimeSpan:= System.TimeSpan.TTimeSpan.Parse(s);
Result := round(TimeSpan.TotalSeconds);
EXCEPT
Result:= -1;
end;
end;

Format a int variable into a mm:ss

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 ... :-)

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

How to get elapsed time & format it?

I have two unix timestamps as LONG INT. I want to subtract start from end to get elapsed time and format it to hh:mm:ss
How do I do this? Thanks
you can use the UnixToDateTime and the FormatDateTime functions see this sample
uses
DateUtils,
SysUtils;
var
StartUnixTime : Int64;
EndUnixTime : Int64;
StartDateTime : TDateTime;
EndDateTime : TDateTime;
begin
try
StartUnixTime:=1293062827;
EndUnixTime :=1293070000;
//option 1 converting both unix times to TDatetime and then subtract
StartDateTime:=UnixToDateTime(StartUnixTime);
EndDateTime :=UnixToDateTime(EndUnixTime);
Writeln(Format('Elapsed time %s',[FormatDateTime('hh:nn:ss',EndDateTime-StartDateTime)]));
//option 2 subtract directly and then convert to TDatetime
Writeln(Format('Elapsed time %s',[FormatDateTime('hh:nn:ss',UnixToDateTime(EndUnixTime-StartUnixTime))]));
except
on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
Readln;
end.
Additionally if you wanna get the Years, Months and Days , you can use the YearsBetween, MonthsBetween and the DaysBetween functions in this way.
Writeln(Format('Years %d Months %d Days %d',[YearsBetween(EndDateTime,StartDateTime),MonthsBetween(EndDateTime,StartDateTime),DaysBetween(EndDateTime,StartDateTime)]));
UnixTime1 := 123456;
UnixTime2 := 123460;
Diff := UnixTime2 - UnixTime1;
if Diff > 24 * 60 * 60 then
raise Exception.CreateFmt('Time difference (%s seconds) is longer than a day.', [Diff]);
s := Format('%.2d:.%2d:%.2d', [Diff div 60 div 60, (Diff div 60) mod 60, Diff mod 60]);
This answer did not produce the results I was looking for. I opted to use the OvcDate.DateDiff procedure to give the results I was looking for.
procedure TBCSJEmpdm.q1CalcFields(DataSet: TDataSet);
var
das, mos, yrs : Integer;
begin
OvcDate.DateDiff(DateTimeToStDate(DataSet.FieldByName('fromd').AsDateTime),
DateTimeToStDate(DataSet.FieldByName('tod').AsDateTime), das, mos, yrs);
DataSet.FieldByName('das').AsInteger := das;
DataSet.FieldByName('mos').AsInteger := mos;
DataSet.FieldByName('yrs').AsInteger := yrs;
end;
This approach give the elapsed years, months, and days between two dates. OvcDate requires ovc.inc file. Just let me know if you need these.

Resources