How do I work around Delphi's inability to accurately handle datetime manipulations?
delphi, delphi-2010, time
Solution
Given
a := StrToTime('7:00');
b := StrToTime('17:30');
ShowMessage(FloatToStr(a));
ShowMessage(FloatToStr(b));
your code, using `MinutesBetween`, effectively does this:
ShowMessage(IntToStr(trunc(MinuteSpan(a, b)))); // Gives 629
However, it might be better to round:
ShowMessage(IntToStr(round(MinuteSpan(a, b)))); // Gives 630
What is actually the floating-point value?
ShowMessage(FloatToStr(MinuteSpan(a, b))); // Gives 630
so you are clearly suffering from traditional floating-point problems here.
Update:
The major benefit of `Round` is that if the minute span is very close to an integer, then the rounded value will guaranteed be that integer, while the truncated value might very well be the preceding integer.
The major benefit of `Trunc` is that you might actually want this kind of logic: Indeed, if you turn 18 in five days, legally you are still not allowed to apply for a Swedish driving licence.
So you if you'd like to use `Round` instead of `Trunc`, you can just add
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
begin
Result := Round(MinuteSpan(ANow, AThen));
end;
to your unit. Then the identifier `MinutesBetween` will refer to this one, in the same unit, instead of the one in `DateUtils`. The general rule is that the compiler will use the function it found latest. So, for instance, if you'd put this function above in your own unit `DateUtilsFix`, then
implementation
uses DateUtils, DateUtilsFix
will use the new `MinutesBetween`, since `DateUtilsFix` occurss to the right of `DateUtils`.
Update 2:
Another plausible approach might be
function MinutesBetween(const ANow, AThen: TDateTime): Int64;
var
spn: double;
begin
spn := MinuteSpan(ANow, AThen);
if SameValue(spn, round(spn)) then
result := round(spn)
else
result := trunc(spn);
end;
This will return `round(spn)` is the span is within the fuzz range of an integer, and `trunc(spn)` otherwise.
For example, using this approach
07:00:00 and 07:00:58
will yield 0 minutes, just like the original `trunc`-based version, and just like the Swedish Trafikverket would like. But it will not suffer from the problem that triggered the OP's question.
Problem
I am new to Delphi (been programming in it for about 6 months now). So far, it's been an extremely frustrating experience, most of it coming from how bad Delphi is at handling dates and times. Maybe I think it's bad because I don't know how to use TDate and TTime properly, I don't know. Here is what is happening on me right now : ``` // This shows 570, as expected ShowMessage(IntToStr(MinutesBetween(StrToTime('8:00'), StrToTime('17:30')))); // Here I would expect 630, but instead 629 is displayed. WTF!? ShowMessage(IntToStr(MinutesBetween(StrToTime('7:00'), StrToTime('17:30')))); ``` That's not the exact code I use, everything is in variables and used in another context, but I think you can see the problem. Why is that calculation wrong? How am I suppose to work around this problem?