4

I'm in an Android project that requires saving a file with TDateTime type (Delphi). I have my date in milliseconds, but I don't know how to convert milliseconds to TDateTime.

I have something like this:

Date dateInMillis = new Date(System.currentTimeMillis());
double dateInDouble = ???;

I'll be glad for any tips that can help me to resolve this.

K-ballo
  • 76,488
  • 19
  • 144
  • 164
Daniel S.
  • 2,169
  • 2
  • 17
  • 19

3 Answers3

9

Delphi's TDateTime measures time in days. Java follows the Unix standard and measures in milliseconds. To convert between the two you need to scale by the number of milliseconds in a day, 86400000.

The other difference is that the two systems use a different epoch. The Unix epoch, as used by Java, is 00:00, 1 Jan 1970. The Delphi epoch is 00:00, 30 December 1899. The Unix epoch, represented as a Delphi TDateTime is 25569.

So, to convert from milliseconds from the Unix epoch, to days from the Delphi epoch you perform the following calculation:

double delphiDateTime = unixMillis/86400000.0 + 25569.0;
David Heffernan
  • 572,264
  • 40
  • 974
  • 1,389
  • That´s what I did a few minutes ago! I need wait 8 hours to Answer my own question but this is exactly what I did! Thanks David – Daniel S. Sep 18 '13 at 20:42
1

More recently post Java 8 with the new date time classes, the following should work:

LocalDateTime localDateTime = LocalDateTime.of(1899, 12, 30, 0, 0);//Delphi date EPOCH time start
double fraction = val % 1;
long intPart = (long) (val - fraction);
localDateTime = localDateTime.plus(intPart, ChronoUnit.DAYS);
localDateTime = localDateTime.plus((long) (24*60*60*1000 * fraction), ChronoUnit.MILLIS); //fraction is a fraction of the time of day
Deepak
  • 2,459
  • 1
  • 17
  • 15
1

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.

Table of date-time types in Java, both modern and legacy

Basil Bourque
  • 218,480
  • 72
  • 657
  • 915