W3docs

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]D

A forma em string é PnYnMnDP1Y2M3D é 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 days

Esse é 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 negative

Trê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 years

normalized() é 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));  // true

Para "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) retorna 567 (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."

java— editable, runs on the server

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ê quer ChronoUnit.YEARS.between(birth, today) >= 18, não age.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-se Period.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 um Period a partir de aritmética e quiser uma representação "limpa," chame normalized antes de armazenar ou exibir.
  • P1Y.equals(P12M) foi false, e P1Y.equals(P365D) também foi false. A igualdade é estrutural, não por comprimento. Aplicado a 2024-01-31 (um ano bissexto), + P1Y e + P12M ambos deram 2025-01-31, mas + P365D deu 2025-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 com LocalDate.isEqual. A forma .normalized() corrige o caso ano/mês, mas nunca o caso de dias.
  • A chamada inst.plus(Period.ofMonths(1)) lançou UnsupportedTemporalTypeException. Um Instant não tem calendário, então um mês (cujo comprimento varia) não tem significado nele. A parte de dia de um Period é válida em um Instant — um dia é 86.400 segundos fixos — mas os meses e anos não são. Converta através de ZonedDateTime primeiro 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 (Duration em um LocalDate) é 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.

Prática

Prática
Um usuário se cadastra em 31 de janeiro de 2025. Seu código de cobrança calcula o próximo débito com `signupDate.plus(Period.ofMonths(1))`. Qual é a data do próximo débito, e o que você deve saber sobre o comportamento?
Um usuário se cadastra em 31 de janeiro de 2025. Seu código de cobrança calcula o próximo débito com `signupDate.plus(Period.ofMonths(1))`. Qual é a data do próximo débito, e o que você deve saber sobre o comportamento?
Was this page helpful?