Watch, Follow, &
Connect with Us
Public Report
Report From: Delphi-BCB/RTL/Delphi/Date - Time    [ Add a report in this area ]  
Report #:  3820   Status: Closed
HoursBetween fails to return the good value
Project:  Delphi Build #:  6.240
Version:    9.0 Submitted By:   Iman Crawford
Report Type:  Basic functionality failure Date Reported:  3/16/2003 8:58:06 PM
Severity:    Infrequently encountered problem Last Updated: 3/20/2012 2:24:39 AM
Platform:    All platforms Internal Tracking #:   161567
Resolution: Fixed (Resolution Comments) Resolved in Build: : 15.0.3729.28755
Duplicate of:  None
Voting and Rating
Overall Rating: (3 Total Ratings)
4.67 out of 5
Total Votes: 5
Description
This has also been reported for BCB/6.0, report #3526.  I just copied the same report here.

HoursBetween does not return the correct value of HoursBetween in some cases.

By analysing the behaviour of the functions involved in returning the number of hours, i found that the problem seems to be localised on the Trunc function (called in HoursBetween) returning 999 as the truncated value of 1000.000008, calculated by the Hourspan function.
Steps to Reproduce:
Compile and run the attached program.

See the following output.

FAIL #2
FAIL

FAIL #2 indicates that the expected value of 1000 was not seen from HoursBetween.

program Test;

{$APPTYPE CONSOLE}

uses
  SysUtils, DateUtils;

var
  datebeg, dateEnd: TDateTime;
  RetVal: Integer;
  Counter: Integer = 0;

begin
  datebeg := EncodeDateTime(2003,1,1,0,0,0,0);
  dateend := EncodeDateTime(2003,2,11,15,0,0,0);
  RetVal := HoursBetween(datebeg,dateend);
  if RetVal = 999 then
    Inc(Counter)
  else
    WriteLn('FAIL #1');

  datebeg := EncodeDateTime(2003,1,1,0,0,0,0);
  dateend := EncodeDateTime(2003,2,11,16,0,0,0);
  RetVal := HoursBetween(datebeg,dateend);
  if RetVal = 1000 then
    Inc(Counter)
  else
    WriteLn('FAIL #2');

  datebeg := EncodeDateTime(2003,1,1,0,0,0,0);
  dateend := EncodeDateTime(2003,2,11,17,0,0,0);
  RetVal := HoursBetween(datebeg,dateend);
  if RetVal = 1001 then
    Inc(Counter)
  else
    WriteLn('FAIL #3');

  if Counter = 3 then
    WriteLn('PASS')
  else
    WriteLn('FAIL');
end.
Workarounds
None
Attachment
None
Comments

Iman Crawford at 3/16/2003 9:28:59 PM -
Correction, the number being truncated is 999.999999999942 instead of 1000.000008.  So the trunc function is working correctly, but HoursBetween returns the wrong number of hours.  One may look at using SimpleRoundTo with enough significant digits so milleseconds are not affected.  I assumed 9 in the following workaround, not sure how many are needed to leave milleseconds unaffected for TDateTime calculations.

function HoursBetween(const ANow, AThen: TDateTime): Int64;
begin
  Result := Trunc(SimpleRoundTo(HourSpan(ANow, AThen), -9));
end;

Iman

David Marcus at 3/20/2003 6:35:36 PM -
Delphi 7 does the same.

John Herbster at 1/25/2006 5:59:17 PM -

Given that the FormatDateTime, and DateTimeToStr and related functions do a good job of converting the internal TDateTime values into display values, when these internal values are generated as days and true fractions of a day. The complimentary Str-to-DateTime values also work well.

So our methods of doing date-time difference arithmetic must be compatible with the above functions.  

After some experimentation, I found that the date-time to string functions work by first doing a *round to the nearest millisecond-of-a-day*. Then they divide and remainder to get first the milliseconds, then seconds, then minutes, then hours, and then days.

The half millisecond in the following calc will duplicate the effect of the rounding in the date-time to string functions and make the results compatible with what the human user might expect from hand calculations on date and time variables.

So here is what I recommend:

function MinutesBetween_JH(const ANow, AThen: TDateTime): LongInt;
const HalfMillisecond = 0.5/(24*60*60*1000);
var M1, M2: integer;
begin
    M1 := trunc((AThen + HalfMillisecond)*(24*60));
    M2 := trunc((ANow  + HalfMillisecond)*(24*60));
    Result := M2 - M1;
end;

The other differences for days, hours, seconds, and milliseconds can be done in a very similar way.  Only the multiplier (24*60) must be changed to 1, 24, (24*60*60), and (24*60*60*1000), respectively. Further, for the seconds and milliseconds differences, the type of M1, M2, and Result should be changed to type Int64.

I have not investigated the requirements for values of TDateTime variables less than zero.

--JohnH

John Herbster at 12/11/2006 11:16:54 AM -
Suggested fixes for the DateUtils Unit

Note that the SysUtils.DateTimeToTimeStamp function is the common function used as the basis for DateToStr, TimeToStr, DateTimeToStr, and FormatDateTime.

This, DateTimeToTimeStamp, function decodes any TDateTime variable into the TTimeStamp record's integer Time {Number of milliseconds since midnight} and Date {One plus number of days since 1/1/0001}.

I suggest that the subject problem of this QC report and the multitude of other problems in the DateUtils module could be fixed by replacing every use of the trunc function on TDateTime variables in DateUtils with the use of the DateTimeToTimeStamp function.  

At the same time, the in-code documention of all of the DateUtils functions be revised to clarify the action of these functions so that the programmer could know without testing if MinutesBetween(<01:02:03.004>,<01:03:02.004>) would return 0 or 1; if MinutesBetween(<01:02:03.004>,<01:03:03.003>) would return 0 or 1; and what sign is given to the result.

Possibly DateUtils problems:
3240 (C) EndOfADay long format returns value 1 month out
3820 (O) HoursBetween fails to return the good value
4430 (O) Errors using MinutesBetween function
5953 (O) Some functions that use TFormatSettings are not independant of SysLocale & thread's locale
9184 (D) Incorrect Value returned by MinutesBetween function in the DateUtils Unit
27191 (O) DateTime is loosing precision when accessed as Double from DateTime utils functions
36551 (C) TryEncodeDateTime
37174 (O) CompareDate function in DateUtils.pas use Trunc and can wrong compare  

--JohnH, 2006-12-11

John Herbster at 1/15/2008 5:04:08 AM -

After a lot thought and testing, I filed the QC report #56957, titled "A Fix for DateUtils Date-Time Compare Functions", with sources for a program which tests suggested changes to the DateUtils module.  For the details, see that report. --JohnH

Server Response from: ETNACODE01