Java LocalDate
Represente datas sem hora ou fuso horário em Java com LocalDate — criação, manipulação e consultas.
LocalDate é uma data de calendário — ano, mês, dia — sem hora do dia e sem fuso horário. Ela representa a mesma data em qualquer relógio do mundo: quando você escreve LocalDate.of(2025, 11, 4), isso é o quarto de novembro no calendário ISO, ponto final. Sem 14:30 anexado, sem offset UTC, sem ambiguidade entre Tóquio e Honolulu.
Isso a torna o tipo certo para muitas coisas que o legado java.util.Date tratava mal: aniversários, datas de contratos, datas de faturas, a data selecionada por um seletor de datas na interface. Em qualquer lugar onde um dia de calendário é a unidade, LocalDate é a classe.
Criando
As três fábricas padrão:
LocalDate today = LocalDate.now(); // system default zone
LocalDate stardate = LocalDate.of(2025, 11, 4); // year, month (1-12), day (1-31)
LocalDate parsed = LocalDate.parse("2025-11-04"); // ISO-8601 yyyy-MM-ddnow() lê a data atual no fuso horário padrão da JVM. Isso é quase sempre o que você quer; para testes é um problema, e as formas sobrecarregadas com Clock (LocalDate.now(clock)) permitem injetar um relógio fixo. O capítulo sobre análise cobre parse com formatos personalizados; o padrão aceita apenas datas ISO-8601.
Você também pode usar um enum Month em vez de um inteiro 1..12:
LocalDate.of(2025, Month.NOVEMBER, 4); // type-safe; no risk of using 0 for JanuarySe você já escreveu new GregorianCalendar(2025, 11, 4) e obteve dezembro (porque a API legada usa meses com base zero), a forma com enum é a atualização que você quer.
Inspecionando
O catálogo de acessores:
int year = date.getYear();
Month month = date.getMonth(); // enum
int monthVal = date.getMonthValue(); // 1-12
int day = date.getDayOfMonth();
DayOfWeek dow = date.getDayOfWeek(); // enum: MONDAY, TUESDAY, ...
int dayOfYear = date.getDayOfYear(); // 1-366
boolean leap = date.isLeapYear();
int monthLen = date.lengthOfMonth(); // 28-31
int yearLen = date.lengthOfYear(); // 365 or 366Month e DayOfWeek são enums. Use-os; eles tornam o código que compara com um dia ou mês específico dramaticamente mais claro:
if (date.getDayOfWeek() == DayOfWeek.MONDAY) ... // type-safe
if (date.getMonth() == Month.NOVEMBER) ... // no off-by-one riskCada enum possui seus próprios métodos auxiliares — Month.length(boolean leap), DayOfWeek.getValue() retornando 1-7 com segunda-feira = 1, e DayOfWeek.plus(7) para "o mesmo dia, n dias a partir de agora."
Modificando — todo método retorna uma nova instância
Os métodos aritméticos:
date.plusDays(7); // a week later
date.plusWeeks(2);
date.plusMonths(1); // careful: month length varies
date.plusYears(1);
date.minusDays(30);
date.minusYears(5);E as formas de "substituir um campo":
date.withYear(2026);
date.withMonth(1);
date.withDayOfMonth(1);
date.withDayOfYear(1); // first day of the yearTodos esses retornam um novo LocalDate. O original permanece inalterado. date.plusDays(7) sem capturar o resultado é uma operação sem efeito — e um bug que todos já escrevemos pelo menos uma vez.
O aviso "o comprimento do mês varia" para plusMonths: quando adicionar um mês resultaria em um dia que não existe no mês de destino, java.time limita ao último dia. LocalDate.of(2025, 1, 31).plusMonths(1) é 2025-02-28 (ou 02-29 em ano bissexto), não 2025-03-03. O comportamento é documentado e consistente, mas significa que plusMonths(1) e minusMonths(1) nem sempre são inversos.
Comparando
date.isBefore(other);
date.isAfter(other);
date.isEqual(other); // same as equals here; useful on ZonedDateTime
date.compareTo(other); // -1 / 0 / +1LocalDate implementa Comparable<LocalDate>, portanto ordena naturalmente em qualquer coleção. Para "esta data está em [início, fim]?" a forma típica é !date.isBefore(start) && !date.isAfter(end).
Distância: until e ChronoUnit.between
Quantos dias há entre duas datas?
long days = ChronoUnit.DAYS.between(start, end); // a long; signed
long weeks = ChronoUnit.WEEKS.between(start, end);
long months = ChronoUnit.MONTHS.between(start, end);
Period diff = start.until(end); // a Period (years/months/days)ChronoUnit.X.between é a chamada certa para "quantos X inteiros existem entre esses?" until retorna um Period, que é o detalhamento em forma de calendário — útil para "você é membro há 2 anos, 3 meses e 14 dias."
Observe a convenção de sinal: between(start, end) é positivo quando end é posterior a start, negativo caso contrário.
O atalho "que dia da semana é..."
O pacote de ajustadores temporais oferece os predicados que você calcularia manualmente:
import static java.time.temporal.TemporalAdjusters.*;
date.with(firstDayOfMonth());
date.with(lastDayOfMonth());
date.with(firstDayOfNextMonth());
date.with(next(DayOfWeek.MONDAY)); // next Monday strictly after `date`
date.with(nextOrSame(DayOfWeek.MONDAY)); // today if today is Monday, else next
date.with(previousOrSame(DayOfWeek.SUNDAY));
date.with(lastInMonth(DayOfWeek.FRIDAY)); // last Friday of the monthO capítulo sobre Temporal Adjusters cobre esses em profundidade. Por agora, a conclusão: não escreva "próxima segunda-feira após esta data" manualmente; os ajustadores já têm isso.
Aviso sobre fuso horário
LocalDate não tem fuso, então LocalDate.now() precisa escolher um para saber qual dia de calendário "agora" é. O padrão é o fuso padrão da JVM (ZoneId.systemDefault()). Se você estiver rodando em um servidor configurado para UTC às 23:30 horário local de Nova York, LocalDate.now() retorna a data de amanhã na perspectiva de Nova York — porque o fuso da JVM diz que já passou da meia-noite UTC.
Para uma data local em um fuso conhecido, passe o fuso explicitamente:
LocalDate tokyoToday = LocalDate.now(ZoneId.of("Asia/Tokyo"));Isso acontece em produção exatamente quando o laptop do desenvolvedor está em um fuso diferente do servidor implantado. Seja explícito quando o fuso importa.
Um exemplo trabalhado: aritmética de data de fatura
O programa abaixo usa LocalDate para o tipo de trabalho que um pequeno sistema de faturamento faria — gerar uma data de fatura, calcular datas de vencimento, contar dias em aberto, calcular o final do mês e encontrar o próximo dia útil. É a forma realista do código com LocalDate.
O que extrair da execução:
LocalDate.of(2025, Month.NOVEMBER, 4)foi a forma segura. A sobrecarga com inteiro (2025, 11, 4) funciona, mas o enumMonthtorna impossível usar 0 para janeiro — o erro legado doGregorianCalendar. Quando o segundo argumento pode ser qualquer um, use o enum.plusDays(30)retornou um novoLocalDate; imprimir o original ao final do programa mostrou-o inalterado. Todo método aritmético ewith*segue essa regra, o que torna o tipo seguro para threads por construção. Nenhuma cópia defensiva necessária; passar umLocalDatepara um método é sempre seguro.- A demonstração de
plusMonths(1)mostrou o comportamento de limitação: 31 de janeiro + 1 mês = 28 de fevereiro (ou 29 em ano bissexto). O comportamento é documentado e consistente, masjan31.plusMonths(1).minusMonths(1)retorna28 de janeiro, não31 de janeiro. Ir e voltar esperando o original funciona paraplusDays/minusDays, não paraplusMonths/minusMonths. - Os ajustadores temporais (
lastDayOfMonth,firstDayOfNextMonth,nextOrSame(MONDAY)) substituíram percursos manuais de calendário com várias linhas. Encadeados, expressam "a primeira segunda-feira no ou após o primeiro do próximo mês" em dois ajustadores. O próximo capítulo sobre LocalTime e o capítulo dedicado a Temporal Adjusters vão mais fundo. ChronoUnit.DAYS.between(invoice, today)retornou umlongcom sinal. O companheiroinvoiceDate.until(today)retornou umPeriod— em forma de calendário, com campos separados de ano/mês/dia. Os dois respondem perguntas diferentes:ChronoUnit.Xpara "quantos X inteiros,"Periodpara "em forma amigável ao calendário." Escolha o que melhor corresponde ao formato de saída desejado.
O que vem a seguir
LocalDate foi o lado da data. O próximo capítulo, Java LocalTime, é seu espelho — hora do dia, sem data anexada e sem fuso. Mesma API fluente, classe menor, mesmas garantias de imutabilidade.