Executando verificação de segurança...
2

Artigo em boa hora, estava esta semana estudando sobre o java.time, isto sobre fuso-horários acabou me tomando boa parcela do meu tempo, mas acho que consegui compreender as nuances (não completamente, óbvio) da abordagem da linguagem Java para as datas, segue as minhas anotações e contribuições para complementar às suas de Javascript:

  • TemporalAcessor: Uma interface implementada por todas abaixo referente a Date-Time, permitindo tratar dados de forma genérica sem precisar converter numa ou outra específica para atender a algum parâmetro, etc.
  • Temporal: Implementa TemporalAcessor e fornece os métodos básicos implado por todas abaixo referente a Date-Time
  • LocalDate: Apenas as datas, dispensando as horas;
  • LocalTime: Apenas o horário, dispensando as datas;
  • LocalDateTime: Utiliza ambos, tanto a hora quanto a data. Mas não tem fuso-horário;
  • OffsetDateTime: Utiliza ambos com fuso-horário, mas sem sensibilidade ou mudanças a longo prazo (horário de verão);
  • ZonedDateTime: Utiliza ambos com fuso-horário sensível à mudanças como horário de verão, mas maior complexidade;
  • ZoneOffset: Apenas o fuso-horário sem nome ou sensibilidade a mudanças sazonais (usado por OffsetDateTime);
  • ZoneId: Um identificador de fuso-horário completo e todas as regras aplicadas a estes fusos por cada região;
  • Instant: Um momento específico da linha do tempo em milisegundos (UTC+0), mas sem considerar datas;
  • Clock: Similar ao System.currentTimeMillis(), mas permite importar relógios externos como API;
  • ChronoField: Enumeração que implementa TemporalField;
  • ChronoUnit: Enumeração que implementa TemporalUnit;
  • Chronology: Implementa diferentes tipos de calendários junto do ChronoLocalDate (menos utilizado).

Observação: Enumeração, para quem não manja de Java é uma lista truncada de opções que DEVEM ser selecionadas.


O ideal para contornar a situação é o ZonedDateTime, pois nele você consegue passar não só o idioma, mas o local que a data será baseada, sensíveis à estas alterações. Entretanto, num sistema internacional o Instant é melhor, pois sempre estará junto ao UTC e consegue converter ZonedDateTime para o Instant. Entretanto, ele não é uma data (pode ser convertido para uma, no entanto), é um momento no tempo contado a partir do epoch (1 de janeiro de 1970, às 00:00), podendo ser tanto negativo quanto positivo.

Ou seja, na minha cabeça o ideal seria armazenar ZonedDateTime -> Converta para Instant -> Faz as operações -> Retorna a resposta convertendo de volta para ZonedDateTime (se algum veterano tiver experiência na utilização, feedback seria bom)


Por último, acho que discordo do seu ponto do "não há uma medida universal de datas", pois o UTC veio justamente para fazer isso, é como se fosse um contrato inerente e consensual da humanidade entre si para: "independente da sua opinião ou localização que você utiliza na sua casa, aqui usamos X", é como se fosse... "independente do seu comportamento na sua casa, o ideal é X!" e isso é o que define algo universal, de comum acordo entre as partes.

A dificuldade técnica apontada seria em nos adaptarmos à "casa" de todos, mas é muito mais fácil fazer a "casa" de todos se adaptarem a nós, na medida universal da coisa.

Carregando publicação patrocinada...
3

Sobre ZoneOffset e ZoneId: o primeiro representa um offset, e o segundo, um timezone. Estes conceitos estão relacionados, mas muitas vezes são confundidos e tratados como se fossem a mesma coisa.

Um offset é a diferença em relação a UTC. É um valor fixo, por exemplo, -03:00 é 3 horas antes do UTC. Um timezone é um identificador que representa uma região geográfica, e possui o histórico de todos os offsets que esta região teve ao longo da história. Por exemplo, America/Sao_Paulo possui a lista de offsets que uma região específica do Brasil teve ao longo do tempo (isso inclui as mudanças de horário de verão).

Vale notar que muitos timezones podem ter o mesmo offset em determinadas épocas. Por exemplo, America/Sao_Paulo e America/Recife estão com o offset -03:00, mas São Paulo teve horário de verão em épocas nas quais Recife não teve (um estava em -02:00 enquanto o outro permaneceu em -03:00). Como o histórico é diferente, eles não são o mesmo timezone (mesmo que eles coincidam na maior parte do tempo).

Enfim, ZoneOffset representa apenas um offset específico, e ZoneId representa um timezone, com todo o histórico de offsets.

O mesmo vale para OffsetDateTime (data e hora com um offset fixo) e ZonedDateTime (data e hora em um timezone, com o offset podendo variar de acordo com o histórico do timezone). No meu blog tem exemplos em Java que explicam melhor.

Quanto a "sempre usar UTC" (ou seja, sempre usar Instant), varia muito, nem sempre será a melhor solução. Veja aqui, principalmente a seção "UTC e eventos futuros" e também aqui para ver um caso em que somente converter para UTC não é suficiente.


não há uma medida universal de datas

Em nenhum ponto do texto eu afirmo isso. Eu só disse que não há uma norma oficial sobre como a aritmética de datas deve funcionar, e que em outro comentário foi citado como "aritmética universal".

2

Em nenhum ponto do texto eu afirmo isso. Eu só disse que não há uma norma oficial sobre como a aritmética de datas deve funcionar, e que em outro comentário foi citado como "aritmética universal".

Ah, sim. É que eu tinha visto noutro comentário, mas peço perdão pelo equívoco. Neste ponto não há realmente consenso pelo que estudei então há convergência nas informações. (Não quero me sobressair, apenas em caso de divergência é uma oportunidade para eu aprender.)

Enfim, ZoneOffset representa apenas um offset específico, e ZoneId representa um timezone, com todo o histórico de offsets.

Realmente tinha sido este o entendimento que eu tive, mas acho que na minha própria anotação não deixei tão claro, isso iria me atrapalhar em possiveis revisões (eu entendo as anotações porque ainda está fresco na mente). E sobre o ZoneId guardar o histórico de Offset das regiões eu não fazia ideia que as "informações completas" incluiam isso.

Por último, obrigado pelos artigos, tô com um acumulado pra ler, mas logo mais darei uma atenção a eles.

3

Complementando mais um pouco...

A ideia do TemporalAccessor é ser uma interface bem básica que define uma maneira genérica de obter os valores numéricos correspondentes aos campos de data. Tanto que os únicos métodos que vc precisa implementar recebem um TemporalField, e um indica se o campo é suportado, enquanto outro retorna seu valor.

É a funcionalidade mais básica, que permite que se obtenha qualquer campo de uma classe que represente datas/horas, quando suportado.

Já a interface Temporal adiciona métodos para modificar campos (na verdade, para retornar outra instância modificada, já que as classes do java.time são imutáveis) e somar/subtrair uma unidade de tempo.

Mas na documentação de ambas diz para evitar usá-las diretamente - salvo raras exceções, e de fato na prática eu acabo usando tipos específicos para cada caso (LocalDate, ZonedDateTime, Instant, etc). Inclusive, os getters de cada classe são mais fáceis de usar do que o get genérico de TemporalAccessor (que também quase não uso na prática).


Sobre o histórico de offsets, podemos fazer um pequeno código para ver a diferença. Primeiro com um timezone:

// obtém a lista de mudanças de offset
List<ZoneOffsetTransition> mudancas = ZoneId.of("America/Sao_Paulo").getRules().getTransitions();
System.out.println(mudancas.size()); // 91
for (ZoneOffsetTransition tr : mudancas) {
    System.out.println(tr);
}

A quantidade exata de mudanças vai variar conforme a versão do Java e do TZDB que estiver instalada, já que isso é sempre atualizado. Mas enfim, no timezone America/Sao_Paulo tem uma lista bem grande de alterações.

Agora se usarmos um offset:

List<ZoneOffsetTransition> mudancas = ZoneOffset.ofHours(-3).getRules().getTransitions();
System.out.println(mudancas.size()); // 0
for (ZoneOffsetTransition tr : mudancas) {
    System.out.println(tr);
}

A lista terá zero mudanças, e o for não vai imprimir nada. Isso porque o offset é fixo, nunca muda.


Aliás, essa é uma das pouquíssimas API's que fazem esta distinção. A maioria simplesmente assume que offset é o mesmo que timezone e acabam ficando incompletas e até mesmo incorretas. Não é raro ter apenas uma única classe para ambos (isso quando tem, pois há API's que nem sequer permitem manipular o timezone).

Eu particularmente considero o java.time uma das mais completas e bem pensadas API's de data existentes, por esse e vários outros detalhes.

2

Concordo que seja bem pensada, ainda mais pelo fato dela contemplar tudo o que existe em datas. E por eu ter dedicado fucking 2 ou 3 dias estudando todas as suas variações, estranhei a complicação excessiva nesta coisa de data (menos Chronology, pois acho que será beem raro ao ponto de ser possível de eu nunca precisar utilizar um calendário diferente do ISO). O que me pegou mesmo foi saber a diferença de data e um ponto específico no tempo, mas agora já tenho uma ideia do que é.

Entretanto, ainda assim, quanto mais se estuda sobre esta documentação, pior fica. Parece um conteúdo infinito de camada abaixo de camada kkkkk e isso tem me apavorado e estou quase saindo deste método de estudo de ir na documentação e se aprofundar em seus métodos e voltar a fazer os cursos que só passa uma explicação rasa por cima do que faz e já era. Desta forma me sinto mais aprofundado, mas come muuuito tempo que eu poderia estar estudando coisas e frameworks que o mercado pede para entrar logo na área. O que acha?

2

Como vc já pegou os conceitos básicos (entende que data e ponto no tempo são diferentes, timezone e offset não são a mesma coisa, etc), já está na frente de muita gente :-)

Eu sei como é se perder na documentação, a API é bem grande e completa, e não dá pra pegar todos os conceitos de uma vez. Data/hora é um assunto complicado e muito mais extenso do que as pessoas imaginam.

Minha sugestão é que agora vc pode ir estudando sob demanda: quando surgir a necessidade de fazer algo com datas, pesquise como é nesta API. Os casos de uso mais comuns estão implementados lá, e vários outros não tão comuns assim também.