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.Date
y 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
, Calendar
Y 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 usandoz
yZ
.) - desplazamiento de tiempo:
'Z'
(no es compatible con otras zonas horarias): debe usarformat.setTimezone(TimeZone.getTimeZone("UTC"))
antes de usar esto.) - tiempo parcial:
HH:mm:ss
oHH:mm:ss.SSS
. - tiempo completo:
HH:mm:ss'Z'
oHH:mm:ss.SSS'Z'
. - fecha completa:
yyyy-MM-dd
- fecha y hora:
yyyy-MM-dd'T'HH:mm:ss'Z'
oyyyy-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");
}