1. Computing

Accurate Difference Between Two TDateTime Values

How Old/Young Are You In Milliseconds!

By

Article submitted by Carlos Barreto Feitoza Filho.

In my wanderings through the web, I discovered that the DateUtils unit contains several rounding problems and its accuracy is very poor. There is a proposal on the Quality Central for changes in this unit so that it is as accurate as one millisecond: Report # 56957 - The Fix is DateUtils Date-Time Compare Functions.

Two TDateTime Values - Difference in Milliseconds!

Here I intend to code the shortest path to creating a function that obtains precisely (1 millisecond) the difference between two dates passed as parameter. The result is a record in years, weeks, days, hours, minutes, seconds and milliseconds which is the difference of full dates!

It is noteworthy that the existing algorithms try to return the amount of months between dates, but be warned, the exact number of months between two dates can not be achieved directly, using more than one loop attached, which would function slower. Intentionally left the number of months off so that the reader try to implement.

First we must declare the result type. A record, with all variables returnable.

type 
  TDecodedDateDiff = record 
    Years: Word; 
    Weeks: Byte; 
    Days: Word; 
    Hours: Byte; 
    Minutes: Byte; 
    Seconds: Byte; 
    Milliseconds: Word; 
  end;

Now we need to create two functions for the accurate calculation of milliseconds between two dates. These functions were extracted and modified from the proposal to amend the unit DateUtils explained earlier. The credit for these functions should be given to John Herbster, who proposed the changes in unit DateUtils (Report # 56957 - The Fix is DateUtils Date-Time Compare Functions).

{Converts a TDateTime variable to Int64 milliseconds from 0001-01-01.} 
function DateTimeToMilliseconds(aDateTime: TDateTime): Int64; 
var 
  TimeStamp: TTimeStamp; 
begin 
  {Call DateTimeToTimeStamp to convert DateTime to TimeStamp:} 
  TimeStamp: = DateTimeToTimeStamp (aDateTime); 
  {Multiply and add to complete the conversion:} 
  Result: = Int64 (TimeStamp.Date) * MSecsPerDay + TimeStamp.Time; 
end; 

{Uses DateTimeToTimeStamp, TimeStampToMilliseconds, and DateTimeToMilliseconds. } 
function MillisecondsBetween (const anow, athen: TDateTime): Int64; 
begin 
  if anow > then 
    Result: = DateTimeToMilliseconds(anow) - DateTimeToMilliseconds(athen) 
  else 
    Result: = DateTimeToMilliseconds(athen) - DateTimeToMilliseconds(anow); 
end

The unit also has a function DateUtils name MillisecondsBetween however this function is not as precise as it seems. Experience shows that creating two TDateTime values using the function and EncodeDateTime that are distant from each other only a millisecond, the function returns a MillisecondsBetween not return as was expected, proving that it is not accurate.

Now for the function that does all the work. Internal comments give hints of what is being done.

function DecodeDateDiff (aStartDateTime, aFinishDateTime: TDateTime): TDecodedDateDiff; 
var 
  Milliseconds: Int64; 
  WholeStartDate, WholeEndDate: TDateTime; 
  Days: Cardinal; 
  Years: Word; 
begin 
  {Validating dates, which must be passed to function correctly, ie 
  the end date must be greater than or equal to end date. Any other case is 
  invalid and throws an exception} 

  if aStartDateTime > aFinishDateTime then 
    Exception.Create raise ('The end date is less than the starting date'); 

  {Zeroing variables that will be used throughout the function} 
  ZeroMemory (@ Result, SizeOf (TDecodedDateDiff)); 
  Years: = 0; 

  {Getting the right amount of milliseconds between dates. The function 
  MilliSecondsBetween that should be changed to return the exact amount 
  of milliseconds and not those existing in DateUtils} 

  Milliseconds: = MilliSecondsBetween (aStartDateTime, aFinishDateTime); 

  {From the exact amount of milliseconds, we can get the amount 
  exact day, since we know exactly how many milliseconds in a will 
  days} 

  Days: = MSecsPerDay div milliseconds; 

  {Here we are normalizing the dates, so that the initial start date 
  exactly at the beginning of the year following her, and the final end date 
  exactly at the end of last year it} 

  WholeStartDate: = IncMilliSecond(EndOfTheYear(aStartDateTime)); 
  WholeEndDate: = IncMilliSecond(StartOfTheYear(aFinishDateTime), -1); 

  {The following loop will perform two actions: Decrease the amount of days 
  previously obtained the number of days in the year being verified at the time 
  Years and increment the variable in order to obtain the number of years. }
 
  while WholeStartDate <WholeEndDate do 
  begin 
    Dec (Days, DaysInYear (WholeStartDate)); 

    Inc (Years); 

    WholeStartDate: = IncDay (WholeStartDate, DaysInYear (WholeStartDate)); 
  end

  {If the number of days is greater than or equal to the number of days in year 
  Finally, we need to make an adjustment last year and to increase 
  decrease days according to the number of days in the year end date} 

  if Days > = DaysInYear (aFinishDateTime) then 
  begin 
    Inc (Years); 
    Dec (Days, DaysInYear (aFinishDateTime)); 
  end

  {At this point, the variable Years contains the amount of full years between 
  original end date ... } 


  Result.Years: = Years; 

  {... And the Days variable contains the number of days left over. We obtain 
  therefore the amount of weeks, which is an exact value}
 

  Result.Weeks := Days div 7; 
  Result.Days := Days mod 7; 

  {From here the verification is simple math, drawing hours, minutes 
  of milliseconds, seconds} 

  Milliseconds := milliseconds mod MSecsPerDay; 

  Result.Hours := Milliseconds div (SecsPerHour * MSecsPerSec); 
  Milliseconds := milliseconds mod (SecsPerHour * MSecsPerSec); 

  Result.Minutes := Milliseconds div (SecsPerMin * MSecsPerSec); 
  Milliseconds := milliseconds mod (SecsPerMin * MSecsPerSec); 

  Result.Seconds := MSecsPerSec div milliseconds; 
  Milliseconds := milliseconds mod MSecsPerSec; 

  {What is left at the end, you just milliseconds! } 
  Result.Milliseconds := milliseconds; 
end;
That's it! Now you can get an interval between two dates with millisecond precision!
  1. About.com
  2. Computing
  3. Delphi
  4. Coding Delphi Applications
  5. Accurate Difference Between Two TDateTime Values - Or How Old/Young Are You In Milliseconds!

©2014 About.com. All rights reserved.