Java Period
Represente quantidades baseadas em datas (anos, meses, dias) em Java com Period.
Period é o irmão baseado em calendário de Duration. Enquanto Duration é "X segundos mais Y nanos," Period é "X anos, Y meses, Z dias." É o tipo certo para qualquer duração que você expressaria em termos de calendário: "período de teste de 30 dias," "assinatura anual," "aviso prévio de dois meses," "adicionar um ciclo de cobrança à data de renovação."
Os dois nunca se misturam. Duration.ofDays(30) é exatamente 30 × 24 × 3600 segundos. Period.ofDays(30) são 30 dias de calendário, o que geralmente, mas nem sempre, equivale a 30 × 24 horas (as transições de horário de verão adicionam ou removem uma hora). Para "exatamente aquela quantidade de segundos," use Duration. Para "o dia do calendário que é N dias depois," use Period.
Criação
Period.ofDays(30);
Period.ofWeeks(2); // stored as 14 days
Period.ofMonths(3);
Period.ofYears(1);
Period.of(1, 6, 0); // 1 year, 6 months, 0 days
Period.between(startDate, endDate); // takes LocalDate (not LocalDateTime)
Period.parse("P1Y2M3D"); // ISO-8601: P[years]Y[months]M[days]DA forma em string é PnYnMnD — P1Y2M3D é um ano, dois meses, três dias. O prefixo P é obrigatório. Sem T (isso faria um Duration); sem horas, minutos ou segundos (esses não se encaixam).
Period.between(start, end) recebe dois LocalDates e retorna uma decomposição da diferença:
Period age = Period.between(LocalDate.of(1990, 3, 15), LocalDate.of(2025, 11, 4));
// P35Y7M20D — 35 years, 7 months, 20 daysEsse é o idioma padrão para "calcular uma idade." O resultado é uma decomposição, não um número único — há 35 anos completos, depois 7 meses por cima, depois 20 dias. Para reduzir a uma única contagem, use ChronoUnit.YEARS.between(...), que retorna um long.
Inspeção
Period p = Period.of(1, 6, 14);
p.getYears(); // 1
p.getMonths(); // 6
p.getDays(); // 14
p.toTotalMonths(); // 1 * 12 + 6 = 18 (years + months, ignoring days)
p.isZero(); // false
p.isNegative(); // true if any component is negativeTrês acessores para os três componentes, mais toTotalMonths para uma agregação rápida. Não existe toTotalDays — isso exigiria conhecer o contexto do calendário (um ano tem 365 ou 366 dias; um mês tem 28-31).
Aritmética
p.plus(Period.ofMonths(1));
p.plusYears(1);
p.plusMonths(6);
p.plusDays(14);
p.minus(Period.ofDays(7));
p.multipliedBy(3);
p.negated();
p.normalized(); // collapse extra months into yearsnormalized() é interessante: ele colapsa qualquer contagem de meses de 12 ou mais em anos. Period.of(0, 14, 0).normalized() é Period.of(1, 2, 0). Ele não afeta os dias — não existe "normalizar 31 dias em 1 mês e 1 dia" porque os meses não têm comprimento constante.
Adicionando a uma data
Period é um TemporalAmount. Qualquer Temporal semelhante a uma data o aceita:
LocalDate maturity = LocalDate.of(2025, 11, 4).plus(Period.ofMonths(6));
LocalDate retirement = LocalDate.of(1990, 3, 15).plus(Period.ofYears(65));
LocalDateTime renewal = LocalDateTime.of(2025, 11, 4, 9, 0).plus(Period.ofYears(1));
ZonedDateTime nextBill = zdt.plus(Period.ofMonths(1));Adicionar a parte de mês ou ano de um Period a um Instant lança UnsupportedTemporalTypeException — um Instant é um ponto na linha do tempo sem calendário, então o JDK recusa calcular "um instante um mês depois" sem um fuso horário. (A parte de dia é válida: Instant.plus(Period.ofDays(1)) funciona, porque o JDK trata um dia como exatamente 86.400 segundos. São apenas os meses e anos que não têm comprimento fixo e, portanto, não têm significado em um Instant.) Quando você precisar de aritmética de calendário, converta via ZonedDateTime:
Instant nextMonth = inst.atZone(ZoneId.of("UTC"))
.plus(Period.ofMonths(1))
.toInstant();Isso é verbosidade intencional — a conversão é o lugar onde você fornece as informações de calendário ausentes.
A regra de clamping de plusMonths do LocalDate aplica-se à aritmética com Period da mesma forma: 31 de janeiro + Period.ofMonths(1) é 28 de fevereiro, não 3 de março.
Period não normaliza entre componentes
Um comportamento sutil: Period.of(1, 0, 365) não é igual a Period.of(2, 0, 0), mesmo que descrevam o mesmo comprimento quando adicionados a uma data típica. A classe armazena a decomposição literalmente e compara por estrutura:
Period.of(1, 0, 365).equals(Period.of(2, 0, 0)); // false
Period.of(0, 14, 0).equals(Period.of(1, 2, 0)); // false (until normalized())
Period.of(0, 14, 0).normalized().equals(Period.of(1, 2, 0)); // truePara "este período é pelo menos um ano independentemente de como está decomposto," compare nas datas: start.plus(p1).isEqual(start.plus(p2)) é a única verificação totalmente correta.
Distância: Period.between vs ChronoUnit.between
Period diff = Period.between(start, end); // calendar breakdown
long days = ChronoUnit.DAYS.between(start, end); // single long
long months = ChronoUnit.MONTHS.between(start, end);
long years = ChronoUnit.YEARS.between(start, end);Os dois respondem perguntas diferentes:
Period.between(start, end)retorna "1 ano, 6 meses, 14 dias" — útil quando você quer exibir uma decomposição.ChronoUnit.DAYS.between(start, end)retorna567(ou qualquer que seja a contagem literal de dias) — útil quando você quer comparar ou acumular.
Use o segundo quando precisar fazer aritmética sobre o resultado. Use o primeiro quando quiser mostrar para um humano.
Um exemplo prático: assinaturas, testes e idades
O programa abaixo usa Period para um pequeno cenário de assinatura: o teste termina um mês após o cadastro, a data de renovação se repete todo ano, a idade do cliente é calculada a partir da data de nascimento, e o comportamento de clamping nos limites do mês é tornando explícito. Ele também mostra o contraste com Duration para "o mesmo intervalo em tempo decorrido."
O que extrair da execução:
- Adicionar
Period.ofMonths(1)a 31 de janeiro produziu 28 de fevereiro — a mesma regra de clamping do LocalDate.Period.plusMonths(1).minusMonths(1)nem sempre é a identidade. Quando você estiver calculando datas de cobrança próximas ao fim do mês, projete em torno do clamping explicitamente (ex.: sempre cobrar no 1º do mês seguinte) em vez de assumir simetria de ida e volta. Period.between(birth, today)retornou uma decomposição em calendário — anos, meses, dias. Para "o cliente é adulto?" você querChronoUnit.YEARS.between(birth, today) >= 18, nãoage.getYears() >= 18. Ambos dão a mesma resposta neste caso, mas respondem perguntas diferentes em geral —ChronoUnit.YEARS.betweené o total de anos inteiros,age.getYears()é o componente de anos da decomposição.Period.of(0, 14, 0).normalized()tornou-sePeriod.of(1, 2, 0). A contagem de dias não foi tocada — os dias não podem ser normalizados sem saber quais meses estão envolvidos. Se você construir umPerioda partir de aritmética e quiser uma representação "limpa," chamenormalizedantes de armazenar ou exibir.P1Y.equals(P12M)foifalse, eP1Y.equals(P365D)também foifalse. A igualdade é estrutural, não por comprimento. Aplicado a2024-01-31(um ano bissexto),+ P1Ye+ P12Mambos deram2025-01-31, mas+ P365Ddeu2025-01-30— um dia a menos, porque 2024 tem 366 dias. Portanto, mesmo "o mesmo comprimento" depende da data à qual você o aplica. Quando você realmente quer "esses dois períodos produzem a mesma data final?", calcule ambas as datas finais e compare comLocalDate.isEqual. A forma.normalized()corrige o caso ano/mês, mas nunca o caso de dias.- A chamada
inst.plus(Period.ofMonths(1))lançouUnsupportedTemporalTypeException. UmInstantnão tem calendário, então um mês (cujo comprimento varia) não tem significado nele. A parte de dia de umPeriodé válida em umInstant— um dia é 86.400 segundos fixos — mas os meses e anos não são. Converta através deZonedDateTimeprimeiro quando precisar de aritmética de calendário; o sistema de tipos obriga você a escolher um fuso horário explicitamente. A falha espelhada do capítulo de Duration (Durationem umLocalDate) é o mesmo design: o JDK recusa inventar o contexto ausente.
O que vem a seguir
Period encerra o par "durações de tempo." Os próximos dois capítulos cobrem a fronteira string ↔ valor: Java Date Formatting para converter valores java.time em strings, e Java Date Parsing para o inverso. Ambos rodam no DateTimeFormatter, que é o substituto moderno e thread-safe para o legado SimpleDateFormat.