Saltar al contenido

¿Cómo analizo las fechas y horas de RFC 3339 con Java?

Solución:

tl; dr

Instant.parse( "2011-05-03T11:58:01Z" )

ISO 8601

En realidad, RFC 3339 no es más que un mero “perfil” autoproclamado del estándar actual, ISO 8601.

El RFC es diferente en el sentido de que viola intencionalmente la norma ISO 8601 para permitir una compensación negativa de cero horas (-00:00) y le da un significado semántico de “compensación desconocida”. Esa semántica me parece una muy mala idea. Aconsejo ceñirse a las reglas ISO 8601 más sensatas. En ISO 8601, no tener ningún desplazamiento significa que el desplazamiento es desconocido, un significado obvio, mientras que la regla RFC es abstrusa.

El moderno java.time las clases utilizan los formatos ISO 8601 de forma predeterminada al analizar / generar cadenas.

Su cadena de entrada representa un momento en UTC. los Z al final es corto para Zulu y significa UTC.

Instant (no Date)

La clase moderna Instant representa un momento en UTC. Esta clase reemplaza java.util.Datey utiliza una resolución más fina de nanosegundos en lugar de milisegundos.

Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;

ZonedDateTime (no Calendar)

Para ver ese mismo momento a través del tiempo de reloj de pared utilizado por la gente de una determinada región (una zona horaria), aplique un ZoneId conseguir un ZonedDateTime. Esta clase ZonedDateTime reemplaza el java.util.Calendar clase.

ZoneId z = ZoneId.of( "Africa/Tunis" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, same point on the timeline, different wall-clock time.

Mudado

Recomiendo encarecidamente evitar las clases de fecha y hora heredadas cuando sea posible. Pero si debe interactuar con un código antiguo que aún no se ha actualizado a java.time, puede convertir de un lado a otro. Llamar a los nuevos métodos agregados al viejo clases.

Instant reemplaza java.util.Date.

java.util.Date myJUDate = java.util.Date.from( instant ) ;  // From modern to legacy.
Instant instant = myJUDate.toInstant() ;                    // From legacy to modern.

ZonedDateTime reemplaza GregorianCalendar.

java.util.GregorianCalendar myGregCal = java.util.GregorianCalendar.from( zdt ) ;  // From modern to legacy.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ;           // From legacy to modern.

Si tienes un java.util.Calendar eso es en realidad un GregorianCalendar, emitir.

java.util.GregorianCalendar myGregCal = ( java.util.GregorianCalendar ) myCal ;  // Cast to the concrete class.
ZonedDateTime zdt = myGregCal.toZonedDateTime() ;           // From legacy to modern.

Preocupaciones con viñetas

Con respecto a los problemas específicos de su pregunta …

  • Sin bibliotecas externas (frascos)

los java.time las clases están integradas en Java 8, 9, 10 y posteriores. También se incluye una implementación en Android posterior. Para Java anterior y Android anterior, consulte la siguiente sección de esta respuesta.

  • Maneja todos los formatos RFC 3339 aceptables

Los diversos java.time las clases manejan todos los formatos ISO 8601 que conozco. Incluso manejan algunos formatos que desaparecieron misteriosamente de ediciones posteriores del estándar.

Para otros formatos, consulte el parse y toString métodos de las diversas clases tales como LocalDate, OffsetDateTime, etcétera. Además, busque Stack Overflow ya que hay muchos ejemplos y discusiones sobre este tema.

  • Una cadena debe poder validarse fácilmente para ver si es una fecha RFC 3339 válida

Para validar cadenas de entrada, intercepte para DateTimeParseException.

try {
    Instant instant = Instant.parse( "2011-05-03T11:58:01Z" ) ;
} catch ( DateTimeParseException e ) {
    … handle invalid input
}

Sobre java.time

los java.time framework está integrado en Java 8 y versiones posteriores. Estas clases suplantan las antiguas y problemáticas clases de fecha y hora heredadas, como java.util.Date, CalendarY SimpleDateFormat.

los Joda-Time proyecto, ahora en modo de mantenimiento, aconseja la migración a las clases java.time.

Para obtener más información, consulte el Tutorial de Oracle. Y busque Stack Overflow para obtener muchos ejemplos y explicaciones. La especificación es JSR 310.

Puedes intercambiar java.time objetos directamente con su base de datos. Utilice un controlador JDBC compatible con JDBC 4.2 o posterior. Sin necesidad de cuerdas, sin necesidad de java.sql.* clases.

¿Dónde obtener las clases java.time?

  • Java SE 8, Java SE 9, y después

    • Incorporado.
    • Parte de la API estándar de Java con una implementación integrada.
    • Java 9 agrega algunas características y correcciones menores.
  • Java SE 6 y Java SE 7

    • Gran parte de la funcionalidad de java.time está retroportada a Java 6 y 7 en ThreeTen-Backport.
  • Androide

    • Versiones posteriores de implementaciones de paquetes de Android de las clases java.time.
    • Para Android anterior (<26), el ThreeTenABP el proyecto se adapta ThreeTen-Backport (mencionado anteriormente). Ver Cómo usar ThreeTenABP….

los ThreeTen-Extra el proyecto extiende java.time con clases adicionales. Este proyecto es un campo de pruebas para posibles adiciones futuras a java.time. Puede encontrar algunas clases útiles aquí, como Interval, YearWeek, YearQuarter, y más.

Entonces, en principio, esto se haría usando diferentes patrones SimpleDateFormat.

Aquí una lista de patrones para las declaraciones individuales en RFC 3339:

  • fecha-año completo: yyyy
  • fecha-mes: MM
  • fecha-mday: dd
  • tiempo-hora: HH
  • tiempo -minuto: mm
  • tiempo-segundo: ss
  • tiempo-secfrac: .SSS (S significa milisegundos, sin embargo, no está claro qué sucedería si hay más o menos de 3 dígitos de estos).
  • time-numoffset: (como +02:00 parece no ser compatible, en cambio, es compatible con los formatos +0200, GMT+02:00 y algunas zonas horarias con nombre usando z y Z.)
  • desplazamiento de tiempo: 'Z' (no es compatible con otras zonas horarias): debe usar format.setTimezone(TimeZone.getTimeZone("UTC")) antes de usar esto.)
  • tiempo parcial: HH:mm:ss o HH:mm:ss.SSS.
  • tiempo completo: HH:mm:ss'Z' o HH:mm:ss.SSS'Z'.
  • fecha completa: yyyy-MM-dd
  • fecha y hora: yyyy-MM-dd'T'HH:mm:ss'Z' o yyyy-MM-dd'T'HH:mm:ss.SSS'Z'

Como podemos ver, esto parece no poder analizar todo. Quizás sería una mejor idea implementar un RFC3339DateFormat desde cero (usando expresiones regulares, por simplicidad, o analizando a mano, por eficiencia).

Acabo de descubrir que Google implementó el analizador Rfc3339 en la biblioteca de cliente HTTP de Google

https://github.com/google/google-http-java-client/blob/dev/google-http-client/src/main/java/com/google/api/client/util/DateTime.java

Probado. Funciona bien para analizar el fragmento de tiempo varía en segundos.

import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Date;

import com.google.api.client.util.DateTime;

DateTimeFormatter formatter = DateTimeFormatter
            .ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'")
            .withZone(ZoneId.of("UTC"));

@Test
public void test1e9Parse() {
    String timeStr = "2018-04-03T11:32:26.553955473Z";

    DateTime dateTime = DateTime.parseRfc3339(timeStr);
    long millis = dateTime.getValue();

    String result = formatter.format(new Date(millis).toInstant());

    assert result.equals("2018-04-03T11:32:26.553Z");
}

@Test
public void test1e3Parse() {
    String timeStr = "2018-04-03T11:32:26.553Z";

    DateTime dateTime = DateTime.parseRfc3339(timeStr);
    long millis = dateTime.getValue();

    String result = formatter.format(new Date(millis).toInstant());

    assert result.equals("2018-04-03T11:32:26.553Z");
}

@Test
public void testEpochSecondsParse() {

    String timeStr = "2018-04-03T11:32:26Z";

    DateTime dateTime = DateTime.parseRfc3339(timeStr);
    long millis = dateTime.getValue();

    String result = formatter.format(new Date(millis).toInstant());

    assert result.equals("2018-04-03T11:32:26.000Z");
}
¡Haz clic para puntuar esta entrada!
(Votos: 0 Promedio: 0)



Utiliza Nuestro Buscador

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *