Java 8: problemas de DateTimeFormatter e ISO_INSTANT con ZonedDateTime

4 minutos de lectura

avatar de usuario de asieira
asieira

Por lo tanto, espero que este código funcione con el nuevo paquete de fecha/hora de Java 8, ya que todo lo que hace es convertir un ZonedDateTime determinado en una cadena y viceversa utilizando la misma instancia integrada de DateTimeFormatter (ISO_INSTANT):

ZonedDateTime now = ZonedDateTime.now();
System.out.println(ZonedDateTime.parse(
    now.format(DateTimeFormatter.ISO_INSTANT),
    DateTimeFormatter.ISO_INSTANT));

Pero aparentemente no:

Exception in thread "main" java.time.format.DateTimeParseException: Text '2014-09-01T19:37:48.549Z' could not be parsed: Unable to obtain ZonedDateTime from TemporalAccessor: {MilliOfSecond=549, NanoOfSecond=549000000, MicroOfSecond=549000, InstantSeconds=1409600268},ISO of type java.time.format.Parsed
    at java.time.format.DateTimeFormatter.createError(DateTimeFormatter.java:1918)
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1853)
    at java.time.ZonedDateTime.parse(ZonedDateTime.java:597)

Ya he visto esta entrada, pero no me ayudó porque necesito un objeto ZonedDateTime y no uno local y también porque ya tengo 8u20 instalado: No se puede obtener ZonedDateTime de TemporalAccessor usando DateTimeFormatter y ZonedDateTime en Java 8

¿Alguien tiene idea de lo que está pasando aquí?

Avatar de usuario de JodaStephen
jodastephen

los ISO_INSTANT el formateador está documentado aquí – “Este es un formateador de casos especiales destinado a permitir una forma legible por humanos de un Instant”. Como tal, este formateador está diseñado para usarse con un Instant No un ZonedDateTime.

Formateo

Al formatear, ISO_INSTANT puede formatear cualquier objeto temporal que pueda proporcionar ChronoField.INSTANT_SECONDS y ChronoField.NANO_OF_SECOND. Ambas cosas Instant y ZonedDateTime puede proporcionar estos dos campos, por lo que ambos funcionan:

// works with Instant
Instant instant = Instant.now();
System.out.println(DateTimeFormatter.ISO_INSTANT.format(instant));

// works with ZonedDateTime 
ZonedDateTime zdt = ZonedDateTime.now();
System.out.println(zdt.format(DateTimeFormatter.ISO_INSTANT));

// example output
2014-09-02T08:05:23.653Z

análisis

Al analizar, ISO_INSTANT solo producirá ChronoField.INSTANT_SECONDS y ChronoField.NANO_OF_SECOND. Un Instant se puede construir a partir de esos dos campos, pero ZonedDateTime requiere un ZoneId también:

para analizar un ZonedDateTime es esencial que una zona horaria ZoneId está presente. La zona horaria se puede (a) analizar desde la cadena, o (b) especificar al formateador (usando JDK 8u20):

// option a - parsed from the string
DateTimeFormatter f = DateTimeFormatter.ISO_DATE_TIME;
ZonedDateTime zdt = ZonedDateTime.parse("2014-09-02T08:05:23.653Z", f);

// option b - specified in the formatter - REQUIRES JDK 8u20 !!!
DateTimeFormatter f = DateTimeFormatter.ISO_INSTANT.withZone(ZoneId.systemDefault());
ZonedDateTime zdt = ZonedDateTime.parse("2014-09-02T08:05:23.653Z", f);

Consulte la documentación para ISO_ZONED_DATE_TIME, ISO_OFFSET_DATE_TIME y ISO_DATE_TIME (cualquiera de estos tres se puede utilizar para analizar un ZonedDateTime sin especificar withZone()).

Resumen

los ISO_INSTANT formatter es un formateador de casos especiales diseñado para trabajar con Instant. Si está usando un ZonedDateTime debe usar un formateador diferente, como ISO_DATE_TIME o ISO_ZONED_DATE_TIME.

  • Lo extraño de esto es que la Z final en la cadena generada es un indicador de zona horaria para UTC, por lo que la cadena contiene un indicador de zona horaria (en.wikipedia.org/wiki/ISO_8601#Time_zone_designators). Sin embargo, la documentación establece que esto solo funcionará por instantes, por lo que tiene toda la razón. Gracias.

    – asieira

    02/09/2014 a las 13:31

No estoy seguro, pero esto podría ser un error en Java 8. Tal vez tenía la intención de comportarse de esta manera, pero creo que la solución alternativa que voy a proponerle debería ser el comportamiento predeterminado (cuando no hay ZoneId especificado, simplemente tome el valor predeterminado del sistema):

ZonedDateTime now = ZonedDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ISO_INSTANT
    .withZone(ZoneId.systemDefault());
System.out
    .println(ZonedDateTime.parse(now.format(formatter), formatter));

Hay un error similar que se solucionó en OpenJDK: JDK-8033662 – pero es solo similar, no exactamente igual.

avatar de usuario de assylias
asilias

No sé si es el comportamiento esperado o no (probablemente lo sea), pero técnicamente el ISO_INSTANT formateador no incluye una zona horaria. Si intentas con un DateTimeFormatter.ISO_ZONED_DATE_TIME formateador en su lugar obtendrá lo que espera.

si estás usando org.threeten.bp de java 1.8Aquí está un Hoja de trucos sobre cómo tratar con String to Date y viceversa.

Fecha para cadena

val date = Date()
val calendar = Calendar.getInstance().apply { time = date }
val zonedDateTime = DateTimeUtils.toZonedDateTime(calendar)
val formattedDate = DateTimeFormatter.ISO_INSTANT.format(zonedDateTime)

Cadena hasta la fecha

val dateString = "2020-03-04T09:04:43.835Z"
val dateInstant = Instant.from(DateTimeFormatter.ISO_INSTANT.parse(dateString))
DateTimeUtils.toDate(dateInstant)

¿Ha sido útil esta solución?