delphidelphi-11-alexandria

Converting ticks to a TDatetime in Delphi


In a Delphi (11.3) VCL program, I'm using a method to convert 638563016295520210 ticks to a TDateTime. I know Delphi works with a TDateTime from 12/30/1899 and I tried using TTimeSpan.FromTicks(), but without any success.

Here's the function I'm calling:

function TicksToDateTime(Ticks: Int64): TDateTime;  // Ticks input is 638563016295520210
const
  TicksPerDay = 864000000000; // Number of ticks in a day
var
  Days, RemainingTicks, FractionOfDay: Int64;
  BaseDate: TDateTime;
begin
  BaseDate := EncodeDate(1, 1, 1); // January 1, 0001
  Days := Ticks div TicksPerDay; // Calculate the number of days (638563016295520210 div 864000000000)
  RemainingTicks := Ticks mod TicksPerDay; // Calculate the Remaining Ticks
  FractionOfDay := RemainingTicks div TicksPerDay; // Convert Remaining Ticks to Fraction of a Day
  Result := BaseDate + Days + (RemainingTicks div TicksPerDay); // The Result should equal 07/11/2024 9:33 AM EST.
end;

I'm making the call via:

MyDate := TicksToDateTime(iTicks);

And then formatting the TDateTime result. The function call is giving me a date result of 05/10/0203. How do I get the desired result of 07/11/2024 9:33 AM?


Solution

  • TDateTime is implemented using a floating-point Double value, where the date is in the integral portion and the time is in the fractional portion.

    div performs integer division, you can't get a fraction from that. You need to use / instead, which performs floating-point division.

    But, in any case... in comments, you said:

    When I input the number of ticks to the datetimetoticks-converter it does convert properly

    That is not what I see happen. When I enter your input value 63856301629552021 into that site, it reports 10/05/0203 18:09:22 (May 10 0203 6:09:22pm), not 07/11/2024 09:33:00 (July 11?, November 7? 2024 9:33am) as you are expecting.

    Which makes sense, as @AmigoJack commented:

    63856301629552021 div 864000000000 = 73907 days = 202 years... Adding 202 years and something to 0001-01-01 is expected to sum up to 0203-01-01 and something.

    (of course, his comment isn't taking months and days of each month into account, but you get the idea.)

    So, either your input is wrong, or your expectation is wrong (probably both).

    According to that site:
    Jul 11 2024 9:33 AM = 638562871800000000
    Nov 07 2024 9:33 AM = 638665687800000000

    Notice your input value is 17 digits, but the values above are both 18 digits, which means your input is missing quite a lot of precision to reach anywhere near the year 2024.

    When I use the following code and correct input values, I get the TDateTime value you are expecting:

    uses
      System.DateUtils, System.TimeSpan;
    
    function TicksToDateTime(Ticks: Int64): TDateTime;
    begin
      Result := IncMillisecond(
        EncodeDate(1, 1, 1),
        Trunc(TTimeSpan.FromTicks(Ticks).TotalMilliseconds)
      );
    end;
    
    TicksToDateTime(63856301629552021 ) // returns May 10 0203 6:09:22.955 PM
    TicksToDateTime(638562871800000000) // returns Jul 11 2024 9:33:00.000 AM
    TicksToDateTime(638665687800000000) // returns Nov 07 2024 9:33:00.000 AM
    

    If you don't want to use TTimeSpan, a minor tweak to your existing code will yield similar1 results:

    function MyTicksToDateTime(Ticks: Int64): TDateTime;
    const
      TicksPerDay = 864000000000;
    var
      BaseDate: TDateTime;
      Days: Int64;
      RemainingTicks: Int64;
      FractionOfDay: Double; // <--
    begin
      BaseDate := EncodeDate(1, 1, 1);
      Days := Ticks div TicksPerDay;
      RemainingTicks := Ticks mod TicksPerDay;
      FractionOfDay := RemainingTicks / TicksPerDay; // <--
      Result := BaseDate + Days + FractionOfDay;
    end;
    
    TicksToDateTime(63856301629552021 ) // returns May 11 0203 5:50:37.045 AM
    TicksToDateTime(638562871800000000) // returns Jul 11 2024 9:33:00.000 AM
    TicksToDateTime(638665687800000000) // returns Nov 07 2024 9:33:00.000 AM
    

    1: notice that in this case, 63856301629552021 returns May 11 0203 5:50:37.045 AM instead of May 10 0203 6:09:22.955 PM as with TTimeSpan. I'm not sure why the difference, and I don't care to figure it out right now. It has something to do with how FractionOfDay is being (mis?)calculated. The other two values return the correct result, though.