tl;dr
Use modern java.time classes that years ago supplanted Date
class.
Duration duration =
Duration.between(
LocalDate.of( 1899 , Month.DECEMBER , 30 ).atStartOfDay( ZoneOffset.UTC ).toInstant() , // Epoch reference moment used by Delphi.
Instant.now() // Current moment as seen in UTC.
);
double temp =
// Get a whole number of days (integer ) + a decimal fraction of partial day = a `double` number = the `TDateTime` type in Delphi.
duration.toDays() +
(
( double ) duration.minusDays( duration.toDays() ).toMillis() // Milliseconds in our partial day.
/
( double ) Duration.ofDays( 1 ).toMillis() // Milliseconds in a full day, a generic 24-hour day.
);
double r = Math.floor( temp * 1000 ) / 1000; // Truncate to third decimal place to represent a resolution of milliseconds, the limit of `TDateTime` type in Delphi.
java.time
Use only the modern java.time classes in Java, never the legacy classes such as java.util.Date
or java.sql.Date
.
Capture the current moment as seen in UTC.
Instant now = Instant.now() ;
Because of some twisted history, as its epoch reference for its TDateTime
class Delphi uses the first moment of 1899-12-30 presumably in UTC. For the date portion, use LocalDate
.
LocalDate delphiEpochDate = LocalDate.of( 1899 , Month.DECEMBER , 30 ); // No, not the 31st, the 30th.
epochDate.toString(): 1899-12-30
As a habit, let java.time determine the first moment of the day, as it is not always 00:00 on all dates in all zones.
Instant delphiEpochMoment = delphiEpochDate.atStartOfDay( ZoneOffset.UTC ).toInstant();
odt.toString(): 1899-12-30T00:00Z
Delphi uses a terrible method of tracking time as inherited from spreadsheets: Using a double
floating-point fractional number.
The integer portion represents full days, generic 24-hour long days that ignore the anomalies of political time. For such elapsed time, we use Duration
.
Duration d = Duration.between( delphiEpochMoment , now ) ;
long days = d.toDays() ; // Generic 24-hour long days.
Next, get the fractional part that represents a portion of a 24-hour day. First we subtract the amount of the full days, to leave us with a partial day.
Duration partialDay = d.minusDays( days ) ;
Then divide the partial amount by the length of a full day. We will use a resolution of milliseconds rather than the nanoseconds capability of Duration
, as it seems Delphi is limited to milliseconds.
double millisOfPartialDay = partialDay.toMillis() ;
double millisOfFullDay = Duration.ofDays( 1 ).toMillis() ;
double tempResult = ( millisOfPartialDay / millisOfFullDay ) ;
We should truncate results to milliseconds. For truncating a double
, see this Answer. And we should add our whole number of days.
double tempResult = days + ( millisOfPartialDay / millisOfFullDay ) ;
double result = Math.floor( tempResult * 1000 ) / 1000 ;
Putting that all together.
Instant now = Instant.now();
LocalDate delphiEpochDate = LocalDate.of( 1899 , Month.DECEMBER , 30 ); // No, not the 31st, the 30th.
Instant delphiEpochMoment = delphiEpochDate.atStartOfDay( ZoneOffset.UTC ).toInstant();
Duration d = Duration.between( delphiEpochMoment , now );
long days = d.toDays(); // Generic 24-hour long days.
Duration partialDay = d.minusDays( days );
double millisOfPartialDay = partialDay.toMillis();
double millisOfFullDay = Duration.ofDays( 1 ).toMillis();
double tempResult = days + ( millisOfPartialDay / millisOfFullDay );
double result = Math.floor( tempResult * 1000 ) / 1000; // Truncate to third decimal place to represent a resolution of milliseconds.
System.out.println( "delphiEpochMoment = " + delphiEpochMoment );
System.out.println( "d = " + d );
System.out.println( "tempResult = " + tempResult );
System.out.println( "result = " + result );
delphiEpochMoment = 1899-12-30T00:00:00Z
d = PT1056815H56M10.613011S
tempResult = 44033.99734505787
result = 44033.997
Caveat: I have not tested this code. And I do not use Delphi. So buyer beware: this code is worth every penny you paid for it.
Avoid LocalDateTime
Beware: Do not use LocalDateTime
to represent a moment. This class represents a date with time-of-day, but lacks the context of a time zone or offset-from-UTC. For example, take the value of Noon on January 23rd of 2021. Would that be noon in Tokyo Japan? Or noon in Toulouse France? Or noon in Toledo Ohio US? Those would be three very different moments, several hours apart. We do not know which is intended if the time zone is lacking.
You could get away with LocalDateTime
in this specific problem of Delphi time-tracking because Delphi (presumably) uses UTC, and UTC uses generic 24-hour-long days as does LocalDateTime
. But using LocalDateTime
is conceptually not fit for this problem, as we need moments for here.
