charts icon indicating copy to clipboard operation
charts copied to clipboard

Inconsistent dates when using setters taking a java.time.Instant

Open ingokegel opened this issue 6 years ago • 0 comments

I just migrated from Charts 3.2.1 and wanted to convert to the setters that take a java.time.Instant instead of a java.util.Date - not least because all the setters with java.util.Date are deprecated now.

However, the logic for the converting a java.time.Instant to a Highchart time is not consistent with the previous behavior and other parts of the API. All the corresponding setters call Util::toHighchartsTS(Instant) which just returns the epoch milliseconds. Setters taking a java.util.Date call Util::toHighchartsTS(Date) which returns

date.getTime() - date.getTimezoneOffset() * 60000;

java.util.Date does not have a time zone, Date::getTimezoneOffset() just refers to the default time zone. Just like java.time.Instant, its getTime() method returns the same value as Instant::toEpochMilli(), namely the epoch in milliseconds. Adding the time zone offset is important, though, because Highchart will subtract it again to convert to UTC.

What happens now is this:

  1. With the Date-based setters, the chart will show server times. In my opinion, this is the desirable behavior and this is how it worked before 4.0.
  2. With the Instant-based setters, the chart will show UTC times. However, you cannot implement this consistently, because there are still (undeprecated) Date-based setters without an Instant-based equivalent, such as Axis::setMax/setMin and those are still transmitted with the timezone offset.

To solve this issue, the implementation of Util::toHighchartsTS(Instant) should be changed to

public static long toHighchartsTS(Instant date) {
    long epochMilli = date.toEpochMilli();
    return epochMilli + TimeZone.getDefault().getOffset(epochMilli);
}

To convince yourself of the relationships between Date and Instant, run the following code:

Instant instant = Instant.now();
Date date = Date.from(instant);
long epochMilli = instant.toEpochMilli();
long time = date.getTime();

System.out.println("instant epochMills = " + epochMilli);
System.out.println("date time = " + time);
System.out.println("equals " + (epochMilli == time));

long instantPlusOffset = epochMilli + TimeZone.getDefault().getOffset(epochMilli);
System.out.println("instant + offset = " + instantPlusOffset);
long datePlusOffset = time - date.getTimezoneOffset() * 60000;
System.out.println("date + offset = " + datePlusOffset);

System.out.println("equals " + (datePlusOffset == instantPlusOffset));

ingokegel avatar Nov 16 '19 17:11 ingokegel