Difference between 2 dates in Delphi 5 - delphi

I want to be able to find the difference in seconds between two TDateTime variables in Delphi 5. In the later versions of delphi there are helper functions SecondsBetween to do this. Is there a better way to do this other than rolling my own (Finding the difference of the two variables as a double then converting them to seconds)?
Thanks.

Secs := Round(SecsPerMin * MinsPerHour * HoursPerDay * Abs(Time2 - Time1));

Here goes another way of doing the same. Tested in Delphi 5
var
Hour, Min, Sec, MSec: Word;
begin
DecodeTime(datetime2-datetime1, Hour, Min, Sec, MSec);
end;

Related

I want to calculate the difference in hours between two times. What components should i use: DateTimepickers or ComboBoxes

var
Hour, Min, Sec, MSec: Word;
begin
DecodeTime(ComboBox1-ComboBox2, Hour, Min, Sec, MSec);
end;
This is the code but it keeps getting errors?
DecodeTime expects a TDateTime variable. ComboBox1 and ComboBox2 are components. Use TDateTimePicker and their DateTime properties. (Or possibly the Time property if that suits better).
To calculate the hours between both times, use DateUtils.HoursBetween instead of DecodeTime.
hourDiff := HoursBetween(DateTimePicker1.DateTime,DateTimePicker2.DateTime);

Convert Extended to Time

I need to convert extended values to time format. For example :
3.50 represents 00:03:50
62.02 represents 01:02:02
73.70 represents 01:14:10
I have tried the following function to convert the Hour and Minutes part but I don't have any idea on how to convert the Seconds' part.
function ConvertToTime(AValue: Extended): TDateTime;
begin
Result:= EncodeTime(trunc(ArticleRec.Quantity) div 60,trunc(ArticleRec.Quantity) mod 60,0,0);
end;
Thanking you in anticipation for your help.
The fractional part is obtained like this:
var
SecondsFrac: Double;
....
SecondsFrac := Frac(Value);
And then you can convert from a floating point fractional value in the range 0 to 1 to an integer in the range 0 to 100 like this:
var
Seconds: Integer;
....
Seconds := Round(SecondsFrac*100);
This is a pretty weird way to store time though. You have to deal with the fact that when Seconds >= 60 you need to increment the minutes, and decrement Seconds by 60.
I guess I'd do that by converting the time into seconds, and going from there:
function ConvertWeirdTimeFormatToSeconds(const Value: Double): Integer;
var
SecondsFrac: Double;
begin
SecondsFrac := Frac(Value);
Result := Round(SecondsFrac*100) + Trunc(Value)*60;
end;
You can then decode the seconds into distinct parts like this:
procedure DecodeSeconds(Value: Integer; out Hours, Minutes, Seconds: Integer);
begin
Seconds := Value mod 60;
Value := Value div 60;
Minutes := Value mod 60;
Value := Value div 60;
Hours := Value;
end;
Which makes me think it might be better to just store the time in an integer number seconds from midnight. It makes far more sense to use a standard format, in my view.
I see no reason to use Extended here, or indeed anywhere for that matter. It's a non-standard type that due to its strange size and consequent alignment issues tends to perform poorly. And it's only supported on x86.

? about Anatomy of a Double in general and in Delphi specifically

I understand that doubles have a sign (1 bit), exponent (11 bits) and fraction (52 bits) (according to Wikipedia anyway).
I have three questions.
1) How do I use these three parts to convert them to a number I can use (both the Integral and fractional parts)?
2) How are the three parts stored in a double within Delphi?
3) Once I know the integral and fractional parts, I'm really using them as a date and time with the date being the integral part being an offset in days and the fractional being an offset, I think, in milliseconds from midnight. So really its two numbers I am trying to derive from the double, days and milliseconds. The question is, is there a way to separate the two into ints? that is, day offset into int (or longint) and milliseconds into another int (or longint) using standard math functions?
I found out earlier that hex representations of dates I have stored were indeed Delphi doubles (see below). But I don't know how the double is represented
----------- answer to an earlier question --------------------------
Effectively these strings looks like the Hex representation for the Delphi TDatetime type which is an alias for the Double type (8 bytes), Where the integral part of the TDateTime value is the number of days that have passed since 12/30/1899 and the fractional part of the TDateTime value is fraction of a 24 hour day that has elapsed.
in Delphi you can parse such values using the HexToBin function like so
{$APPTYPE CONSOLE}
uses
System.Classes,
System.SysUtils;
function Decode(const HexStr: AnsiString) : TDateTime;
begin
Assert(Length(HexStr)=16, 'Lenght must be 16');
HexToBin(PAnsiChar(HexStr), #Result, SizeOf(TDateTime));
end;
begin
try
Writeln(DateTimeToStr(Decode('a297780add3ee440')));
Writeln(DateTimeToStr(Decode('5c6c320bdd3ee440')));
Writeln(DateTimeToStr(Decode('67b176e0dd3ee440')));
Writeln(DateTimeToStr(Decode('38a7e155bc42e440')));
Writeln(DateTimeToStr(Decode('d94ee458bc42e440')));
Writeln(DateTimeToStr(Decode('d22af9134989dc40')));
Writeln(DateTimeToStr(Decode('d4fb7863c542e440')));
Writeln(DateTimeToStr(Decode('e501c962c542e440')));
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
ReadLn;
end.
This will return
07-07-2013 21:46:50
07-07-2013 21:46:58
07-07-2013 22:24:27
07-08-2013 21:15:05
07-08-2013 21:15:37
01-01-1980 3:24:15
08-08-2013 4:02:29
08-08-2013 4:02:21
You're misunderstanding what the relevant parts of a TDateTime contain.
The integral part (the part to the left of the decimal point) is the number of days that have elapsed since December 30, 1899, as you state. However, the fractional part (the part to the right of the decimal), is simply the decimal representation of the time that has passed since 00:00:00 0001 (the start of that day).
In other words,
0.25 12/30/1899 06:00:00 (6:00 AM)
0.5 12/30/1899 12:00:00 (noon)
0.75 12/30/1899 18:00:00 (6:00 PM)
The simplest way to work with Delphi's TDateTime and it's various parts is to let the RTL handle it for you. To separate out the individual portions of the time, use DecodeTime. To retrieve the individual parts of a date, use DecodeDate (both from the 'SysUtils' unit:
var
Hr, Min, Sec: Word;
Yr, Mon, Day: Word;
DecodeTime(Now, Hr, Min, Sec);
DecodeDate(Now, Yr, Mon, Day);
To retrieve them all at once, use the DateUtils unit and DecodeDateTime instead:
DecodeDateTime(Now, Yr, Mon, Day, Hr, Min, Sec);
If you really do want just the individual parts (the left and right sides of the decimal point), then as David says you just use Int and Frac
A Delphi double value is a standard IEEE754 double, using the standard representation. All languages do this for various reasons. Not least of which is that the underlying hardware operates of IEEE754 types.
You can pull a double value apart using the TDoubleHelper introduced in XE3. The documentation shows you how: http://docwiki.embarcadero.com/Libraries/en/System.TDoubleHelper. For older Delphi versions you would use TDoubleRec instead.
Now, splitting the value into its significand and exponent will not help you. You want the integer part and the fractional part which are entirely different things. Get them using the standard functions Int() and Frac().
However, when you do this, you still won't have what you need. The fractional part is a floating point number between 0 and 1. That doesn't sound much like a number of milliseconds. So I believe that you do not yet fully understand this representation.
Perhaps what you have is nothing more than a TDateTime! In which case you use the standard functions from the RTL to decode it.
I've just read your earlier question. You are indeed on the wrong track. Read again the answer. Specifically:
Where the integral part of the TDateTime
value is the number of days that have passed since
12/30/1899 and the fractional part of the TDateTime value is
fraction of a 24 hour day that has elapsed.
So, in your VB code, convert the hex string, or binary, whichever you have, into a double. Then get the integer part and the fractional part and you are done..

Add Time to a TDateTime

I would like to add seconds to a TDateTime variable, so that the result is the top of the minute. For example, if it's 08:30:25, I want change the TDateTime variable to store 08:31:00.
I see that TDateTime has a Decode function, which I could use. There isn't, however, an encode function to put the altered time back into a TDateTime variable.
Using DateUtils it's possible to do it like this:
Uses
DateUtils;
var
Seconds : Word;
Seconds := SecondOfTheMinute(MyTime); // Seconds from last whole minute
// Seconds := SecondOf(MyTime); is equivalent to SecondOfTheMinute()
if (Seconds > 0) then
MyTime := IncSecond(MyTime,60 - Seconds);
There sure is, at least in the recent versions - see the DateUtils unit, especially all the Recode* routines and EncodeDateTime. The DateUtils unit is already available in Delphi 2010, perhaps even in earlier version.
Theory
The TDateTime data type represents number of days since 30 Dec 1899 as a real number. That is, the integral part of TDateTime is an amount of whole days, and the fractional part represents a time of day.
Practical
Therefore, your problem could be solved using simple arithmetics:
var
Days: TDateTime;
Mins: Extended; { widen TDateTime's mantissa by 11 bits to accommodate division error }
begin
Days := Date + StrToTime('08:30:25');
Writeln(DateTimeToStr(Days));
Mins := Days * 24 * 60 ; // compute minutes
Mins := Math.Ceil(Mins); // round them up
Days := Mins / (24 * 60); // and back to days
{ or as simple and concise expression as: }
// Days := Ceil(Days * MinsPerDay) / MinsPerDay;
Writeln(DateTimeToStr(Days));

Change today's date, advancing one month and setting the systemtime

I would like a code sample for a function that takes a tDateTime and an integer as input and sets the system time using setlocaltime after advancing that tDateTime by (int) months. The time should stay the same.
pseudo code example
SetNewTime(NOW,2);
The issues I'm running into are rather frustrating. I cannot use incmonth or similar with a tDateTime, only a tDate, etc.
Below is a complete command-line program that works for me. Tested in Delphi 5 and 2007. Why do you say IncMonth does not work for TDateTime?
program OneMonth;
{$APPTYPE CONSOLE}
uses
SysUtils,
Windows,
Messages;
procedure SetLocalSystemTime(settotime: TDateTime);
var
SystemTime : TSystemTime;
begin
DateTimeToSystemTime(settotime,SystemTime);
SetLocalTime(SystemTime);
//tell windows that the time changed
PostMessage(HWND_BROADCAST,WM_TIMECHANGE,0,0);
end;
begin
try
SetLocalSystemTime(IncMonth(Now,1));
except on E:Exception do
Writeln(E.Classname, ': ', E.Message);
end;
end.
IncMonth should work with a TDateTime:
function IncMonth ( const StartDate : TDateTime {; NumberOfMonths : Integer = 1} ) : TDateTime;
Keep in mind a TDate is really just a TDateTime that by convention your ignore the fraction on.
Based on your pseudocode:
procedure SetNewTime(aDateTime: TDateTime; aMonths: Integer);
var
lSystemTime: TSystemTime;
begin
DateTimeToSystemTime(aDateTime, lSystemTime);
Inc(lSystemTime.wMonth, aMonths);
setSystemTime(lSystemTime);
end;
setSystemTime uses UTC time, so you have to adjust for your time zone. The bias is the number of minutes your machine's timezone differs from UTC. This adjusts the date properly on my system:
procedure SetNewTime(aDateTime: TDateTime; aMonths: Integer);
var
lSystemTime: TSystemTime;
lTimeZone: TTimeZoneInformation;
begin
GetTimeZoneInformation(lTimeZone);
aDateTime := aDateTime + (lTimeZone.Bias / 1440);
DateTimeToSystemTime(aDateTime, lSystemTime);
Inc(lSystemTime.wMonth, aMonths);
setSystemTime(lSystemTime);
end;
There isn't enough information to provide a definitive answer to your question.
Consider what you would want to happen if the day of the current month doesn't exist in your future month. Say, January 31 + 1 month. (7 months of the year have 31 days and the rest have fewer.) You have the same problem if you increment the year and the starting date is February 29 on a leap year. So there can't be a universal IncMonth or IncYear function that will work consistantly on all dates.
For anyone interested, I heartily recommend Julian Bucknall's article on the complexities that are inherent in this type of calculation
on how to calculate the number of months and days between two dates.
The following is the only generic date increment functions possible that do not introduce anomolies into generic date math. But it only accomplishes this by shifting the responsibility back onto the programmer who presumably has the exact requirements of the specific application he/she is programming.
IncDay - Add a or subtract a number of days.
IncWeek - Add or subtract a number of weeks.
But if you must use the built in functions then at least be sure that they do what you want them to do. Have a look at the DateUtils and SysUtils units. Having the source code to these functions is one of the coolest aspects of Delphi. Having said that, here is the complete list of built in functions:
IncDay - Add a or subtract a number of days.
IncWeek - Add or subtract a number of weeks.
IncMonth - Add a or subtract a number of months.
IncYear - Add a or subtract a number of years.
As for the second part of your question, how to set the system date & time using a TDatetime, the following shamelessly stolen code from another post will do the job once you have a TDatetime that has the value you want:
procedure SetSystemDateTime(aDateTime: TDateTime);
var
lSystemTime: TSystemTime;
lTimeZone: TTimeZoneInformation;
begin
GetTimeZoneInformation(lTimeZone);
aDateTime := aDateTime + (lTimeZone.Bias / 1440);
DateTimeToSystemTime(aDateTime, lSystemTime);
setSystemTime(lSystemTime);
end;

Resources