Analizar una cadena con fecha y hora en un punto particular en el tiempo (Java lo llama " Instant
") es bastante complicado. Java ha estado abordando esto en varias iteraciones. El último java.time
y java.time.chrono
cubre casi todas las necesidades (excepto Time Dilation :)).
Sin embargo, esa complejidad trae mucha confusión.
La clave para comprender el análisis de fechas es:
¿Por qué Java tiene tantas formas de analizar una fecha?
- Hay varios sistemas para medir un tiempo. Por ejemplo, los calendarios japoneses históricos se derivaron de los rangos de tiempo del reinado del emperador o dinastía respectivos. Luego está, por ejemplo, la marca de tiempo UNIX. Afortunadamente, todo el mundo (de negocios) logró usar lo mismo.
- Históricamente, los sistemas se cambiaban de / a, por varias razones . Por ejemplo, del calendario juliano al calendario gregoriano en 1582. Por lo tanto, las fechas 'occidentales' antes de eso deben tratarse de manera diferente.
- Y, por supuesto, el cambio no ocurrió de inmediato. Debido a que el calendario provenía de las sedes centrales de algunas religiones y otras partes de Europa que creían en otras dietas, por ejemplo, Alemania no cambió hasta el año 1700.
... y por qué es el LocalDateTime
, ZonedDateTime
et al. tan complicado
Hay zonas horarias . Una zona horaria es básicamente una "franja" * [1] de la superficie de la Tierra cuyas autoridades siguen las mismas reglas de cuándo tiene qué compensación de tiempo. Esto incluye las reglas del horario de verano.
Las zonas horarias cambian con el tiempo para varias áreas, principalmente en función de quién conquista a quién. Y las reglas de una zona horaria también cambian con el tiempo .
Hay compensaciones de tiempo. Eso no es lo mismo que las zonas horarias, porque una zona horaria puede ser, por ejemplo, "Praga", pero tiene un desplazamiento de horario de verano y otro de invierno.
Si obtiene una marca de tiempo con una zona horaria, el desplazamiento puede variar, dependiendo de la parte del año en que se encuentre. Durante la hora bisiesta, la marca de tiempo puede significar 2 tiempos diferentes, por lo que sin información adicional, no puede ser confiable convertido.
Nota: Por marca de tiempo me refiero a "una cadena que contiene una fecha y / o hora, opcionalmente con una zona horaria y / o desplazamiento de hora".
Varias zonas horarias pueden compartir el mismo desplazamiento de tiempo para ciertos períodos. Por ejemplo, la zona horaria GMT / UTC es la misma que la zona horaria "Londres" cuando la compensación de horario de verano no está vigente.
Para hacerlo un poco más complicado (pero eso no es demasiado importante para su caso de uso):
- Los científicos observan la dinámica de la Tierra, que cambia con el tiempo; basado en eso, agregan segundos al final de los años individuales. (Por
2040-12-31 24:00:00
lo tanto, puede ser una fecha y hora válida). Esto necesita actualizaciones periódicas de los metadatos que los sistemas usan para tener las conversiones de fecha correctas. Por ejemplo, en Linux, obtiene actualizaciones periódicas de los paquetes de Java, incluidos estos nuevos datos.
Las actualizaciones no siempre mantienen el comportamiento anterior para las marcas de tiempo históricas y futuras. Por lo tanto, puede ocurrir que el análisis de las dos marcas de tiempo alrededor del cambio de una zona horaria al compararlas pueda dar resultados diferentes cuando se ejecuta en diferentes versiones del software. Eso también se aplica a la comparación entre la zona horaria afectada y otra zona horaria.
Si esto causa un error en su software, considere usar una marca de tiempo que no tenga reglas tan complicadas, como la marca de tiempo UNIX .
Debido a 7, para las fechas futuras, no podemos convertir fechas exactamente con certeza. Entonces, por ejemplo, el análisis actual 8524-02-17 12:00:00
puede estar desactivado unos segundos después del análisis futuro.
Las API de JDK para esto evolucionaron con las necesidades contemporáneas
- Las primeras versiones de Java
java.util.Date
tenían un enfoque un poco ingenuo, suponiendo que solo hay año, mes, día y hora. Esto rápidamente no fue suficiente.
- Además, las necesidades de las bases de datos eran diferentes, por lo que
java.sql.Date
se introdujo muy temprano , con sus propias limitaciones.
- Como ninguno de los dos cubría diferentes calendarios y zonas horarias,
Calendar
se introdujo la API.
- Esto todavía no cubría la complejidad de las zonas horarias. Y, sin embargo, la combinación de las API anteriores fue realmente difícil de trabajar. Entonces, a medida que los desarrolladores de Java comenzaron a trabajar en aplicaciones web globales, las bibliotecas dirigidas a la mayoría de los casos de uso, como JodaTime, se hicieron rápidamente populares. JodaTime fue el estándar de facto durante aproximadamente una década.
- Pero el JDK no se integró con JodaTime, por lo que trabajar con él fue un poco engorroso. Entonces, después de una larga discusión sobre cómo abordar el asunto, JSR-310 fue creado principalmente basado en JodaTime .
Cómo lidiar con eso en Java java.time
Determine a qué tipo analizar una marca de tiempo
Cuando consume una cadena de marca de tiempo, necesita saber qué información contiene. Este es el punto crucial. Si no lo hace bien, terminará con excepciones crípticas como "No se puede crear Instantáneo" o "Falta el desplazamiento de zona" o "ID de zona desconocida", etc.
¿Contiene la fecha y la hora?
¿Tiene un desplazamiento de tiempo?
Un desplazamiento de tiempo es la +hh:mm
parte. A veces, +00:00
se puede sustituir por Z
"hora Zulú", UTC
como hora universal coordinada o GMT
como hora media de Greenwich. Estos también establecen la zona horaria.
Para estas marcas de tiempo, usted usa OffsetDateTime
.
¿Tiene una zona horaria?
Para estas marcas de tiempo, usted usa ZonedDateTime
.
La zona se especifica por
- nombre ("Praga", "Hora estándar del Pacífico", "PST") o
- "ID de zona" ("América / Los_Angeles", "Europa / Londres"), representada por java.time.ZoneId .
La lista de zonas horarias está compilada por una "base de datos TZ" , respaldada por ICAAN.
Según ZoneId
el javadoc, los id de zona también se pueden especificar Z
y compensar de alguna manera . No estoy seguro de cómo esto se asigna a zonas reales. Si la marca de tiempo, que solo tiene una TZ, cae en un cambio de compensación de hora bisiesta, entonces es ambigua y la interpretación está sujeta a ResolverStyle
, ver más abajo.
Si no tiene ninguno , entonces el contexto faltante es asumido o descuidado. Y el consumidor tiene que decidir. Por lo tanto, debe analizarse LocalDateTime
y convertirse al OffsetDateTime
agregar la información que falta:
- Puede suponer que es una hora UTC. Agregue el desplazamiento UTC de 0 horas.
- Puede suponer que es el momento del lugar donde está ocurriendo la conversión. Conviértalo agregando la zona horaria del sistema.
- Puede descuidar y simplemente usarlo como está. Eso es útil, por ejemplo, para comparar o restar dos veces (ver
Duration
), o cuando no sabe y realmente no importa (por ejemplo, el horario del autobús local).
Información de tiempo parcial
- Sobre la base de lo que contiene la marca de tiempo, se puede tomar
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, o YearMonth
fuera de él.
Si tiene la información completa, puede obtener un java.time.Instant
. Esto también se usa internamente para convertir entre OffsetDateTime
y ZonedDateTime
.
Descubre cómo analizarlo
Existe una extensa documentación sobre la DateTimeFormatter
cual se puede analizar una cadena de marca de tiempo y formatearla.
Los s creados previamenteDateTimeFormatter
deben abarcar más todos los formatos de marca de tiempo estándar. Por ejemplo, ISO_INSTANT
puede analizar 2011-12-03T10:15:30.123457Z
.
Si tiene algún formato especial, puede crear su propio DateTimeFormatter (que también es un analizador).
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
Recomiendo mirar el código fuente DateTimeFormatter
e inspirarse en cómo construir uno usando DateTimeFormatterBuilder
. Mientras esté allí, también observe ResolverStyle
qué controles controla si el analizador es LENIENTE, INTELIGENTE o ESTRICTO para los formatos y la información ambigua.
Accesorio temporal
Ahora, el error frecuente es entrar en la complejidad de TemporalAccessor
. Esto proviene de cómo solían trabajar los desarrolladores SimpleDateFormatter.parse(String)
. Bien, DateTimeFormatter.parse("...")
te da TemporalAccessor
.
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
Pero, equipado con el conocimiento de la sección anterior, puede analizar convenientemente el tipo que necesita:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
En realidad no es necesario DateTimeFormatter
tampoco. Los tipos que desea analizar tienen los parse(String)
métodos.
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Al respecto TemporalAccessor
, puede usarlo si tiene una idea vaga de qué información hay en la cadena y desea decidir en tiempo de ejecución.
Espero arrojar algo de luz sobre tu alma :)
Nota: Hay un backport de java.time
Java 6 y 7: ThreeTen-Backport . Para Android tiene ThreeTenABP .
[1] No solo que no son rayas, sino que también hay algunos extremos extraños. Por ejemplo, algunas islas vecinas del Pacífico tienen zonas horarias de +14: 00 y -11: 00. Eso significa que, mientras que en una isla, es el 1 de mayo a las 3 p.m., en otra isla no tan lejos, todavía son 30 de abril a las 12 p.m. (si contaba correctamente :))
ZonedDateTime
lugar de unLocalDateTime
. El nombre es contra-intuitivo; EstoLocal
significa cualquier localidad en general en lugar de una zona horaria específica. Como tal, unLocalDateTime
objeto no está vinculado a la línea de tiempo. Para tener significado, para obtener un momento específico en la línea de tiempo, debe aplicar una zona horaria.