This is a known issue in older versions of the DateUtils
unit, which was first introduced in Delphi/C++Builder 6. The issue lasted for several years until it was finally fixed in Delphi/C++Builder XE.
TDateTime
is essentially just a double
, where the date is stored in the integral portion and the time is stored in the fractional portion. As such, it is subject to approximate representations and rounding.
In your example, dtEnter
is 42703.0340277778
and dtExit
is 42703.0347222222
.
The span between two TDateTime
values is calculated using simple floating-point math:
function SpanOfNowAndThen(const ANow, AThen: TDateTime): TDateTime;
begin
if ANow < AThen then
Result := AThen - ANow
else
Result := ANow - AThen;
end;
In your example, SpanOfNowAndThen(dtEnter, dtExit)
is 0.000694444439432118
.
In the case of the MinutesBetween()
function, prior to XE it would call MinuteSpan()
, which returns a Double
that is the result of SpanOfNowAndThen()
multiplied by the MinsPerDay
constant, and then it would truncate off the fractional portion to produce the final integer:
function MinuteSpan(const ANow, AThen: TDateTime): Double;
begin
Result := MinsPerDay * SpanOfNowAndThen(ANow, AThen);
end;
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
Result := Trunc(MinuteSpan(ANow, AThen));
end;
In your example, MinuteSpan()
produces a decimal value that is slightly less than 1.0
(0.99999999278225
, to be exact), which becomes 0 when the decimal is truncated off.
In XE, many of the DateUtils
functions were re-written to use more reliable calculations that are not based on floating-point math. Although MinuteSpan()
is still the same, MinutesBetween()
no longer uses MinuteSpan()
. Instead, it now converts the two TDateTime
values to milliseconds (which is lossless since TDateTime
has millisecond precision), subtracts the values, and then divides the absolute value of the difference by a constant number of milliseconds per minute:
function DateTimeToMilliseconds(const ADateTime: TDateTime): Int64;
var
LTimeStamp: TTimeStamp;
begin
LTimeStamp := DateTimeToTimeStamp(ADateTime);
Result := LTimeStamp.Date;
Result := (Result * MSecsPerDay) + LTimeStamp.Time;
end;
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
Result := Abs(DateTimeToMilliseconds(ANow) - DateTimeToMilliseconds(AThen))
div (MSecsPerSec * SecsPerMin);
end;
In your example, DateTimeToMilliseconds(dtEnter)
is 63616063740000
and DateTimeToMilliseconds(dtExit)
is 63616063800000
, so the difference is 60000
ms, which is exactly 1
minute.
For versions prior to XE, you will have to implement a similar fix manually in your own code. This is discussed in various online blogs, such as:
How do I work around Delphi's inability to accurately handle datetime manipulations?
Accurate Difference Between Two TDateTime Values