W3docs

Java LocalDateTime

Combine data e hora sem fuso horário em Java com LocalDateTime. Veja como criar, calcular e converter.

LocalDateTime é LocalDate e LocalTime unidos — uma data do calendário e um horário do dia, mas ainda sem fuso horário. É o valor natural para "este evento ocorreu às 14:30 do dia 4 de novembro", e é o tipo java.time mais utilizado em codebases reais que não lidam com usuários globais.

A API fluente tem a mesma forma que as duas metades — as mesmas factories now/of/parse, os mesmos modificadores plusX/minusX/withX, as mesmas comparações isBefore/isAfter. As partes interessantes são as novas: combinar com uma data ou hora, decompor novamente e a conversão explícita para ZonedDateTime quando o fuso finalmente importa.

Criando

LocalDateTime now    = LocalDateTime.now();                  // JVM default zone
LocalDateTime made   = LocalDateTime.of(2025, 11, 4, 14, 30);
LocalDateTime made2  = LocalDateTime.of(2025, Month.NOVEMBER, 4, 14, 30, 15);
LocalDateTime made3  = LocalDateTime.of(LocalDate.of(2025, 11, 4), LocalTime.of(14, 30));
LocalDateTime parsed = LocalDateTime.parse("2025-11-04T14:30:00");   // ISO-8601

O formato ISO-8601 tem um T literal entre a data e a hora — esse é o separador padrão. LocalDateTime.parse("2025-11-04 14:30:00") (espaço em vez de T) não é analisado com o padrão; você precisaria de um DateTimeFormatter personalizado para isso.

Decomposição

LocalDate date = dt.toLocalDate();
LocalTime time = dt.toLocalTime();

São o inverso de LocalDate.atTime(time) / LocalTime.atDate(date). Ambos são projeção pura — sem perda de informação, sem introdução de fuso.

Todos os acessores de uma vez

LocalDateTime herda o menu de acessores de ambas as metades:

dt.getYear();          dt.getMonth();        dt.getMonthValue();
dt.getDayOfMonth();    dt.getDayOfWeek();    dt.getDayOfYear();
dt.getHour();          dt.getMinute();       dt.getSecond();          dt.getNano();

Mesmos nomes, mesma semântica. Não é necessário chamar toLocalDate() ou toLocalTime() para acessar as partes individuais — todas estão disponíveis diretamente.

Aritmética além do limite

A diferença crucial em relação ao LocalTime: LocalDateTime.plusHours(3) em 23:00 não volta silenciosamente ao início. Ele avança para o dia seguinte:

LocalDateTime late = LocalDateTime.of(2025, 11, 4, 23, 0);
late.plusHours(3);   // 2025-11-05T02:00 — date advanced as expected

Esse é o principal motivo para usar LocalDateTime em vez de LocalTime em qualquer cálculo que possa cruzar a meia-noite. A matemática é consistente com o que se espera de um relógio real que sabe que dia é.

dt.plusDays(7);          dt.plusHours(36);        dt.plusMinutes(150);
dt.minusYears(1);        dt.minusSeconds(45);

dt.withYear(2026);       dt.withHour(0);          dt.withMinute(0);

A regra de truncamento de plusMonths do LocalDate também se aplica aqui: LocalDateTime.of(2025, 1, 31, 12, 0).plusMonths(1) é 2025-02-28T12:00, não 2025-03-03T12:00. O truncamento ocorre apenas no componente de data; a hora não é alterada.

O fuso está ausente por design

Um LocalDateTime não é um momento na linha do tempo global. LocalDateTime.of(2025, 11, 4, 9, 0) pode ser 9h em Nova York, 9h em Berlim ou 9h em Tóquio — três Instants muito diferentes, e LocalDateTime não dirá qual. Se dois LocalDateTimes são iguais, isso significa que as strings de data e hora são iguais; não significa que os momentos subjacentes são iguais.

Isso é uma característica, não um bug. Para "o contrato é assinado às 14:00 no horário local de onde o signatário estiver", LocalDateTime é exatamente a forma certa. Para "o servidor recebeu a requisição às...", é a forma errada — use Instant. Para "a reunião começa às 14:00 no horário de Nova York", também está errado — use ZonedDateTime.

Para converter para um momento com fuso, você precisa adicionar o fuso explicitamente:

ZonedDateTime ny = ldt.atZone(ZoneId.of("America/New_York"));
Instant       inst = ldt.atZone(ZoneId.systemDefault()).toInstant();

O atZone(...) é a chamada essencial — é o momento em que o sistema de tipos obriga você a decidir qual fuso você quer dizer. Após a decisão, a conversão para Instant é mecânica. Os dois próximos capítulos (ZonedDateTime, Instant) cobrem as formas com fuso e global em detalhes.

Comparando

dt.isBefore(other);
dt.isAfter(other);
dt.isEqual(other);
dt.compareTo(other);

A ordenação é lexicográfica por (date, time). O mesmo aviso de antes: dois LocalDateTimes são comparados pelas suas representações em string de data e hora, não pelos momentos subjacentes — porque não há momentos subjacentes sem um fuso.

Distância

ChronoUnit.X.between funciona diretamente:

long minutes = ChronoUnit.MINUTES.between(start, end);
long days = ChronoUnit.DAYS.between(start, end);
Duration d = Duration.between(start, end);

Duration.between funciona com LocalDateTime (funciona em qualquer Temporal). Para aritmética puramente calendária — "quantos meses entre estes dois LocalDateTimes" — use ChronoUnit.MONTHS.between, que retorna um long, ou Period.between(start.toLocalDate(), end.toLocalDate()) para a decomposição em formato calendário.

Um exemplo prático: agendamento através da meia-noite

O programa abaixo usa LocalDateTime para um pequeno trecho de código de agendamento: um turno noturno que começa às 22:00 e termina às 06:00, calculando a duração corretamente através da meia-noite; arredondando "agora" para o próximo quarto de hora; encontrando a próxima ocorrência de uma reunião recorrente às 09:30; e demonstrando a regra de que o fuso deve ser adicionado explicitamente ao converter para um momento no tempo.

java— editable, runs on the server

O que extrair da execução:

  • Duration.between(startShift, endShift) produziu PT8H. O turno cruzou a meia-noite, e os componentes de data carregaram o transporte — não havia ambiguidade. O mesmo cálculo com LocalTimes puros teria retornado PT-16H (o problema do LocalTime do capítulo anterior). Para aritmética que pode cruzar a meia-noite, LocalDateTime é o tipo certo.
  • Começando de 20:00, plusHours(3) ficou em 11-04 (23:00, sem cruzar a meia-noite); plusHours(5) avançou para 11-05T01:00. A família plus/minus em LocalDateTime propaga os transportes corretamente por toda a cadeia Y/M/D/h/m/s/ns. Não é necessário tratamento especial no seu código.
  • O bloco "próxima reunião às 09:30" construiu o horário de hoje às 09:30 com três chamadas withX, depois escolheu entre hoje e amanhã com base em isBefore. Essa é a forma comum para "o próximo evento recorrente neste horário do dia" — pequeno o suficiente para deixar inline, comum o suficiente para extrair para um helper se você tiver vários deles.
  • O bloco "mesmo LocalDateTime, fusos diferentes" produziu dois Instants diferentes com seis horas de diferença. Esse é o motivo central pelo qual LocalDateTime não afirma ser um momento. A classe recusa-se a fingir que data e hora sozinhas são informação suficiente; é um rótulo em um relógio de parede em algum lugar, e qual parede depende do fuso que você fornecer.
  • A verificação final de imutabilidade mostrou now inalterado após plusDays(7).withHour(0).withMinute(0). Essa garantia se aplica a todas as operações, todos os encadeamentos, todos os helpers — não há como mutar um LocalDateTime. Passe-o livremente, compartilhe-o entre threads, armazene-o em um Map.

O que vem a seguir

LocalDateTime é o último dos três tipos "Local" — sem fuso, sem afirmar ser um momento. O próximo capítulo, Java ZonedDateTime, adiciona o fuso explicitamente: um LocalDateTime mais um ZoneId mais o offset resolvido para aquele horário local naquele fuso, que juntos fixam um momento real na linha do tempo global.

Prática

Prática
Dois desenvolvedores — um em Nova York, outro em Berlim — ambos constroem o valor `LocalDateTime.of(2025, 11, 4, 14, 0)`. Esses são o mesmo momento no tempo?
Dois desenvolvedores — um em Nova York, outro em Berlim — ambos constroem o valor `LocalDateTime.of(2025, 11, 4, 14, 0)`. Esses são o mesmo momento no tempo?
Was this page helpful?