W3docs

Análise de Datas em Java

Converta strings em objetos de data e hora em Java com DateTimeFormatter e trate exceções de análise.

A análise é o oposto da formatação. Mesmo DateTimeFormatter, mesmo alfabeto de padrões, mesmas ressalvas — mas lendo uma string em vez de escrevê-la. Cada tipo java.time possui um factory parse(...); com um padrão padrão (ISO-8601) ele recebe um argumento, e com um padrão personalizado recebe um formatador.

LocalDate    d  = LocalDate.parse("2025-11-04");                                       // ISO default
LocalTime    t  = LocalTime.parse("14:30:00");
LocalDateTime dt = LocalDateTime.parse("2025-11-04T14:30:00");                          // note the T
ZonedDateTime zdt = ZonedDateTime.parse("2025-11-04T14:30:00-05:00[America/New_York]");
Instant       i = Instant.parse("2025-11-04T19:30:00Z");                                // trailing Z mandatory

Cada um desses usa o formatador padrão do tipo — ISO-8601 estrito. Para qualquer coisa que não seja ISO, construa um formatador e passe-o.

Análise com padrão personalizado

DateTimeFormatter f = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate d = LocalDate.parse("04/11/2025", f);                                         // British DMY

O alfabeto de padrões é o mesmo que para formatação — dd MMM yyyy, HH:mm:ss, MM/dd/yyyy h:mm a. A regra de correspondência é estrita por padrão: cada caractere literal no padrão deve aparecer na entrada de forma exata, e cada campo deve ter o número correto de dígitos.

DateTimeFormatter dmy = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate.parse("4/11/2025", dmy);                                                       // FAILS: "dd" requires 2 digits
LocalDate.parse("04/11/2025", dmy);                                                      // OK

Se sua entrada às vezes tem um único dígito, use d/M/yyyy (que aceita 1-2 dígitos) ou construa o formatador com DateTimeFormatterBuilder e parseStrict(false). A forma com letra única é a correção mais simples.

O locale importa na análise

A mesma situação do locale que na formatação: nomes de meses (MMMM) e nomes de dias da semana (EEEE) são específicos do idioma, portanto o formatador precisa saber em qual idioma a entrada está escrita.

DateTimeFormatter englishDay = DateTimeFormatter.ofPattern("EEEE, MMMM d yyyy", Locale.US);
DateTimeFormatter germanDay  = DateTimeFormatter.ofPattern("EEEE, d. MMMM yyyy",  Locale.GERMAN);

LocalDate.parse("Tuesday, November 4 2025", englishDay);   // 2025-11-04
LocalDate.parse("Dienstag, 4. November 2025", germanDay);  // 2025-11-04 (German month name)

Observe o yyyy em ambos os padrões. Para produzir um LocalDate, a entrada deve fornecer um ano — um padrão como "EEEE, MMMM d" analisa, mas apenas para um TemporalAccessor sem campo de ano, então LocalDate.parse nele lança uma exceção. Se suas strings realmente não têm ano, analise para um TemporalAccessor e combine-o com um ano você mesmo.

Sem um locale explícito, Locale.getDefault() é usado — e o locale padrão de uma JVM de servidor é imprevisível. Sempre passe um locale ao analisar nomes de meses ou dias de uma string que o usuário possa digitar. O oposto de "sempre formatar com um locale" se aplica aqui.

DateTimeParseException

Uma análise com falha lança DateTimeParseException (uma subclasse de RuntimeException, portanto não declarada em parse). A mensagem informa tanto a posição quanto o que era esperado:

try {
  LocalDate.parse("2025-13-45");                              // month 13, day 45
} catch (DateTimeParseException e) {
  e.getParsedString();                                         // "2025-13-45"
  e.getErrorIndex();                                           // index where parsing gave up
  e.getMessage();                                              // human description
}

Dois tipos distintos de falhas chegam aqui:

  • Incompatibilidade de formato. A string não se encaixa no padrão de forma alguma — "04 nov 2025" contra "dd-MM-yyyy".
  • Valor fora do intervalo. A string se encaixa no padrão, mas um valor é impossível — mês 13, dia 32.

Ambos lançam a mesma classe. Capture e reporte; nunca ignore silenciosamente.

Anos com dois dígitos e a armadilha do MAX_VALUE

O padrão yy (ano com dois dígitos) tem um padrão documentado, mas surpreendente: ele analisa para o ano mais próximo de hoje dentro de uma janela de 100 anos. LocalDate.parse("11/04/25", DateTimeFormatter.ofPattern("MM/dd/yy")) é 2025-11-04 em 2025 e 2125-11-04 em 2076. Isso é um recurso para casos "próximos de hoje" e uma armadilha para dados de arquivo.

A correção é usar yyyy sempre que a entrada tiver quatro dígitos e ser explícito sobre a janela do século quando não tiver:

DateTimeFormatter f = new DateTimeFormatterBuilder()
    .appendPattern("MM/dd/")
    .appendValueReduced(ChronoField.YEAR, 2, 2, 1950)         // window starts at 1950
    .toFormatter();

Se você estiver lidando com dados legados com yy, documente a janela no código. O padrão é "pivô rotativo em torno do ano atual," o que não é o que você quer para "todos os meus dados são dos anos 1980."

Análise sem comprometimento com um tipo

DateTimeFormatter.parse(String) retorna um TemporalAccessor — o fundo da hierarquia de tipos. Útil quando a entrada pode ser um LocalDate ou um LocalDateTime:

TemporalAccessor ta = DateTimeFormatter.ISO_DATE_TIME.parseBest(
    "2025-11-04T14:30:00",
    LocalDateTime::from,                                       // preferred
    LocalDate::from);                                          // fallback

parseBest(text, ...queries) tenta o método from de cada tipo em ordem e retorna o primeiro que tiver sucesso. O resultado precisa de instanceof para ser usado de forma específica:

if (ta instanceof LocalDateTime ldt) ...
else if (ta instanceof LocalDate ld) ...

Para a maioria dos códigos, chamar diretamente o parse(...) do tipo específico é mais simples. parseBest é para o caso em que você aceita múltiplos formatos (uma coluna CSV que pode ser uma data ou uma data-hora).

Análise leniente com o builder

DateTimeFormatterBuilder permite montar um formatador mais tolerante:

DateTimeFormatter lenient = new DateTimeFormatterBuilder()
    .appendPattern("yyyy-MM-dd[ HH:mm[:ss]]")                  // optional sections in []
    .parseLenient()                                            // accept missing leading zeros etc.
    .parseCaseInsensitive()                                    // ignore case on month/day names
    .toFormatter(Locale.US);

A sintaxe de colchetes [...] marca uma seção opcional — esse padrão analisa tanto "2025-11-04" (sem hora) quanto "2025-11-04 14:30" (com hora). Combinado com parseLenient e parseCaseInsensitive, você pode construir um formatador que aceita uma gama mais ampla de entradas sem escrever um parser personalizado.

Isso é excessivo para código que controla ambos os lados. Use o padrão estrito, a menos que você esteja lendo entrada do usuário ou dados legados.

Instant.parse é estrito

Instant.parse("2025-11-04T19:30:00Z") funciona. O Z final (UTC) é obrigatório; qualquer outro offset (-05:00, +09:00) precisa de OffsetDateTime.parse ou ZonedDateTime.parse primeiro, depois .toInstant():

Instant inst = OffsetDateTime.parse("2025-11-04T14:30:00-05:00").toInstant();

Essa é a conversão canônica quando uma API externa fornece strings ISO-8601 com offsets de fuso horário, mas sem zona IANA.

Um exemplo prático: ler uma config, analisar, reagir a entradas inválidas

O programa abaixo analisa três strings com formato de data de uma config sintética: uma data ISO, uma data com padrão personalizado e um instant ISO. Em seguida, demonstra o builder leniente com uma seção de hora opcional, a API parseBest para "uma data ou uma data-hora," e o modo de falha quando a entrada não corresponde.

java— editable, runs on the server

O que tirar da execução:

  • Os três parsers ISO padrão aceitaram exatamente a forma padrão: yyyy-MM-dd para LocalDate, a forma separada por T para LocalDateTime e a forma terminada em Z para Instant. Sem flexibilidade, sem adivinhação — e esse é o ponto. Se sua entrada se encaixa, nenhum formatador é necessário; se não se encaixa, construir um é uma linha.
  • O formatador leniente aceitou três formas diferentes de entrada — apenas data, data com hora, data com hora e segundos — porque a seção entre colchetes [...] é opcional. parseBest(text, LocalDateTime::from, LocalDate::from) escolheu o tipo mais rico que cada entrada suportava. Esse é o padrão correto quando você aceita datas inseridas pelo usuário ou de configuração com precisão variável.
  • OffsetDateTime.parse(wire).toInstant() foi a ponte canônica de "um timestamp ISO-8601 com offset" para Instant. O próprio Instant.parse só aceita UTC com sufixo Z; qualquer outra coisa precisa passar por OffsetDateTime (ou ZonedDateTime) primeiro. A conversão é uma linha em cada direção.
  • A análise com locale alemão só funcionou porque o formatador foi construído com Locale.GERMAN. O locale padrão teria rejeitado "November" se a JVM estivesse rodando em alemão (que espera que o "November" de Locale.GERMAN seja correspondido com os nomes alemães). Sempre fixe o locale no ponto de análise — o formatador por si só não é suficiente; o locale controla a resolução de nomes de meses e dias.
  • Os dois blocos de falha lançaram DateTimeParseException com informações úteis de posição. getErrorIndex é onde o parser desistiu; getParsedString é a entrada como o parser a viu. Mostre isso em erros para o usuário — "não foi possível analisar a data no caractere 5" é dramaticamente mais útil do que "data inválida."

O que vem a seguir

Formatação e análise encerram o limite de strings. O próximo capítulo, Java Temporal Adjusters, volta ao lado dos valores e aborda os ajustadores integrados (firstDayOfMonth, nextOrSame(MONDAY), etc.) e como escrever o seu próprio — útil sempre que a data que você quer depende da data que você tem ("primeira terça-feira após o dia 15").

Prática

Prática
Um formulário web permite que os usuários digitem uma data no formato '04/11/2025' (dia/mês/ano). Seu código usa `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` e então pergunta 'essa data está no futuro?'. Um usuário digita '4/11/2025' (sem zero à esquerda no dia) e a análise lança uma exceção. Qual é a correção mais simples?
Um formulário web permite que os usuários digitem uma data no formato '04/11/2025' (dia/mês/ano). Seu código usa `LocalDate.parse(input, DateTimeFormatter.ofPattern('dd/MM/yyyy'))` e então pergunta 'essa data está no futuro?'. Um usuário digita '4/11/2025' (sem zero à esquerda no dia) e a análise lança uma exceção. Qual é a correção mais simples?
Was this page helpful?