Deepen PracticeOrdered learning track

Date/Time: Instant, Local, Zoned, Duration, Period

Learn Java Data Types, Type Semantics, Object Model & Data Representation - Part 024

Deep dive into Java date and time modeling with Instant, LocalDate, LocalTime, LocalDateTime, OffsetDateTime, ZonedDateTime, Duration, Period, Clock, timezone, DST, persistence, API, and enterprise temporal correctness.

15 min read2837 words
PrevNext
Lesson 2434 lesson track1928 Deepen Practice
#java#data-types#java-time#instant+8 more

Part 024 — Date/Time: Instant, Local, Zoned, Duration, Period

Target part ini: memahami java.time sebagai model temporal, bukan sekadar API tanggal. Kita akan membedakan instant, local date-time, offset date-time, zoned date-time, duration, period, clock, timezone, DST, formatting, persistence, API boundary, dan failure modes enterprise.

Waktu adalah salah satu tipe data paling berbahaya di sistem enterprise. Ia terlihat sederhana:

LocalDateTime.now()

Tetapi di production, waktu adalah gabungan dari:

  • timeline global;
  • kalender lokal;
  • timezone rules;
  • daylight saving time;
  • business day;
  • legal deadline;
  • storage precision;
  • ordering;
  • auditability;
  • replayability;
  • distributed system clock skew.

1. Kaufman Skill Map

Latihan 20 Jam yang Relevan

JamFokusOutput
1–2Instant, LocalDate, LocalDateTimeBedakan timeline vs calendar
3–4ZoneId, ZoneOffset, ZonedDateTimeKonversi antar zone
5–6DST gap/overlapReproduce kasus jam hilang/berulang
7–8Duration vs PeriodTest penambahan hari/bulan/jam
9–10ClockBuat service testable tanpa now() langsung
11–12formatting/parsingPakai DateTimeFormatter secara eksplisit
13–14DB mappingMapping timestamp/date/time dengan benar
15–16JSON contractISO format, offset, zone, precision
17–18scheduler/deadlineBuat SLA/deadline calculator
19–20capstoneTemporal model untuk enforcement lifecycle

2. Masalah Fundamental: “Tanggal” Bukan Satu Konsep

Banyak bug muncul karena semua hal diberi tipe Date, Timestamp, atau LocalDateTime.

Padahal pertanyaannya berbeda:

PertanyaanTipe yang Mungkin Tepat
Kapan event terjadi di timeline global?Instant
Tanggal lahir seseorang?LocalDate
Jam kerja toko lokal?LocalTime
Meeting jam 09:00 di Jakarta?ZonedDateTime atau local + zone
Timestamp dengan offset eksplisit?OffsetDateTime
Durasi timeout 30 detik?Duration
Periode kontrak 3 bulan?Period
Hari kerja berikutnya?domain BusinessCalendar

Diagram Mental Model


3. java.time Design Principles

Modern Java date-time API dirancang dengan prinsip:

  • immutable;
  • thread-safe;
  • explicit type separation;
  • ISO calendar default;
  • timezone rules via ZoneId;
  • clear distinction antara machine time dan human calendar time.

Ini berbeda dari legacy java.util.Date dan Calendar yang mutable, confusing, dan banyak method deprecated.


4. Instant: Titik di Timeline Global

Instant merepresentasikan point di timeline UTC.

Instant now = Instant.now();
System.out.println(now); // 2026-06-30T...

Gunakan Instant untuk:

  • event happened at;
  • audit timestamp;
  • created/updated timestamp;
  • message produced at;
  • log event time;
  • ordering global;
  • expiry absolute;
  • distributed tracing timestamp.

Instant Tidak Punya Kalender Lokal

Instant tidak tahu:

  • hari Senin atau Selasa lokal;
  • jam 09:00 di Jakarta;
  • tanggal akhir bulan;
  • business day;
  • DST local transition.

Ia hanya titik timeline.

Convert Instant ke Local View

Instant instant = Instant.parse("2026-06-30T02:00:00Z");
ZoneId jakarta = ZoneId.of("Asia/Jakarta");
ZonedDateTime localView = instant.atZone(jakarta);

System.out.println(localView);

Satu instant bisa punya banyak tampilan lokal tergantung zone.


5. LocalDate: Tanggal Tanpa Timezone

LocalDate merepresentasikan date tanpa time dan tanpa timezone.

LocalDate birthDate = LocalDate.of(1990, 5, 20);
LocalDate filingDate = LocalDate.parse("2026-06-30");

Gunakan untuk:

  • birth date;
  • filing date;
  • business date;
  • effective date berbasis kalender;
  • due date yang didefinisikan sebagai tanggal lokal;
  • holiday calendar.

LocalDate Bukan Midnight Instant

Jangan otomatis menganggap LocalDate sebagai 00:00 UTC.

LocalDate date = LocalDate.of(2026, 6, 30);
Instant wrong = date.atStartOfDay(ZoneOffset.UTC).toInstant();
Instant jakartaStart = date.atStartOfDay(ZoneId.of("Asia/Jakarta")).toInstant();

Start of day tergantung zone. Bahkan dalam zone tertentu, start of day bisa terdampak DST rules.


6. LocalTime: Jam Tanpa Tanggal dan Timezone

LocalTime merepresentasikan time-of-day.

LocalTime opening = LocalTime.of(9, 0);
LocalTime closing = LocalTime.of(17, 30);

Gunakan untuk:

  • jam operasional;
  • daily cutoff local;
  • recurring schedule local;
  • SLA daily window.

Jangan gunakan LocalTime untuk durasi.

Duration lunchBreak = Duration.ofMinutes(45); // benar
LocalTime lunchBreakWrong = LocalTime.of(0, 45); // salah model

7. LocalDateTime: Local Calendar Date-Time Tanpa Zone

LocalDateTime adalah date + time tanpa timezone/offset.

LocalDateTime appointment = LocalDateTime.of(2026, 6, 30, 9, 0);

Ia tidak cukup untuk menentukan instant global.

LocalDateTime local = LocalDateTime.of(2026, 6, 30, 9, 0);

Instant jakartaInstant = local.atZone(ZoneId.of("Asia/Jakarta")).toInstant();
Instant londonInstant = local.atZone(ZoneId.of("Europe/London")).toInstant();

Keduanya berbeda.

Kapan LocalDateTime Tepat?

Use CaseTepat?Catatan
Form input “2026-06-30 09:00” sebelum user memilih timezoneyamasih incomplete
Meeting globaltidak sendiributuh zone/offset
DB audit timestampbiasanya tidakpakai Instant/OffsetDateTime
Local business eventkadangsimpan zone context juga
Cron schedule localya, dengan zonelocal time + zone rules

Anti-Pattern

LocalDateTime createdAt = LocalDateTime.now();

Untuk audit, ini kehilangan zone/offset. Lebih baik:

Instant createdAt = clock.instant();

8. ZoneOffset vs ZoneId

ZoneOffset adalah fixed offset dari UTC, seperti +07:00.

ZoneId adalah timezone region/rules, seperti Asia/Jakarta, Europe/Paris, America/New_York.

ZoneOffset offset = ZoneOffset.of("+07:00");
ZoneId zone = ZoneId.of("Asia/Jakarta");

Perbedaan Penting

KonsepContohPunya DST/rules?
ZoneOffset+07:00tidak
ZoneIdAsia/Jakartaya, berdasarkan rules

Offset adalah fakta pada saat tertentu. Zone adalah aturan historis/future untuk lokasi.

Untuk jadwal masa depan, simpan zone ID jika aturan lokal penting.


9. OffsetDateTime

OffsetDateTime adalah local date-time + fixed offset.

OffsetDateTime timestamp = OffsetDateTime.parse("2026-06-30T09:00:00+07:00");
Instant instant = timestamp.toInstant();

Gunakan untuk:

  • API timestamp dengan offset;
  • event time yang membawa offset;
  • DB timestamp with offset, tergantung database/driver;
  • interoperability.

Batasannya: offset bukan timezone region. +07:00 tidak memberi tahu apakah event berasal dari Jakarta, Bangkok, atau tempat lain.


10. ZonedDateTime

ZonedDateTime adalah local date-time + zone rules.

ZonedDateTime meeting = ZonedDateTime.of(
    2026, 6, 30, 9, 0, 0, 0,
    ZoneId.of("Asia/Jakarta")
);

Gunakan untuk:

  • meeting/schedule lokal;
  • deadline lokal dengan zone;
  • recurring event;
  • conversion ke instant;
  • UI display yang butuh timezone name/rules.

Same Instant vs Same Local Time

ZonedDateTime jakarta = ZonedDateTime.of(
    2026, 6, 30, 9, 0, 0, 0,
    ZoneId.of("Asia/Jakarta")
);

ZonedDateTime londonSameInstant = jakarta.withZoneSameInstant(ZoneId.of("Europe/London"));
ZonedDateTime londonSameLocal = jakarta.withZoneSameLocal(ZoneId.of("Europe/London"));
MethodMakna
withZoneSameInstantinstant tetap, local time berubah
withZoneSameLocallocal date-time tetap, instant berubah

Ini sering menentukan benar/salahnya reschedule.


11. DST Gap and Overlap

Daylight Saving Time membuat local time tertentu bisa:

  • tidak ada;
  • terjadi dua kali.

Gap

Saat clock maju, local time tertentu hilang.

ZoneId paris = ZoneId.of("Europe/Paris");
LocalDateTime gap = LocalDateTime.of(2026, 3, 29, 2, 30);
ZonedDateTime zdt = gap.atZone(paris);
System.out.println(zdt);

02:30 lokal mungkin tidak valid pada hari transisi DST.

Overlap

Saat clock mundur, local time terjadi dua kali.

ZoneId paris = ZoneId.of("Europe/Paris");
LocalDateTime overlap = LocalDateTime.of(2026, 10, 25, 2, 30);
ZonedDateTime zdt = overlap.atZone(paris);

02:30 bisa merujuk ke dua instant berbeda.

Production Impact

AreaRisiko
Schedulerjob skipped atau double-run
SLAdeadline salah 1 jam
Billingduration salah
Logsordering membingungkan
Recurring eventmeeting shift
Legal deadlinedispute

Rule

Untuk event happened, simpan Instant.

Untuk schedule future local, simpan local date-time + zone ID + resolution policy.

public enum DstResolutionPolicy {
    SHIFT_FORWARD,
    REJECT_INVALID_LOCAL_TIME,
    EARLIER_OFFSET,
    LATER_OFFSET
}

12. Duration vs Period

Duration adalah amount berbasis seconds/nanos.

Period adalah amount berbasis date units: years, months, days.

Duration timeout = Duration.ofSeconds(30);
Period subscription = Period.ofMonths(1);

Jangan Campur

KebutuhanTipe
HTTP timeout 5sDuration
Cache TTL 15mDuration
SLA response 4hDuration, kecuali business hours
Contract valid 1 yearPeriod
Add 1 calendar monthPeriod
Age calculationPeriod

Contoh Perbedaan

ZonedDateTime beforeDst = ZonedDateTime.of(
    2026, 3, 28, 12, 0, 0, 0,
    ZoneId.of("Europe/Paris")
);

ZonedDateTime plusOneDayPeriod = beforeDst.plus(Period.ofDays(1));
ZonedDateTime plus24HoursDuration = beforeDst.plus(Duration.ofHours(24));

Di sekitar DST, “1 day” dan “24 hours” dapat berbeda secara local-time semantics.


13. Clock: Waktu Harus Injectable

Jangan panggil Instant.now() langsung di domain service.

Buruk

public boolean isExpired(Instant expiresAt) {
    return Instant.now().isAfter(expiresAt);
}

Baik

public final class ExpiryService {
    private final Clock clock;

    public ExpiryService(Clock clock) {
        this.clock = Objects.requireNonNull(clock);
    }

    public boolean isExpired(Instant expiresAt) {
        return clock.instant().isAfter(expiresAt);
    }
}

Test:

Clock fixed = Clock.fixed(
    Instant.parse("2026-06-30T00:00:00Z"),
    ZoneOffset.UTC
);

ExpiryService service = new ExpiryService(fixed);

Rule

  • Application composition boleh membuat Clock.systemUTC().
  • Domain service menerima Clock.
  • Test menggunakan Clock.fixed().
  • Jangan menyebarkan now() di business logic.

14. Parsing and Formatting

Gunakan DateTimeFormatter, bukan string concat manual.

DateTimeFormatter formatter = DateTimeFormatter.ISO_OFFSET_DATE_TIME;
OffsetDateTime value = OffsetDateTime.parse("2026-06-30T09:00:00+07:00", formatter);

Formatting Boundary

BoundaryFormat Recommendation
Machine timestampISO-8601 with offset or UTC instant
Local dateyyyy-MM-dd ISO date
UI displaylocale-specific formatter
Audit logUTC instant + original zone if relevant
Schedule inputlocal date-time + zone ID

Jangan Pakai Default Locale/Zone Diam-Diam

LocalDateTime parsed = LocalDateTime.parse(input); // okay if input ISO and contract says so

Tapi untuk UI human text:

DateTimeFormatter.ofPattern("dd MMM uuuu", locale);

Locale harus eksplisit.


15. Precision: Millis, Micros, Nanos

Instant mendukung nanosecond precision, tetapi database, broker, dan API mungkin tidak.

Instant now = Instant.now();
System.out.println(now.getNano());

Boundary Risk

SystemCommon Precision
Java Instantnanosecond component
Many databasesmicrosecond/millisecond depending type/config
JSON stringarbitrary text, but parser may truncate
Kafka/event payloaddepends on schema
Logsoften millis/nanos depending framework

Jika timestamp dipakai sebagai equality key, precision mismatch bisa membuat bug.

Safer Boundary

Instant truncated = instant.truncatedTo(ChronoUnit.MILLIS);

Tapi truncation juga policy. Jangan lakukan diam-diam tanpa alasan.


16. Legacy Interop

Kadang harus berinteraksi dengan legacy Date.

Date date = Date.from(instant);
Instant instantAgain = date.toInstant();

java.sql.Date, java.sql.Time, dan java.sql.Timestamp punya sejarah dan behavior yang perlu hati-hati. Untuk code baru, prefer JDBC driver yang mendukung java.time type.

Migration Rule

LegacyModern
java.util.Date event timestampInstant
java.sql.Date SQL DATELocalDate
java.sql.Time SQL TIMELocalTime
java.sql.Timestamp SQL TIMESTAMPLocalDateTime/Instant depending DB semantics
CalendarZonedDateTime or Instant + ZoneId

17. Database Boundary

Database temporal types berbeda antar vendor. Jangan asumsi satu mapping universal.

Common Mapping Intention

Domain MeaningJava TypeDB Type Intention
audit event timestampInstanttimestamp UTC / timestamp with time zone semantics
local business dateLocalDateDATE
local time of dayLocalTimeTIME
local appointmentLocalDateTime + ZoneIdTIMESTAMP + zone column
offset timestampOffsetDateTimeTIMESTAMP WITH TIME ZONE if semantics match

Jangan Simpan Future Schedule sebagai Instant Saja

Misal meeting “setiap hari jam 09:00 Asia/Jakarta”. Jika hanya disimpan sebagai instant pertama + interval 24 jam, local schedule bisa salah di zone dengan DST.

Simpan:

scheduled_local_date_time TIMESTAMP NOT NULL,
zone_id VARCHAR(64) NOT NULL

Lalu resolve ke instant saat eksekusi dengan policy.

Audit Timestamp

Untuk audit event, simpan instant.

public record AuditStamp(
    Instant occurredAt,
    String actorId,
    String source
) {}

Jika local legal interpretation penting, simpan juga zone atau jurisdiction.


18. JSON/API Boundary

Instant as ISO String

{
  "occurredAt": "2026-06-30T02:00:00Z"
}

Local Date

{
  "filingDate": "2026-06-30"
}

Scheduled Local Date-Time with Zone

{
  "scheduledAt": "2026-06-30T09:00:00",
  "zoneId": "Asia/Jakarta"
}

Offset Timestamp

{
  "submittedAt": "2026-06-30T09:00:00+07:00"
}

API Contract Checklist

  • Apakah field adalah instant, local date, local time, offset date-time, atau zoned date-time?
  • Apakah format ISO-8601?
  • Apakah offset wajib?
  • Apakah zone ID wajib?
  • Apakah precision dibatasi?
  • Apakah timezone default dilarang?
  • Apakah DST invalid/ambiguous time punya policy?

19. Deadline Modeling

Deadline bukan hanya timestamp.

Contoh rule:

“Response due 5 business days after filing date, by 17:00 local jurisdiction time, unless holiday.”

Ini membutuhkan:

  • filing date;
  • jurisdiction;
  • business calendar;
  • local cutoff time;
  • zone ID;
  • holiday rules;
  • extension rules;
  • final instant for enforcement.

Domain Model

public record Jurisdiction(String code, ZoneId zoneId) {}

public interface BusinessCalendar {
    boolean isBusinessDay(LocalDate date, Jurisdiction jurisdiction);

    LocalDate plusBusinessDays(LocalDate start, int days, Jurisdiction jurisdiction);
}

public record Deadline(
    LocalDate dueDate,
    LocalTime cutoffTime,
    Jurisdiction jurisdiction
) {
    public Instant toInstant(DstResolutionPolicy policy) {
        LocalDateTime local = LocalDateTime.of(dueDate, cutoffTime);
        return resolve(local, jurisdiction.zoneId(), policy).toInstant();
    }

    private static ZonedDateTime resolve(
        LocalDateTime local,
        ZoneId zone,
        DstResolutionPolicy policy
    ) {
        // Simplified. Real implementation should inspect ZoneRules.
        return local.atZone(zone);
    }
}

Diagram


20. SLA and Duration Modeling

SLA kadang duration murni, kadang business duration.

Duration SLA

public record ResponseSla(Duration duration) {
    public Instant dueAt(Instant start) {
        return start.plus(duration);
    }
}

Business SLA

public record BusinessSla(int businessHours, Jurisdiction jurisdiction) {}

Business SLA tidak bisa diwakili Duration saja jika harus melewati jam kerja, weekend, libur, dan pause/resume.


21. Effective Date vs Transaction Time

Dalam sistem regulasi, “waktu” sering punya beberapa dimensi:

DimensiMaknaTipe Umum
occurred timekapan event terjadiInstant
recorded timekapan sistem mencatatInstant
effective datekapan aturan berlakuLocalDate
valid periodkapan fakta validLocalDate/interval
transaction timekapan database mengetahuiInstant
decision timekapan keputusan dibuatInstant + actor/context

Jangan campur effectiveDate dengan createdAt.

public record RegulationRule(
    String ruleId,
    LocalDate effectiveFrom,
    Optional<LocalDate> effectiveTo,
    Instant recordedAt
) {}

22. Intervals and Ranges

Java standard library tidak punya universal Interval di java.time. Kita sering membuat sendiri.

public record InstantRange(Instant startInclusive, Instant endExclusive) {
    public InstantRange {
        Objects.requireNonNull(startInclusive);
        Objects.requireNonNull(endExclusive);
        if (!startInclusive.isBefore(endExclusive)) {
            throw new IllegalArgumentException("start must be before end");
        }
    }

    public boolean contains(Instant instant) {
        return !instant.isBefore(startInclusive) && instant.isBefore(endExclusive);
    }
}

Prefer Half-Open Interval

[startInclusive, endExclusive)

Alasan:

  • menghindari precision boundary problem;
  • mudah concatenate;
  • umum di query time range;
  • tidak perlu “23:59:59.999”.

Buruk

LocalDateTime endOfDay = date.atTime(23, 59, 59, 999_000_000);

Lebih Baik

Instant start = date.atStartOfDay(zone).toInstant();
Instant endExclusive = date.plusDays(1).atStartOfDay(zone).toInstant();

23. Timezone Default Trap

ZoneId.systemDefault()

Boleh di application edge, tetapi berbahaya di domain logic.

Masalah:

  • berubah antar environment;
  • container base image berbeda;
  • developer laptop berbeda;
  • test flaky;
  • batch job hasil berbeda;
  • audit sulit direplay.

Rule

  • Store/config zone eksplisit.
  • Inject Clock dengan zone jika perlu.
  • Jangan parse/format dengan default zone tanpa contract.
  • Test minimal di dua zone berbeda.
@ParameterizedTest
@ValueSource(strings = {"UTC", "Asia/Jakarta", "Europe/Paris", "America/New_York"})
void deadlineIsStableAcrossZones(String zoneId) {
    // test boundary behavior
}

24. Ordering and Comparison

Untuk ordering global, compare Instant.

boolean late = submittedAtInstant.isAfter(deadlineInstant);

Jangan compare local date-time dari zone berbeda.

LocalDateTime a = LocalDateTime.of(2026, 6, 30, 9, 0);
LocalDateTime b = LocalDateTime.of(2026, 6, 30, 8, 0);

Tanpa zone, kamu tidak tahu mana lebih dulu secara global.

Equality

TypeEquality Meaning
Instantsame point on timeline
LocalDatesame date fields
LocalDateTimesame local fields, not same instant
OffsetDateTimesame local date-time and offset for equals; compare instant separately when needed
ZonedDateTimeincludes local date-time, offset, zone; same instant requires toInstant() comparison

Jangan asumsikan semua temporal equality berarti “same real-world moment”.


25. Testing Temporal Logic

Core Rules

  • Inject Clock.
  • Test DST gap.
  • Test DST overlap.
  • Test leap year.
  • Test month-end.
  • Test timezone conversion.
  • Test precision truncation.
  • Test serialization round-trip.
  • Test DB round-trip.

Leap Year

LocalDate feb29 = LocalDate.of(2024, 2, 29);
System.out.println(feb29.plusYears(1)); // 2025-02-28

This behavior may be correct, but only if the domain accepts it.

Month-End

LocalDate jan31 = LocalDate.of(2026, 1, 31);
System.out.println(jan31.plusMonths(1)); // 2026-02-28

Subscription billing, deadline extension, and recurring schedule must define month-end policy.


26. Failure Modes di Production

26.1 CreatedAt sebagai LocalDateTime

LocalDateTime createdAt = LocalDateTime.now();

Akibat:

  • tidak bisa compare global;
  • ambiguous saat data lintas region;
  • audit replay sulit;
  • migration timezone kacau.

26.2 Deadline Disimpan sebagai Date Tanpa Zone

Deadline “30 Juni jam 17:00 local jurisdiction time” disimpan hanya sebagai 2026-06-30T17:00.

Akibat:

  • jurisdiction tidak diketahui;
  • instant deadline tidak bisa dihitung ulang;
  • dispute legal.

26.3 Scheduler Berbasis 24 Jam untuk Local Daily Event

Job “setiap hari jam 09:00 local” dijadwalkan dengan lastRun.plus(Duration.ofHours(24)).

Akibat:

  • DST dapat menggeser jam lokal;
  • job skip/double-run.

26.4 DB Precision Mismatch

Java menyimpan Instant nanos, DB truncate ke millis, lalu equality gagal.

26.5 System Default Zone

Test pass di laptop Jakarta, fail di CI UTC.


27. Design Patterns Lokal

27.1 AuditClock

public interface AuditClock {
    Instant now();
}

public final class SystemAuditClock implements AuditClock {
    private final Clock clock;

    public SystemAuditClock(Clock clock) {
        this.clock = clock;
    }

    @Override
    public Instant now() {
        return clock.instant();
    }
}

Ini menghindari domain terlalu tergantung pada API global.

27.2 Temporal Value Object

public record FilingDate(LocalDate value) {
    public FilingDate {
        Objects.requireNonNull(value);
    }
}

public record SubmissionTime(Instant value) {
    public SubmissionTime {
        Objects.requireNonNull(value);
    }
}

FilingDate dan SubmissionTime tidak boleh ditukar walaupun sama-sama temporal.

27.3 DeadlinePolicy

public interface DeadlinePolicy {
    Deadline calculate(FilingDate filingDate, Jurisdiction jurisdiction);
}

Policy diberi nama dan bisa versioned.


28. Worked Example: Case Response Deadline

Rule:

  • deadline = 5 business days setelah filing date;
  • cutoff = 17:00 local jurisdiction time;
  • holiday/weekend dilewati;
  • output menyimpan local due date dan instant deadline.
public record CaseFiling(
    String caseId,
    LocalDate filingDate,
    Jurisdiction jurisdiction
) {}

public record CaseDeadline(
    String caseId,
    LocalDate dueDate,
    LocalTime cutoffTime,
    ZoneId zoneId,
    Instant dueInstant
) {}
public final class CaseDeadlineCalculator {
    private final BusinessCalendar businessCalendar;

    public CaseDeadlineCalculator(BusinessCalendar businessCalendar) {
        this.businessCalendar = Objects.requireNonNull(businessCalendar);
    }

    public CaseDeadline calculate(CaseFiling filing) {
        LocalDate dueDate = businessCalendar.plusBusinessDays(
            filing.filingDate(),
            5,
            filing.jurisdiction()
        );

        LocalTime cutoff = LocalTime.of(17, 0);
        ZoneId zone = filing.jurisdiction().zoneId();

        ZonedDateTime zonedDue = ZonedDateTime.of(dueDate, cutoff, zone);

        return new CaseDeadline(
            filing.caseId(),
            dueDate,
            cutoff,
            zone,
            zonedDue.toInstant()
        );
    }
}

Kenapa Menyimpan Keduanya?

  • dueDate, cutoffTime, dan zoneId menjelaskan legal/business meaning.
  • dueInstant memudahkan comparison global dan scheduler.

Ini bukan duplikasi bodoh. Ini penyimpanan dua representasi dengan semantik berbeda.


29. Review Checklist

Type Choice

  • Event timestamp memakai Instant atau tipe yang membawa offset/zone.
  • Date-only memakai LocalDate.
  • Time-only memakai LocalTime.
  • Schedule masa depan menyimpan zone ID jika local meaning penting.
  • LocalDateTime tidak dipakai untuk audit global tanpa context.

Clock

  • Business logic tidak memanggil now() langsung.
  • Clock atau abstraction di-inject.
  • Tests memakai fixed clock.

Timezone

  • Tidak ada ZoneId.systemDefault() di domain logic.
  • Zone ID eksplisit di config/domain.
  • DST gap/overlap punya policy.
  • Test minimal mencakup zone dengan DST.

Duration/Period

  • Timeout memakai Duration.
  • Calendar period memakai Period.
  • Business-day logic tidak direduksi ke Duration.ofDays(...).

Persistence/API

  • DB temporal type cocok dengan domain meaning.
  • JSON format eksplisit.
  • Precision timestamp jelas.
  • Round-trip serialization diuji.

Domain

  • Deadline menyimpan local meaning dan instant jika keduanya diperlukan.
  • Effective date tidak dicampur dengan createdAt.
  • Interval memakai half-open range.
  • Holiday/business calendar diberi model eksplisit.

30. Practice Set

Exercise 1 — Instant vs LocalDateTime

Buat dua LocalDateTime yang sama di dua zone berbeda. Konversikan ke Instant. Jelaskan perbedaannya.

Exercise 2 — Clock Injection

Refactor service yang memakai Instant.now() menjadi service dengan Clock injectable.

Exercise 3 — DST Gap

Cari zone dengan DST dan buat test untuk local time yang invalid. Tentukan policy: reject atau shift forward.

Exercise 4 — Duration vs Period

Bandingkan plus(Duration.ofHours(24)) dan plus(Period.ofDays(1)) di sekitar DST.

Exercise 5 — Deadline Calculator

Implement BusinessCalendar sederhana yang melewati Sabtu/Minggu. Buat calculator deadline 5 business days by 17:00 local zone.

Exercise 6 — API Contract

Desain JSON untuk:

  • audit event timestamp;
  • date of birth;
  • scheduled meeting;
  • SLA duration;
  • legal effective date.

Jelaskan tipe Java untuk masing-masing.


31. Mental Model Ringkas

Rule utama:

Jangan mulai dari “tipe tanggal apa yang biasa dipakai?”. Mulai dari “makna waktu apa yang sedang dimodelkan?”.


32. Sumber Resmi yang Perlu Dibaca

Baca dokumentasi resmi ini setelah menyelesaikan part:

  • java.time package summary
  • Instant
  • LocalDate
  • LocalTime
  • LocalDateTime
  • OffsetDateTime
  • ZonedDateTime
  • Duration
  • Period
  • Clock
  • DateTimeFormatter
  • ZoneId
  • ZoneOffset

Tujuannya bukan menghafal semua method, tetapi memahami grammar konseptual waktu modern Java.


33. Penutup

Setelah part ini, kamu harus bisa melihat bug temporal sebagai kombinasi dari:

timeline + calendar + zone + offset + duration/period + precision + boundary + domain rule

Top 1% engineer tidak hanya tahu LocalDateTime dan Instant. Mereka tahu kapan masing-masing salah.

Part berikutnya akan melanjutkan ke temporal modeling enterprise: effective date, valid time, transaction time, regulatory deadlines, SLA, business calendar, lifecycle state, dan audit defensibility.

Lesson Recap

You just completed lesson 24 in deepen practice. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.