A análise de uma string com data e hora em um determinado momento (Java chama de " Instant
") é bastante complicada. O Java vem resolvendo isso em várias iterações. A mais recente, java.time
e java.time.chrono
, cobre quase todas as necessidades (exceto dilatação do tempo :)).
No entanto, essa complexidade traz muita confusão.
A chave para entender a análise de datas é:
Por que o Java tem tantas maneiras de analisar uma data
- Existem vários sistemas para medir um tempo. Por exemplo, os calendários históricos japoneses foram derivados dos intervalos de tempo do reinado do respectivo imperador ou dinastia. Depois, há, por exemplo, registro de data e hora do UNIX. Felizmente, o mundo inteiro (de negócios) conseguiu usar o mesmo.
- Historicamente, os sistemas estavam sendo alternados de / para, por várias razões . Por exemplo, do calendário juliano ao calendário gregoriano em 1582. Portanto, as datas 'ocidentais' antes disso precisam ser tratadas de maneira diferente.
- E é claro que a mudança não aconteceu de uma só vez. Como o calendário veio das matrizes de algumas religiões e de outras partes da Europa que acreditavam em outras dietas, por exemplo, a Alemanha não mudou até o ano de 1700.
... e por que é o LocalDateTime
, ZonedDateTime
et al. tão complicado
Existem fusos horários . Um fuso horário é basicamente uma "faixa" * [1] da superfície da Terra cujas autoridades seguem as mesmas regras de quando é que esse horário é compensado. Isso inclui regras de horário de verão.
Os fusos horários mudam ao longo do tempo em várias áreas, principalmente com base em quem conquista quem. E as regras de um fuso horário também mudam com o tempo .
Existem compensações de tempo. Isso não é o mesmo que fusos horários, porque um fuso horário pode ser, por exemplo, "Praga", mas com deslocamento de horário de verão e inverno.
Se você obtiver um registro de data e hora com um fuso horário, o deslocamento poderá variar, dependendo da parte do ano em que estiver. Durante a hora do salto, o registro de data e hora poderá significar 2 vezes diferentes, portanto, sem informações adicionais, não poderá ser confiável. convertido.
Nota: Por carimbo de data / hora, quero dizer "uma sequência que contém uma data e / ou hora, opcionalmente com um fuso horário e / ou deslocamento de hora".
Vários fusos horários podem compartilhar o mesmo deslocamento de tempo por determinados períodos. Por exemplo, o fuso horário GMT / UTC é igual ao fuso horário "Londres" quando o deslocamento da hora de verão não está em vigor.
Para tornar um pouco mais complicado (mas isso não é muito importante para o seu caso de uso):
- Os cientistas observam a dinâmica da Terra, que muda com o tempo; com base nisso, eles adicionam segundos no final de anos individuais. (Assim
2040-12-31 24:00:00
Pode ser uma data e hora válidas.) Isso precisa de atualizações regulares dos metadados que os sistemas usam para ter as conversões de data corretas. Por exemplo, no Linux, você recebe atualizações regulares dos pacotes Java, incluindo esses novos dados.
As atualizações nem sempre mantêm o comportamento anterior para registros de data e hora históricos e futuros. Portanto, pode ser que a análise dos dois carimbos de data e hora em torno da alteração de algum fuso horário comparando-os possa fornecer resultados diferentes ao executar em versões diferentes do software. Isso também se aplica à comparação entre o fuso horário afetado e outro fuso horário.
Se isso causar um erro no seu software, considere usar algum registro de data e hora que não possua regras tão complicadas, como o registro de data e hora do UNIX .
Por causa do 7, para as datas futuras, não podemos converter datas exatamente com certeza. Assim, por exemplo, a análise atual 8524-02-17 12:00:00
pode demorar alguns segundos a partir da análise futura.
As APIs do JDK para isso evoluíram com as necessidades contemporâneas
- Os primeiros lançamentos do Java tinham acabado de
java.util.Date
tiveram uma abordagem um pouco ingênua, assumindo que há apenas o ano, mês, dia e hora. Isso rapidamente não foi suficiente.
- Além disso, as necessidades dos bancos de dados eram diferentes, muito cedo,
java.sql.Date
foram introduzidas, com suas próprias limitações.
- Como nenhum dos dois cobriu bem calendários e fusos horários, o
Calendar
API foi introduzida.
- Isso ainda não cobria a complexidade dos fusos horários. E, no entanto, a combinação das APIs acima foi realmente difícil de se trabalhar. Assim, quando os desenvolvedores Java começaram a trabalhar em aplicativos da web globais, as bibliotecas direcionadas à maioria dos casos de uso, como o JodaTime, ficaram rapidamente populares. O JodaTime foi o padrão de fato por cerca de uma década.
- Mas o JDK não se integrou ao JodaTime, portanto, trabalhar com ele foi um pouco complicado. Assim, após uma longa discussão sobre como abordar o assunto, o JSR-310 foi criado principalmente com base no JodaTime .
Como lidar com isso em Java java.time
Determine que tipo analisar um timestamp para
Quando você está consumindo uma sequência de carimbo de data e hora, precisa saber quais informações ela contém. Este é o ponto crucial. Se você não acertar, você terá exceções enigmáticas como "Não é possível criar instantâneo" ou "Falta o deslocamento da zona" ou "ID da zona desconhecida" etc.
Contém a data e a hora?
Tem um deslocamento de tempo?
Um deslocamento de tempo é a +hh:mm
parte. Às vezes, +00:00
pode ser substituído Z
por "horário do Zulu", UTC
como horário universal coordenado ou GMT
horário médio de Greenwich. Eles também definem o fuso horário.
Para esses registros de data e hora, você usa OffsetDateTime
.
Tem um fuso horário?
Para esses registros de data e hora, você usa ZonedDateTime
.
A zona é especificada por
- nome ("Praga", "Horário padrão do Pacífico", "PST") ou
- "ID da zona" ("America / Los_Angeles", "Europe / London"), representado por java.time.ZoneId .
A lista de fusos horários é compilada por um "banco de dados TZ" , apoiado pelo ICAAN.
De acordo com ZoneId
o javadoc, os IDs da zona também podem, de alguma forma, ser especificados como Z
e deslocados. Não tenho certeza de como isso é mapeado para zonas reais. Se o registro de data e hora, que possui apenas um TZ, cai em uma hora bissexta da mudança de deslocamento de tempo, é ambíguo e a interpretação está sujeita ResolverStyle
, veja abaixo.
Se não tiver nenhum , o contexto ausente será assumido ou negligenciado. E o consumidor tem que decidir. Portanto, ele precisa ser analisado LocalDateTime
e convertido em OffsetDateTime
, adicionando as informações ausentes:
- Você pode assumir que é uma hora UTC. Adicione o deslocamento UTC de 0 horas.
- Você pode supor que é uma hora do local em que a conversão está ocorrendo. Converta-o adicionando o fuso horário do sistema.
- Você pode negligenciar e apenas usá-lo como está. Isso é útil, por exemplo, para comparar ou subtrair duas vezes (ver
Duration
), ou quando você não sabe e isso realmente não importa (por exemplo, horário do ônibus local).
Informações de tempo parcial
- Baseado no que o timestamp contém, você pode tomar
LocalDate
, LocalTime
, OffsetTime
, MonthDay
, Year
, ou YearMonth
fora dele.
Se você tiver todas as informações, poderá obter um java.time.Instant
. Isso também é usado internamente para converter entre OffsetDateTime
e ZonedDateTime
.
Descobrir como analisá-lo
Existe uma extensa documentação na DateTimeFormatter
qual é possível analisar uma string de carimbo de data e hora e formatar a string.
Os s pré-criadosDateTimeFormatter
devem abranger todos os formatos padrão de registro de data e hora. Por exemplo, ISO_INSTANT
pode analisar 2011-12-03T10:15:30.123457Z
.
Se você tiver algum formato especial, poderá criar seu próprio DateTimeFormatter (que também é um analisador).
private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();
Eu recomendo olhar para o código fonte DateTimeFormatter
e me inspirar em como criar um usando DateTimeFormatterBuilder
. Enquanto estiver lá, verifique também o ResolverStyle
que controla se o analisador é LENIENT, SMART ou STRICT para os formatos e informações ambíguas.
TemporalAccessor
Agora, o erro frequente é entrar na complexidade de TemporalAccessor
. Isso vem de como os desenvolvedores estavam acostumados a trabalhar SimpleDateFormatter.parse(String)
. Certo, DateTimeFormatter.parse("...")
te dá TemporalAccessor
.
// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");
Mas, equipado com o conhecimento da seção anterior, você pode analisar convenientemente o tipo que precisa:
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);
Você realmente não precisa disso DateTimeFormatter
. Os tipos que você deseja analisar têm os parse(String)
métodos
OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");
Em relação a TemporalAccessor
, você pode usá-lo se tiver uma vaga idéia de quais informações existem na string e deseja decidir em tempo de execução.
Espero lançar alguma luz de entendimento em sua alma :)
Nota: Há um backport java.time
para Java 6 e 7: ThreeTen-Backport . Para o Android, ele possui o ThreeTenABP .
[1] Não apenas por não serem listras, mas também por alguns extremos estranhos. Por exemplo, algumas ilhas do Pacífico vizinhas têm fusos horários +14: 00 e -11: 00. Isso significa que, enquanto em uma ilha há 1 de maio às 15h, em outra ilha não tão longe, ainda é 30 de abril às 12h (se contei corretamente :))
ZonedDateTime
não umLocalDateTime
. O nome é contra-intuitivo; oLocal
significa qualquer localidade, em geral, ao invés de um fuso horário específico. Como tal, umLocalDateTime
objeto não está vinculado à linha do tempo. Para ter significado, para obter um momento específico na linha do tempo, você deve aplicar um fuso horário.