W3docs

Introdução à API de Data e Hora do Java

Introdução à moderna API de data e hora do Java em java.time, substituindo as classes legadas Date e Calendar.

O Java 8 adicionou java.time, um novo pacote para representar datas, horas, durações, fusos horários e a aritmética entre eles. Ele substituiu duas APIs anteriores — java.util.Date e java.util.Calendar — que tinham uma reputação merecida de ser o canto mais mal projetado do JDK. A nova API foi impulsionada pela biblioteca Joda-Time anterior de Stephen Colebourne; se você já usou Joda, java.time parecerá familiar.

As duas coisas importantes sobre o redesenho:

  1. Todo tipo é imutável. Um LocalDate uma vez criado nunca muda. Métodos como plusDays(7) retornam um novo LocalDate. Isso torna a API thread-safe por construção e elimina toda uma categoria de bugs.
  2. Cada tipo significa uma coisa. LocalDate é uma data sem hora. Instant é um momento na linha do tempo. Duration é uma duração de tempo. O Date legado era de alguma forma tudo isso ao mesmo tempo, dependendo de qual construtor você usava; a nova API os separa para que o tipo indique que tipo de valor você tem.

Este capítulo é o mapa. Os próximos dez capítulos aprofundam cada classe.

Os tipos principais

"A date"           LocalDate            2025-11-04
"A time of day"    LocalTime            14:30:00
"Both, no zone"    LocalDateTime        2025-11-04T14:30:00
"Both, with zone"  ZonedDateTime        2025-11-04T14:30:00-05:00 [America/New_York]
"A moment"         Instant              2025-11-04T19:30:00Z      (UTC, seconds-since-epoch)
"A length of time" Duration             PT1H30M                   (1 hour 30 minutes)
"A length of date" Period               P1Y2M3D                   (1 year 2 months 3 days)

A divisão horizontal — Local* vs Zoned/Instant — é a mais importante. Os tipos Local não carregam fuso horário. Um LocalDate de 2025-11-04 é "o quarto de novembro"; ele não diz se é o quarto em Tóquio ou em Honolulu. É o tipo certo para uma data de aniversário, uma data de contrato ou um seletor de datas na interface.

Os tipos com zona carregam seu fuso. ZonedDateTime é "este instante no calendário neste lugar," que é o que você quer para "reunião agendada para 9h em Nova York." Instant é um momento na linha do tempo global — segundos UTC desde a época — que é o que você quer para registros de log, timestamps de mensagens, qualquer coisa que precise ser ordenada globalmente sem precisar de rótulos locais.

A divisão horizontal entre Duration e Period também importa. Duration é uma duração de tempo que você pode comparar em segundos — PT24H é exatamente 24 × 3600 segundos. Period é uma duração expressa em termos de calendário — P1M (um mês) tem 30 dias em alguns meses e 31 em outros. Para medições de tempo, você quer Duration. Para "adicionar um mês a uma data de cobrança," você quer Period.

A forma fluente

Todo tipo é construído e modificado por meio de uma API fluente consistente:

LocalDate today    = LocalDate.now();
LocalDate stardate = LocalDate.of(2025, 11, 4);
LocalDate parsed   = LocalDate.parse("2025-11-04");

LocalDate nextWeek = today.plusDays(7);                     // immutable: returns a NEW LocalDate
LocalDate lastYear = today.minusYears(1);
LocalDate firstOfMonth = today.withDayOfMonth(1);            // with* returns a copy with one field changed

boolean before = today.isBefore(stardate);
int year = today.getYear();

Três formatos que você verá em todo lugar:

  • now() — valor atual do relógio do sistema.
  • of(...) — componentes explícitos.
  • parse(...) — a partir de uma string (ISO-8601 por padrão).

E para transformações:

  • plusX(n) / minusX(n) — aritmética.
  • withX(value) — substitui um único campo.
  • isBefore(other) / isAfter(other) — comparação.

Esse formato se repete em LocalDate, LocalTime, LocalDateTime, ZonedDateTime e Instant. Uma vez que você conhece o padrão, cada classe fala o mesmo dialeto com um vocabulário ligeiramente diferente.

Fusos horários são difíceis, e a API admite isso

O maior motivo pelo qual java.util.Date era problemático é que ele tentava tornar os fusos horários invisíveis. O resultado foi a famosa classe de bug de "armazenar um Date, recuperá-lo em um servidor em um fuso horário diferente e obter a data do calendário errada." java.time resolve isso tornando o fuso explícito no tipo.

Se você aceitar uma data de um usuário e não souber em qual fuso ele está, armazene como LocalDate. Se ele disser que é "9h no horário dele" e você conhecer o fuso, armazene como ZonedDateTime com o fuso. Se você registrar um evento de servidor, armazene como Instant. Não armazene um LocalDateTime esperando que o fuso horário apareça; o fuso ausente é exatamente o bug.

Instant now = Instant.now();                                 // unambiguous: a moment in UTC
ZonedDateTime localized = now.atZone(ZoneId.of("Europe/Berlin"));  // a label for that moment in Berlin

A hierarquia de fusos:

  • ZoneOffset é um deslocamento fixo ±HH:MM em relação ao UTC: +05:30, -08:00. Sem tratamento de horário de verão.
  • ZoneId é um fuso nomeado: Europe/Berlin, America/New_York. Carrega o registro do banco de dados IANA sobre qual deslocamento aquele fuso tem em qualquer dia específico, incluindo transições de horário de verão e mudanças históricas.

Sempre prefira ZoneId em vez de ZoneOffset quando tiver escolha. "America/New_York" está correto durante o horário de verão e fora dele; "−05:00" está correto apenas fora do horário de verão.

Os tipos legados não desapareceram

java.util.Date, java.util.Calendar e java.text.SimpleDateFormat ainda existem. Código novo não deveria usá-los — mas muito código antigo usa, e você precisará interoperar. Os métodos de conversão são diretos:

// java.util.Date <-> java.time.Instant
Instant inst = legacyDate.toInstant();
Date    back = Date.from(inst);

// java.util.Calendar -> java.time.ZonedDateTime
ZonedDateTime zdt = ZonedDateTime.ofInstant(
    cal.toInstant(), cal.getTimeZone().toZoneId());

O padrão é unidirecional: legado → java.time é simples; para qualquer coisa nova, permaneça em java.time e converta apenas no limite da API onde vive o código antigo. Os capítulos Legacy Date e Calendar no final desta parte cobrem a ponte em detalhes.

Um exemplo completo: a família de tipos em um programa

O programa abaixo usa todos os tipos que o mapa acima introduziu — LocalDate, LocalTime, LocalDateTime, ZonedDateTime, Instant, Duration, Period — e mostra como eles se convertem entre si. É a versão "tour"; cada tipo individual tem seu próprio capítulo a partir daqui.

java— editable, runs on the server

O que observar na execução:

  • O primeiro bloco construiu a família por composição: LocalDate + LocalTime = LocalDateTime; LocalDateTime + ZoneId = ZonedDateTime; ZonedDateTimeInstant. Essa é a rede de conversões, e você fará isso toda vez que cruzar um limite de API. As setas vão nos dois sentidos para a maioria dos pares — Instant.atZone(zone) e ZonedDateTime.toLocalDateTime() fecham os ciclos.
  • Um único Instant exibiu três horas de aparência diferente quando visto de Nova York, Berlim e Tóquio. Esse é o ponto de Instant: é o momento, independente de onde você está. O ZonedDateTime adiciona o rótulo "onde estou." Confundir os dois é o erro do Date legado.
  • Duration imprimiu como PT1H30M e Period imprimiu como P3M. O formato de duração ISO-8601 é PnYnMnDTnHnMnS — tudo antes do T são unidades de calendário (Period), tudo depois são unidades de tempo (Duration). A string é exatamente o que toString() retorna e exatamente o que parse(...) aceita.
  • today.plusDays(7) produziu um LocalDate diferente. Imprimir today novamente logo depois mostrou que o original não foi alterado — essa é a garantia de imutabilidade. Todo plus/minus/with retorna um novo objeto; o receptor nunca é modificado. Sem cópia defensiva, sem preocupações de thread-safety, nunca.
  • ChronoUnit.DAYS.between(today, launch) foi a operação de "distância." Ela retorna um long, não um Period, porque a resposta em dias não tem ambiguidade de calendário (ao contrário de meses, que variam em comprimento). Cada capítulo nesta parte usa ChronoUnit em algum lugar — é o catálogo de unidades de tempo sobre as quais a API fala.

O que vem a seguir

O próximo capítulo, Java LocalDate, inicia o tour em profundidade. LocalDate é o mais simples dos cinco tipos de "ponto no tempo" e o lugar certo para aprender a forma fluente que todos os outros compartilham.

Prática

Prática
Você precisa armazenar o momento em que um servidor recebeu uma requisição HTTP, para que o log possa ser ordenado globalmente entre servidores em fusos horários diferentes. Qual tipo `java.time` se encaixa?
Você precisa armazenar o momento em que um servidor recebeu uma requisição HTTP, para que o log possa ser ordenado globalmente entre servidores em fusos horários diferentes. Qual tipo `java.time` se encaixa?
Was this page helpful?