Especialização em Sistemas com Java 21 LTS • Módulo de Modelagem Orientada a Objetos


🗺️ Mapa Conceitual do Tópico

flowchart TD
    subgraph MACHINE["Tempo de Máquina (UTC Instant)"]
        A["Instant.now() (Nanossegundos Unix Epoch)"]
    end

    subgraph HUMAN_LOCAL["Tempo Humano sem Fuso (Local)"]
        B["LocalDate (2026-09-28)"]
        C["LocalTime (14:30:00)"]
        D["LocalDateTime (2026-09-28T14:30:00)"]
        B & C --> D
    end

    subgraph ZONED["Tempo Ciente de Fuso (Zoned & Offset)"]
        E["ZoneId ('America/Sao_Paulo', 'UTC')"]
        F["ZonedDateTime (LocalDateTime + ZoneId)"]
        G["OffsetDateTime (LocalDateTime + ZoneOffset)"]
        D & E --> F
    end

    subgraph CALC["Operações e Intervalos Temporais"]
        H["Period: Anos, Meses, Dias (Human Time)"]
        I["Duration: Segundos, Nanos (Machine Time)"]
        J["TemporalAdjusters: lastDayOfMonth(), next(MONDAY)"]
    end

    MACHINE <-->|ZoneId Conversion| ZONED
    HUMAN_LOCAL --> CALC
    ZONED --> CALC

    style A fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
    style D fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
    style F fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
    style H fill:#fff3e0,stroke:#ff9800,stroke-width:2px

🕰️ 1. O Fim do Caos: Do Legado Date à API java.time (JSR-310)

Antes do Java 8, a manipulação de datas dependia de classes como java.util.Date, java.util.Calendar e java.text.SimpleDateFormat. Esse ecossistema legado acumulava falhas históricas graves:

  1. Mutabilidade Desenfreada: Métodos como date.setYear(124) alteravam a instância em memória, causando corrupção silenciosa em arquiteturas multithread.
  2. Convenções Confusas: Os anos eram calculados a partir de 1900 (ex: 2026 era representado por 126), e os meses eram indexados em zero (0 = Janeiro, 11 = Dezembro).
  3. Não Thread-Safe: A classe SimpleDateFormat compartilhada entre threads provocava formatações truncadas e exceções aleatórias.

A API java.time (JSR-310) introduziu classes 100% imutáveis, thread-safe e baseadas no padrão ISO-8601.


🌐 2. A Taxonomia das Classes Temporais Modernas

A nova API divide com rigor os conceitos temporais:

1. Tempo de Máquina: Instant

Representa um ponto contínuo e universal na linha do tempo medido em nanossegundos a partir de 01/01/1970 00:00:00 UTC (Unix Epoch). Ideal para carimbos de auditoria, eventos de mensageria e persistência em bancos de dados.

Instant agoraUtc = Instant.now();

2. Tempo Humano Local: LocalDate, LocalTime e LocalDateTime

Representam datas e horas da perspectiva de um observador civil sem qualquer amarração com fusos horários.

  • LocalDate: Apenas a data (ex: nascimento, vencimento de fatura: 2026-10-15).
  • LocalTime: Apenas a hora (ex: abertura da bolsa: 09:00:00).
  • LocalDateTime: Combinação de data e hora civil (2026-10-15T09:00:00).

3. Tempo com Fuso Horário: ZonedDateTime e ZoneId

Adiciona a regra geopolítica de fuso horário a um LocalDateTime, calculando transições automáticas de horário de verão (Daylight Saving Time):

ZoneId fusoSp = ZoneId.of("America/Sao_Paulo");
ZonedDateTime horarioBrasilia = ZonedDateTime.of(LocalDateTime.now(), fusoSp);
ZonedDateTime horarioToquio = horarioBrasilia.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));

⏳ 3. Intervalos Temporais: Duration, Period e TemporalAdjusters

O Java 21 diferencia com precisão a natureza da medição de intervalos:

  • Duration: Mede diferenças baseadas em tempo de relógio (horas, minutos, segundos e nanossegundos). Utilizado com LocalTime e Instant.
  • Period: Mede diferenças baseadas no calendário humano (anos, meses e dias). Utilizado com LocalDate.
  • ChronoUnit: Enumeração para cálculos diretos entre dois temporais (ChronoUnit.HOURS.between(t1, t2)).
  • TemporalAdjusters: Utilitários para navegação em calendários:
    LocalDate proximaSexta = LocalDate.now().with(TemporalAdjusters.next(DayOfWeek.FRIDAY));
    LocalDate ultimoDiaMes = LocalDate.now().with(TemporalAdjusters.lastDayOfMonth());
    

🎨 4. Formatação e Parsing com DateTimeFormatter

Ao contrário do antigo SimpleDateFormat, DateTimeFormatter é estritamente imutável e thread-safe, podendo ser declarado como constante estática public static final:

public static final DateTimeFormatter FORMATADOR_BR = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");

String textoFormatado = dataHora.format(FORMATADOR_BR);
LocalDateTime parsed = LocalDateTime.parse("28/09/2026 15:30:00", FORMATADOR_BR);

📊 5. Matriz Comparativa de Representação Temporal

Classe Informação de Fuso Componentes Armazenados Uso Recomendado
Instant UTC implícito Segundos + Nanos desde Epoch Logs de sistema, timestamps de mensageria, eventos IoT
LocalDate Nenhum Ano, Mês, Dia Aniversários, datas comemorativas, vencimentos fixos
LocalTime Nenhum Hora, Minuto, Segundo, Nanos Horário comercial de abertura/fechamento
LocalDateTime Nenhum Ano, Mês, Dia, Hora, Minuto, Segundo Agendamento local (sem relevância de múltiplos países)
ZonedDateTime Sim (ZoneId) Data, Hora, Fuso, Regras de Verão Emissão de passagens aéreas, videoconferências globais
OffsetDateTime Sim (ZoneOffset) Data, Hora, Offset fixo (-03:00) Protocolos de integração REST/JSON, bancos de dados

💻 6. Código de Demonstração Corporativo (Java 21 LTS)

package com.empresa.tempo;

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;

/**
 * Sistema de agendamento de voos internacionais com conversão de fusos horários.
 */
public class GestorVoosInternacionais {

    public static final DateTimeFormatter FORMATO_GLOBAL = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm (z)");

    public record ItinerarioVoo(
            String codigoVoo,
            ZonedDateTime partidaOrigem,
            Duration duracaoEstimada,
            ZoneId fusoDestino
    ) {
        public ZonedDateTime calcularPousoDestino() {
            // Soma a duração física em voo e projeta no fuso do aeroporto de destino
            Instant instantePousoUtc = partidaOrigem.toInstant().plus(duracaoEstimada);
            return instantePousoUtc.atZone(fusoDestino);
        }
    }

    public static void main(String[] args) {
        System.out.println("================================================================================");
        System.out.println("            SISTEMA DE RADAR AÉREO: CONVERSÃO DE FUSOS HORÁRIOS                 ");
        System.out.println("================================================================================");

        ZoneId spZone = ZoneId.of("America/Sao_Paulo");
        ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");
        ZoneId londonZone = ZoneId.of("Europe/London");

        // Voo saindo de Guarulhos para Londres
        LocalDateTime decolagemLocalSp = LocalDateTime.of(2026, Month.NOVEMBER, 10, 23, 45);
        ZonedDateTime decolagemSp = ZonedDateTime.of(decolagemLocalSp, spZone);
        Duration duracaoVooLondres = Duration.ofHours(11).plusMinutes(30);

        ItinerarioVoo vooGhulon = new ItinerarioVoo("BA-0246", decolagemSp, duracaoVooLondres, londonZone);
        ZonedDateTime pousoLondres = vooGhulon.calcularPousoDestino();

        System.out.printf("✈️  Voo: %s (Guarulhos -> Londres Heathrow)%n", vooGhulon.codigoVoo());
        System.out.printf("   Partida em SP     : %s%n", vooGhulon.partidaOrigem().format(FORMATO_GLOBAL));
        System.out.printf("   Duração de Voo    : %d horas e %d minutos%n", duracaoVooLondres.toHours(), duracaoVooLondres.toMinutesPart());
        System.out.printf("   Chegada em Londres: %s%n%n", pousoLondres.format(FORMATO_GLOBAL));

        // Cálculo de SLA de conexão
        long diferencaHorasFuso = ChronoUnit.HOURS.between(decolagemSp.getOffset(), pousoLondres.getOffset());
        System.out.printf("Diferença de fuso horário líquido entre cidades: %d horas%n", diferencaHorasFuso);
        System.out.println("================================================================================");
    }
}