W3docs

Classe Date Legada do Java

A classe legada java.util.Date — por que foi substituída pelo java.time e como converter entre elas.

java.util.Date foi a classe de data e hora original do Java, presente desde o Java 1.0 em 1995. Ela ainda está no JDK; código novo não deveria usá-la, mas você a encontrará em bibliotecas, bancos de dados (java.sql.Date a estende) e em qualquer código anterior a aproximadamente 2014. Este capítulo existe para fazer a ponte com o java.time.

A versão resumida: Date é internamente um invólucro em torno de um long de milissegundos desde a época, assim como Instant é internamente um par (seconds, nanos). Portanto, a conversão natural é Date ↔ Instant. Para qualquer outra coisa (ano, mês, dia, aritmética de calendário), converta primeiro para java.time.

O que Date realmente é

public class Date implements Cloneable, Comparable<Date>, Serializable {
  private long fastTime;        // milliseconds since 1970-01-01T00:00:00Z
}

Esse é o estado real completo. Um Date é um ponto no tempo, medido em milissegundos desde a época Unix, em UTC. Apesar do nome, ele não carrega uma data de calendário — getYear() e similares calculam a partir do valor em milissegundos no fuso horário padrão da JVM, que é a origem dos famosos problemas da API.

Date now = new Date();                                       // current moment
Date epoch = new Date(0);                                    // 1970-01-01T00:00:00Z
Date fromMs = new Date(1_700_000_000_000L);

long ms = now.getTime();                                     // milliseconds since epoch

new Date() e Date.getTime() são os dois métodos que envelheceram bem. Tudo o mais foi depreciado ou carrega uma armadilha.

Os acessores de calendário depreciados

Esses métodos foram depreciados no Java 1.1 (1997) quando Calendar foi adicionado:

date.getYear();                                              // year - 1900   (deprecated)
date.getMonth();                                             // 0-11          (deprecated)
date.getDate();                                              // 1-31, day of month (deprecated)
date.getDay();                                               // 0-6, day of week (deprecated)
date.getHours(); date.getMinutes(); date.getSeconds();       // local-zone reads (deprecated)

As depreciações existem há 28 anos. Elas ainda funcionam. As armadilhas:

  • getYear() retorna year - 1900. Para 2025, retorna 125. Esse é um candidato indiscutível ao título de "decisão de API mais estranha do JDK."
  • getMonth() retorna 0-11. Janeiro é 0, Dezembro é 11. Erros de deslocamento por um são garantidos se você escrever getMonth() + 1 e esquecer uma vez.
  • Todo acessor lê no fuso horário padrão da JVM. O mesmo Date em duas máquinas com fusos diferentes dá resultados diferentes em getDate().

Não chame esses métodos. No momento em que você se pegar prestes a usar date.getYear(), converta para Instant/ZonedDateTime e use os acessores modernos.

A ponte Date ↔ Instant

Date legacy = new Date();
Instant inst = legacy.toInstant();                            // since Java 8

Instant other = Instant.parse("2025-11-04T19:30:00Z");
Date back = Date.from(other);                                 // since Java 8

toInstant() e Date.from(...) são os métodos de conversão modernos, adicionados com o java.time. São as únicas duas chamadas de java.util.Date que você deveria escrever em código novo.

A conversão tem perda em uma direção: Date tem precisão de milissegundos, Instant tem precisão de nanossegundos. A viagem de ida e volta Instant → Date → Instant trunca os nanos abaixo do milissegundo:

Instant high = Instant.parse("2025-11-04T19:30:00.123456789Z");
Instant low = Date.from(high).toInstant();
// low = 2025-11-04T19:30:00.123Z — the 456789 nanos are gone

Para timestamps de servidor isso é aceitável; para captura de eventos de alta resolução, mantenha-se em Instant do início ao fim.

java.sql.Date e java.sql.Timestamp

Os tipos JDBC são subclasses de java.util.Date:

  • java.sql.Date — uma data sem hora (o componente de hora é forçado a 00:00:00 em algum fuso). Nome enganoso; por baixo ainda é um invólucro de milissegundos desde a época.
  • java.sql.Time — uma hora sem data.
  • java.sql.Timestamp — como Date, mas com precisão de nanossegundos.

Todos têm métodos de conversão do JDK 8:

java.sql.Date    sqlDate  = java.sql.Date.valueOf(LocalDate.of(2025, 11, 4));
LocalDate localDate = sqlDate.toLocalDate();

java.sql.Timestamp ts = java.sql.Timestamp.from(Instant.now());
Instant inst = ts.toInstant();

Drivers JDBC modernos também aceitam tipos java.time diretamente via setObject/getObject — para código novo, pule os tipos java.sql.* e use LocalDate/Instant. As conversões existem para código que precisa interoperar com um driver ou framework que ainda não migrou.

Comparação e ordenação

Date implementa Comparable<Date>. A ordem é por milissegundos desde a época — igual ao Instant. Portanto, ordenar uma List<Date> funciona da mesma forma que ordenar uma List<Instant>.

equals compara o long subjacente. Dois valores de Date são iguais se e somente se têm o mesmo valor em milissegundos. O hash funciona (é (int)(time ^ (time >>> 32))), então Date pode ser usado como chave de HashMap — embora, novamente, Instant seja a escolha moderna.

Mutabilidade

A maior armadilha oculta: Date é mutável.

Date d = new Date();
d.setTime(0);                                                // mutates d in place

Isso significa que qualquer método que aceite ou retorne um Date é perigoso — quem chama pode alterar o valor após passá-lo; quem é chamado pode alterar o valor que o chamador mantém. Código de biblioteca faz cópia defensiva (new Date(d.getTime())) de todo Date que recebe. Esse tipo de contabilidade foi completamente eliminado pelo java.time, que tornou todos os tipos imutáveis.

Em código legado, trate qualquer campo Date como se pudesse mudar a qualquer momento. Converta para Instant na fronteira da API se precisar de um snapshot estável.

SimpleDateFormat: o outro item depreciado

Junto com Date em código antigo está o java.text.SimpleDateFormat:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String s = sdf.format(new Date());
Date d = sdf.parse("2025-11-04");

SimpleDateFormat não é thread-safe. Compartilhá-lo entre threads produzirá saída incorreta, exceções ou ambos, de forma intermitente. A solução padrão em código legado é ThreadLocal<SimpleDateFormat>; o substituto moderno é DateTimeFormatter, que é thread-safe e pode ser armazenado em cache.

Se você mantém código com um static SimpleDateFormat, trate-o como um bug conhecido, independentemente de alguém ter relatado uma falha ou não.

Um exemplo prático: interoperabilidade legada

O programa abaixo usa Date como você o encontraria em uma API legada e mostra o caminho de conversão para java.time para cada operação. Leia-o como "aqui está a receita de migração": cada chamada legada tem um equivalente de uma linha com Instant ou ZonedDateTime.

java— editable, runs on the server

O que observar na execução:

  • Date.toString() é exibido no fuso horário padrão da JVM. O mesmo valor de Date aparece de forma diferente em um servidor UTC vs. um laptop com America/New_York. Esse é o defeito central de design que motivou a reformulação do java.time — o valor é UTC, a exibição é local, e a API não oferece uma forma fácil de saber qual visão você está vendo. Se você se importa com qual fuso horário você quer, converta para ZonedDateTime e forneça o fuso explicitamente.
  • now.getYear() retornou 125 para 2025. A convenção year - 1900 foi um erro do Java 1.0 que nunca foi corrigido; o método foi depreciado na versão 1.1 e ainda está lá. Qualquer chamada a getYear() em um Date que retorne "125" ou "85" é um bug esperando para aparecer para um usuário.
  • Instant.parse("...nanoseconds") exibiu nove dígitos de precisão; o mesmo valor após ida e volta por Date.from e de volta perdeu os últimos seis. Para logs de servidor (precisão de milissegundos é suficiente), o truncamento não importa. Para "capturei este evento com temporização de alta precisão," não faça a viagem de ida e volta por Date.
  • O +1 dia legado era now.getTime() + 24L * 60 * 60 * 1000 — aritmética manual, fácil de errar (esquecer o L e causar overflow). O moderno inst.plus(Duration.ofDays(1)) é seguro em relação a tipos e legível em voz alta. Ao migrar, substituir todo cálculo time + N * ms por Duration é o ganho mais fácil.
  • A demonstração de mutabilidade no final mostrou que shared.setTime(0) alterou o valor visto pelas duas referências. Em um código multithread isso é uma condição de corrida; em código de thread única ainda é um ritual de cópia defensiva que o JDK impôs a toda biblioteca. A API moderna nunca exige esse ritual.

O que vem a seguir

java.util.Date é metade da API legada. A outra metade é java.util.Calendar — a classe adicionada no Java 1.1 para dar a Date os acessores de calendário que ela mesma não deveria ter. O próximo capítulo, Java Calendar Class, é o último desta parte e o aborda com a mesma estrutura de receita de migração: cada chamada legada tem um substituto em java.time.

Prática

Prática
Uma biblioteca antiga retorna `java.util.Date` de `getCreatedAt()`. Você quer saber 'isso está no mesmo dia de calendário que hoje em Nova York?'. Qual é o caminho correto?
Uma biblioteca antiga retorna `java.util.Date` de `getCreatedAt()`. Você quer saber 'isso está no mesmo dia de calendário que hoje em Nova York?'. Qual é o caminho correto?
Was this page helpful?