14

Why is it possible to pass a LocalDateTime object into the ZonedDateTime.from() method, if it's not possible to parse a simple DateTime, as follows?

@Test
public void test() throws Exception {
    GregorianCalendar.from(ZonedDateTime.from(LocalDateTime.parse("2016-08-31T23:00:59")));
}

Result:

java.time.DateTimeException: Unable to obtain ZonedDateTime from TemporalAccessor: 2016-08-31T23:00:59 of type java.time.LocalDateTime
    at java.time.ZonedDateTime.from(ZonedDateTime.java:565)
Sae1962
  • 1,122
  • 15
  • 31
membersound
  • 81,582
  • 193
  • 585
  • 1,120
  • 2
    Are you asking how to convert to a `GregorianCalendar`, or are you asking why it allows you to pass an implementation of the `TemporalAccessor` interface and only complains about it in runtime? – RealSkeptic Aug 08 '16 at 07:34
  • 2
    GeorgianCalendar requires a timezone so actually you can do `GregorianCalendar.from(ZonedDateTime.parse("2016-08-31T23:00:59+00:00"));` providing a timezone. `ZonedDateTime.from` can fail for different types of input and that mentioned in javadoc. – vsminkov Aug 08 '16 at 07:34
  • 2
    The diagram showing conversions from old date-time classes to java.time in [my Answer](http://stackoverflow.com/a/36639155) to the [Convert java.util.Date to what “java.time” type?](http://stackoverflow.com/q/36639154) may be useful. Also find example code converting in both directions. – Basil Bourque Aug 08 '16 at 16:23
  • @BasilBourque that's a really great diagram! – membersound Aug 09 '16 at 06:00

2 Answers2

12

Why is it possible to pass a LocalDateTime object into the ZonedDateTime.from() method if it's not possible to parse a simple datetime?

Both LocalDateTime and ZonedDateTime implement the interface called Temporal, which extends TemporalAccessor.

LocalDateTime is datetime without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30

ZonedDateTime is datetime with a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30+01:00 Europe/Paris.

Now, if you see the implementation of from(TemporalAccessor temporal) method that is expecting the TemporalAccessor… Both LocalDateTime and ZonedDateTime implements the method of super interface TemporalAccessor (Temporal extends TemporalAccessor), which is normal (i.e. has a polymorphic behavior) to allow us to pass both the local as well as zoned datetime.

Code:

public static ZonedDateTime from(final TemporalAccessor temporal) {
    if (temporal instanceof ZonedDateTime) {
        return (ZonedDateTime) temporal;
    }

    try {
        final ZoneId zone = ZoneId.from(temporal);

        if (temporal.isSupported(INSTANT_SECONDS)) {
            long epochSecond = temporal.getLong(INSTANT_SECONDS);
            int nanoOfSecond = temporal.get(NANO_OF_SECOND);

            return create(epochSecond, nanoOfSecond, zone);
        } else {
            LocalDate date = LocalDate.from(temporal);
            LocalTime time = LocalTime.from(temporal);

            return of(date, time, zone);
        }
    } catch (final DateTimeException exception) {
        throw new DateTimeException(
            "Unable to obtain ZonedDateTime from TemporalAccessor: " +
            temporal + " of type " + temporal.getClass().getName(),
            exception
        );
    }
}

Now, we come to your problem.

You are passing LocalDateTime.parse("2016-08-31T23:00:59") to the from(TemporalAccessor temporal) method. So, the temporal is not an instance of ZonedDateTime, it comes ZoneId zone = ZoneId.from(temporal);.

So, your are getting the error since the LocalDateTime does not contain the timezone.

How to solve the issue?

Use the Zone ID with the ZonedDateTime:

LocalDateTime ldt = LocalDateTime.parse("2016-08-31T23:00:59");
ZoneId zoneId = ZoneId.of("Europe/Paris");
ZonedDateTime zdt = ldt.atZone(zoneId);
GregorianCalendar gc = GregorianCalendar.from(zdt);

with JUnit test

@Test
public void test() throws Exception {
    ZoneId zoneId = ZoneId.of("Europe/Paris");
    GregorianCalendar.from(LocalDateTime.parse("2016-08-31T23:00:59").atZone(zoneId));
}
informatik01
  • 16,038
  • 10
  • 74
  • 104
Unknown
  • 2,037
  • 3
  • 30
  • 47
9

You will have to provide the ZoneId as well. I will have to look up on the behavior as to why this is. I will edit and post as soon as I see it.

LocalDateTime ldt = LocalDateTime.parse("2016-08-31T23:00:59");
GregorianCalendar gc = GregorianCalendar.from(ZonedDateTime.of(ldt, ZoneId.systemDefault()));
System.out.println(gc);

This results:

java.util.GregorianCalendar[time=1472664659000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Calcutta",offset=19800000,dstSavings=0,useDaylight=false,transitions=6,lastRule=null],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2016,MONTH=7,WEEK_OF_YEAR=35,WEEK_OF_MONTH=5,DAY_OF_MONTH=31,DAY_OF_YEAR=244,DAY_OF_WEEK=4,DAY_OF_WEEK_IN_MONTH=5,AM_PM=1,HOUR=11,HOUR_OF_DAY=23,MINUTE=0,SECOND=59,MILLISECOND=0,ZONE_OFFSET=19800000,DST_OFFSET=0]

Edits: Just came across this interesting thread...

The Java 8 java.time.* package is a very strict package. It doesn't allow flexibility between types and inputs - if you want a ZonedDateTime object, you must construct it from an input that has a time zone, a date & a time.

Sae1962
  • 1,122
  • 15
  • 31
Ramachandran.A.G
  • 4,788
  • 1
  • 12
  • 24