Formatação de Datas em Java
Formate datas e horas em Java como strings com DateTimeFormatter e padrões padrão ou personalizados.
Formatação de datas é a conversão de um valor de data/hora em uma string legível por humanos. Este capítulo aborda DateTimeFormatter — como construí-lo (embutido, localizado ou baseado em padrão), o alfabeto completo de padrões, tratamento de locale e fuso horário, e as armadilhas que produzem saída incorreta. Funciona com todos os tipos de java.time: LocalDate, LocalTime, LocalDateTime, ZonedDateTime e Instant.
Todo tipo java.time tem um toString() que produz a representação ISO-8601: 2025-11-04, 14:30:00, 2025-11-04T14:30:00Z. Isso é adequado para logs e tráfego máquina-a-máquina. Para exibição ao usuário ("November 4, 2025" ou "4 Nov, 14:30") você precisa de um formatter.
A classe é java.time.format.DateTimeFormatter. É o substituto moderno, thread-safe e imutável para o legado java.text.SimpleDateFormat (que não era thread-safe e causava bugs sutis em produção quando compartilhado). Armazene-o como static final e reutilize-o entre threads indefinidamente — sem sincronização, sem cópias defensivas.
Três formas de construir um formatter
// 1. Built-in ISO formatters
DateTimeFormatter.ISO_LOCAL_DATE; // 2025-11-04
DateTimeFormatter.ISO_LOCAL_DATE_TIME; // 2025-11-04T14:30:00
DateTimeFormatter.ISO_OFFSET_DATE_TIME; // 2025-11-04T14:30:00-05:00
DateTimeFormatter.ISO_ZONED_DATE_TIME; // 2025-11-04T14:30:00-05:00[America/New_York]
DateTimeFormatter.ISO_INSTANT; // 2025-11-04T19:30:00Z
DateTimeFormatter.BASIC_ISO_DATE; // 20251104
// 2. Localised formatters
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG); // November 4, 2025 (en-US)
DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM); // Nov 4, 2025, 2:30:00 PM
// 3. Pattern-based formatters
DateTimeFormatter.ofPattern("dd MMM yyyy"); // 04 Nov 2025
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm zzz"); // 2025-11-04 14:30 ESTA API de padrões é a que você mais usará. A localizada é adequada quando você precisa de um formato culturalmente apropriado e quer que o JDK escolha o layout por você.
Formatação
A forma da chamada é simétrica em ambos os lados:
String s = formatter.format(temporal);
String s2 = temporal.format(formatter); // same thing, fluent styleAmbas funcionam. A maioria do código usa a forma fluente.
LocalDate today = LocalDate.now();
String us = today.format(DateTimeFormatter.ofPattern("MM/dd/yyyy")); // 11/04/2025
String iso = today.format(DateTimeFormatter.ISO_LOCAL_DATE); // 2025-11-04
String eu = today.format(DateTimeFormatter.ofPattern("dd.MM.yyyy")); // 04.11.2025O alfabeto de padrões
A tabela completa — aquela a que você sempre voltará. As letras são sensíveis a maiúsculas/minúsculas e a quantidade importa.
| Letra | Significado | Exemplo |
|---|---|---|
y | ano | y → 2025, yy → 25, yyyy → 2025 |
M | mês | M → 11, MM → 11, MMM → Nov, MMMM → November |
d | dia do mês | d → 4, dd → 04 |
E | dia da semana | E → Tue, EEEE → Tuesday |
H | hora 0-23 | H → 14, HH → 14 |
h | hora 1-12 | h → 2, hh → 02 (use com a) |
a | AM/PM | a → PM |
m | minuto | m → 5, mm → 05 |
s | segundo | s → 9, ss → 09 |
S | fração de segundo | SSS → 123 (milissegundos) |
n | nanossegundo | nnnnnnnnn → 123456789 |
z | nome do fuso | z → EST, zzzz → Eastern Standard Time |
Z | offset do fuso | Z → -0500, ZZ → -0500, ZZZZ → GMT-05:00 |
X | offset ISO | X → -05, XX → -0500, XXX → -05:00 |
V | ID do fuso | VV → America/New_York |
Texto literal é envolvido em aspas simples:
DateTimeFormatter.ofPattern("EEEE, MMMM d 'at' h:mm a"); // Tuesday, November 4 at 2:30 PMPara uma aspa simples literal, use duas: ''.
O par mais confundido é m vs M (minúsculo = minuto, maiúsculo = mês) e H vs h (maiúsculo = 0-23, minúsculo = 1-12). A maioria dos bugs do tipo "a hora está errada de uma forma estranha" vêm de um desses erros de digitação.
Localização: Locale e withLocale
Um formatter usa o locale padrão da JVM, a menos que você especifique o contrário. Para saída "sempre em inglês" ou "sempre em alemão", fixe o locale:
DateTimeFormatter english = DateTimeFormatter.ofPattern("EEEE, MMMM d", Locale.US);
DateTimeFormatter german = DateTimeFormatter.ofPattern("EEEE, d. MMMM", Locale.GERMAN);
DateTimeFormatter french = DateTimeFormatter.ofPattern("EEEE d MMMM", Locale.FRENCH);
today.format(english); // Tuesday, November 4
today.format(german); // Dienstag, 4. November
today.format(french); // mardi 4 novembrePara conteúdo renderizado no servidor, sempre passe um locale. O "locale padrão da JVM" é imprevisível em servidores de produção e é a fonte dos bugs "funciona certo no meu laptop, errado no servidor".
Exibição de fuso horário
ZonedDateTime e Instant são os únicos tipos que possuem informações de fuso horário. Formatar um LocalDateTime com um padrão que inclui z ou Z lança exceção — não há fuso para imprimir. Converta primeiro:
ZonedDateTime zdt = ldt.atZone(ZoneId.of("America/New_York"));
zdt.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm z")); // 2025-11-04 14:30 ESTPara Instant, o formatter também precisa de um fuso — Instant não tem nenhum, portanto formatters com campos dependentes de fuso precisam de withZone:
DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm")
.withZone(ZoneId.of("America/New_York"));
f.format(Instant.now()); // formatter supplies the zone for displaySem withZone, formatar um Instant com um padrão no formato de calendário lança exceção.
Formatters estilizados com FormatStyle
As fábricas localizadas oferecem quatro tamanhos canônicos:
DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT); // 11/4/25 (en-US), 04.11.25 (de-DE)
DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); // Nov 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG); // November 4, 2025
DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL); // Tuesday, November 4, 2025Os mesmos quatro tamanhos existem para ofLocalizedTime e ofLocalizedDateTime. Use-os quando quiser que o layout siga o locale do usuário em vez de impor um único formato. Combine-os com .withLocale(...) para fixar o locale.
Exemplo prático: uma data, seis variantes de exibição
O programa abaixo formata um ZonedDateTime de seis formas comuns: ISO para logs de máquina, 12 horas no estilo americano para usuários em inglês, 24 horas no estilo europeu para usuários em alemão, uma forma longa localizada, um padrão personalizado com texto literal embutido, e um formatter Instant-via-withZone para timestamps brutos.
O que observar na execução:
- Os campos
static final DateTimeFormatterem cache têm o formato correto.DateTimeFormatteré imutável e thread-safe; criar um é barato, mas não gratuito, e reutilizar a mesma instância em todos os lugares é o padrão recomendado pelo JDK. Não construa um novo dentro de um loop quente. - O mesmo
ZonedDateTimeproduziu seis strings diferentes dependendo do formatter. O objeto de valor nunca mudou; o formatter é o único que controla o layout. Essa é a separação para a qualDateTimeFormatterexiste — mantenha o tipo de valor limpo e delegue a apresentação ao formatter. - O bloco de "erros comuns de digitação" imprimiu
14:11paraHH:MMporqueMé mês, não minuto. Os dois são o par mais confundido no alfabeto de padrões. Se a hora exibida parecer suspeita com um componente de data, verifique as maiúsculas/minúsculas no padrão. - A escada de
FormatStyleproduziu quatro strings progressivamente mais longas. UseFormatStyle.MEDIUMcomo padrão sensato para "mostrar uma data ao usuário sem pensar muito";LONGeFULLpara contextos onde o ano e o dia da semana precisam ser inequívocos;SHORTpara espaços de UI reduzidos. LocalDateTimecom um padrão contendo fuso lançou exceção — o formatter precisa de dados de fuso, eLocalDateTimenão tem nenhum. A correção é converter (ldt.atZone(zone)) ou remover o campo dependente de fuso do padrão. De qualquer forma, o modo de falha fica claro em tempo de execução.
O que vem a seguir
Formatação é a direção valor → string. O próximo capítulo, Parsing de Datas em Java, é o inverso — string → valor — usando os mesmos padrões de DateTimeFormatter e o mesmo conjunto de ressalvas. Os dois juntos formam a fronteira de E/S para qualquer código que troca datas com usuários, configurações, logs ou APIs remotas.