2

(Migrated from CodeReview)

I am experimenting and trying to understand Java Time better. My code is not working as expected and I want to ask the reason, probably because there is a misunderstandment from my side about JSR-310 time zone handling.

I have a timestamp string to parse with DateTimeFormatter

String text = "2019-08-28T10:39:57+02:00";
DateTimeFormatter ISO_RFC_3339_PARSER = DateTimeFormatter.ISO_OFFSET_DATE_TIME.withZone(ZoneId.of("Europe/Paris"));

The above value is generated when the server's local time is 10:39 AM in Central Europe. UTC time is 8:39 in the morning because of the summer time zone. You can do your own math to find what time it is in your zone.

The following test works when the time zone of the computer is the same as displayed in the timestamp (+2)

@Test
public void testTimestamp()
{
    LocalDateTime time = ZonedDateTime.from(ISO_RFC_3339_PARSER.parse(text)).toLocalDateTime();

    errorCollector.checkThat(time.getYear(), is(2019));
    errorCollector.checkThat(time.getMonthValue(), is(8));
    errorCollector.checkThat(time.getDayOfMonth(), is(28));
    errorCollector.checkThat(time.getHour(), is(10));
    errorCollector.checkThat(time.getMinute(), is(39));
    errorCollector.checkThat(time.getSecond(), is(57));

}

I have tried to tinker with the input string to better understand how Java Time ajusts time zones. But results are surprisingly wrong!

I tried to change the time zone in the input string repeatedly, so that the test should fail. For example if I change the string to 2019-08-28T10:39:57+08:00 it means it is 2AM in Paris. But the above test code continues to pass when checking for the hour of day. I.e. in the resulting LocalDateTime the time of the day is still 10AM.

Question

Why is my code still returning 10 as local time of day regardless of the fact that I am constantly changing its time zone in the source string?

Then what is the correct way to parse a RFC 3339 string (which represents an instant in the embedded offset) and convert it into a LocalDateTime object with regards of possible zone adjustments? Assume the machine is running in CE[S]T time zone.

Environment

I need to compare a timestamp with computer time and check whether it is not too old. That means if a US time is evaluated in Europe it may not be "too old".

Community
  • 1
  • 1
usr-local-ΕΨΗΕΛΩΝ
  • 26,101
  • 30
  • 154
  • 305

2 Answers2

1
    String text = "2019-08-28T10:39:57+02:00";
    OffsetDateTime time = OffsetDateTime.parse(text);
    System.out.println("Hour of day is " + time.getHour());

Output is (as you had expected):

Hour of day is 10

Try a different offset:

    String text = "2019-08-28T10:39:57+08:00";

Hour of day is 10

So still the same and still the hour given in the string. So this is where the 10 comes from. I have chosen a slightly simpler code example from yours, but it demonstrates the same behaviour as your code, there is no difference, and will also suffice for the explanation.

One point to note is that the code does not depend on your JVM’s time zone setting (which you report to be Europe/Paris). That’s good. It means that if we trust the code to be correct, we can also trust it to run correctly on all JVMs in all time zones.

In your code you converted to LocalDateTime by calling toLocalDateTime(). Very often there isn’t any good reason for this conversion since the OffsetDateTime or ZonedDateTime fulfills the purpose nicely. What the conversion does is it retains the date and hour of day (the wall clock time) from the OffsetDateTime or ZonedDateTime and discards the offset or time zone information. So if the hour of day was 10 before the conversion, it will also be 10 after the conversion. One possible source of your confusion may be if you had expected a conversion to your local time (Europe/Paris). No such conversion is performed. Local in java.time names means: without time zone or offset. It has been argued that the use of the word Local for this meaning is misleading (it’s historic in the sense that it has been taken over from Joda-Time, the forerunner of java.time).

Then what is the correct way to parse a RFC 3339 string (which represents an instant in the embedded offset) and convert it into a LocalDateTime object with regards of possible zone adjustments? Assume the machine is running in CE[S]T time zone.

Parsing already works correctly for you. You may not need the conversion. You can compare directly. The isBefore, isEqual and isAfter methods of OffsetDateTime and ZonedDateTime do take different offsets or time zones into account when comparing and will give you the results you need. If you do want to convert:

    String text = "2019-08-28T10:39:57+08:00";
    OffsetDateTime time = OffsetDateTime.parse(text);
    System.out.println("Hour of day is " + time.getHour());

    ZoneId myTimeZone = ZoneId.of("Europe/Paris");
    ZonedDateTime timeInFrance = time.atZoneSameInstant(myTimeZone);
    System.out.println("Hour of day in France is " + timeInFrance.getHour());
Hour of day is 10
Hour of day in France is 4

Again, you can convert to LocalDateTimeand preserve the hour of 4, but I don’t see why you should.

About my simplification of your code: Your RFC 3339 string contains an offset from UTC (+02:00), not a time zone (like Europe/Paris or Europe/Rome). So using ZonedDateTime for parsing it is overkill; OffsetDateTime is a better fit. The string format agrees with ISO 8601. java.time classes parse the most common ISO 8601 format variants out of the box without any explicit formatter, so we don’t need a formatter here. As an aside, the zone you assigned to you formatter made no difference since the offset in the string was used.

Link: Wikipedia article: ISO 8601

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
1

Getting LocalDateTime from ZonedDateTime simply cuts of Time Zone. Its value is of no relevance. What you need to do is to convert your ZonedDateTime from zone +8 to zone +2, that will change the time accordingly. Then you can get the LocalDateTime from your modified ZonedDateTime to get the desired effect. Here is an example that will demonstrate it:

private static void testDateParsing() {
    ZonedDateTime zdt = ZonedDateTime.parse("2019-08-28T10:39:57+08:00", DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssZZZZZ"));
    ZonedDateTime modifiedZdt = zdt.withZoneSameInstant(ZoneId.systemDefault());
    System.out.println(zdt);
    System.out.println(modifiedZdt);
    System.out.println(LocalDateTime.from(zdt));
    System.out.println(LocalDateTime.from(modifiedZdt));
}

The output is:

2019-08-28T10:39:57+08:00
2019-08-28T05:39:57+03:00[Asia/Jerusalem]
2019-08-28T10:39:57
2019-08-28T05:39:57

Note that my local time zone is +03 and not +02 as it was for you.

Michael Gantman
  • 7,315
  • 2
  • 19
  • 36