Java Date

1. Java Date and Time Overview

If you have ever dealt with dates in Java using java.util.Date or java.util.Calendar, you know the pain: mutable objects that change unexpectedly, months starting from 0, thread-safety issues, and a confusing API that mixes date concepts with time concepts. For over a decade, Java developers relied on third-party libraries like Joda-Time just to do basic date arithmetic correctly.

Java 8 fixed this by introducing the java.time package (JSR 310), designed by the creator of Joda-Time himself. This package is now the standard for all date and time work in Java.

Why java.time Replaced the Legacy API

Problem with Legacy API How java.time Fixes It
Date is mutable — anyone can call setTime() and change it All java.time classes are immutable — methods return new instances
Months are 0-indexed (January = 0) Months are 1-indexed (January = 1) or use the Month enum
Date represents both date and time, but poorly Separate classes: LocalDate, LocalTime, LocalDateTime
No built-in time zone handling beyond TimeZone ZonedDateTime and ZoneId with IANA time zone database
Not thread-safe — SimpleDateFormat causes race conditions DateTimeFormatter is thread-safe and immutable
No concept of periods or durations Period (date-based) and Duration (time-based)
Confusing API (getYear() returns year minus 1900) Clear, consistent method naming: getYear() returns the actual year

Key Classes in java.time

Class What It Represents Example
LocalDate Date only (no time, no zone) 2026-02-28
LocalTime Time only (no date, no zone) 14:30:00
LocalDateTime Date and time (no zone) 2026-02-28T14:30:00
ZonedDateTime Date, time, and time zone 2026-02-28T14:30:00-05:00[America/New_York]
Instant Machine timestamp (epoch seconds) 2026-02-28T19:30:00Z
Period Date-based amount (years, months, days) 2 years, 3 months, 5 days
Duration Time-based amount (hours, minutes, seconds) 2 hours, 30 minutes
DateTimeFormatter Parsing and formatting “dd/MM/yyyy” pattern

The rule of thumb is simple: use the most specific type that fits your need. If you only care about a date (birthdays, holidays), use LocalDate. If you need to coordinate across time zones (meeting schedulers, flight departures), use ZonedDateTime. If you need a machine-readable timestamp for logging or databases, use Instant.

2. LocalDate — Date Without Time

LocalDate represents a date without a time component and without a time zone. It stores a year, month, and day. This is the class you reach for when you care about what day something happens but not what time: birthdays, holidays, due dates, hire dates.

Creating a LocalDate

There are three primary ways to create a LocalDate:

import java.time.LocalDate;
import java.time.Month;

public class LocalDateCreation {
    public static void main(String[] args) {

        // 1. Current date from the system clock
        LocalDate today = LocalDate.now();
        System.out.println("Today: " + today);  // e.g., 2026-02-28

        // 2. Specific date using of()
        LocalDate christmas = LocalDate.of(2026, 12, 25);
        System.out.println("Christmas: " + christmas);  // 2026-12-25

        // Using Month enum (more readable)
        LocalDate newYear = LocalDate.of(2027, Month.JANUARY, 1);
        System.out.println("New Year: " + newYear);  // 2027-01-01

        // 3. Parsing a string (ISO 8601 format: yyyy-MM-dd)
        LocalDate parsed = LocalDate.parse("2026-07-04");
        System.out.println("Parsed: " + parsed);  // 2026-07-04
    }
}

Getting Date Components

Once you have a LocalDate, you can extract every piece of information from it:

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;

public class LocalDateComponents {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2026, Month.FEBRUARY, 28);

        System.out.println("Year:         " + date.getYear());          // 2026
        System.out.println("Month (enum): " + date.getMonth());         // FEBRUARY
        System.out.println("Month (int):  " + date.getMonthValue());    // 2
        System.out.println("Day of month: " + date.getDayOfMonth());    // 28
        System.out.println("Day of week:  " + date.getDayOfWeek());     // SATURDAY
        System.out.println("Day of year:  " + date.getDayOfYear());     // 59
        System.out.println("Length of month: " + date.lengthOfMonth()); // 28
        System.out.println("Length of year:  " + date.lengthOfYear());  // 365
        System.out.println("Is leap year:    " + date.isLeapYear());    // false
    }
}

Manipulating Dates

Because LocalDate is immutable, every manipulation method returns a new LocalDate. The original is never modified.

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class LocalDateManipulation {
    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 2, 28);

        // Adding
        System.out.println("Plus 1 day:    " + today.plusDays(1));      // 2026-03-01
        System.out.println("Plus 2 weeks:  " + today.plusWeeks(2));     // 2026-03-14
        System.out.println("Plus 1 month:  " + today.plusMonths(1));    // 2026-03-28
        System.out.println("Plus 1 year:   " + today.plusYears(1));     // 2027-02-28

        // Subtracting
        System.out.println("Minus 10 days: " + today.minusDays(10));    // 2026-02-18
        System.out.println("Minus 3 months:" + today.minusMonths(3));   // 2025-11-28

        // Using ChronoUnit (flexible alternative)
        System.out.println("Plus 100 days: " + today.plus(100, ChronoUnit.DAYS));  // 2026-06-09

        // Replacing components with "with" methods
        System.out.println("With year 2030: " + today.withYear(2030));        // 2030-02-28
        System.out.println("With month 6:   " + today.withMonth(6));          // 2026-06-28
        System.out.println("With day 15:    " + today.withDayOfMonth(15));    // 2026-02-15

        // Important: the original date is unchanged (immutability)
        System.out.println("Original: " + today);  // 2026-02-28
    }
}

Comparing Dates

import java.time.LocalDate;

public class LocalDateComparison {
    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 2, 28);
        LocalDate tomorrow = LocalDate.of(2026, 3, 1);
        LocalDate alsoToday = LocalDate.of(2026, 2, 28);

        // isBefore, isAfter, isEqual
        System.out.println(today.isBefore(tomorrow));   // true
        System.out.println(today.isAfter(tomorrow));    // false
        System.out.println(today.isEqual(alsoToday));   // true

        // equals() works the same as isEqual() for LocalDate
        System.out.println(today.equals(alsoToday));    // true

        // compareTo() -- returns negative, zero, or positive
        System.out.println(today.compareTo(tomorrow));  // negative (today < tomorrow)

        // NEVER use == to compare dates (compares object references, not values)
        // today == alsoToday  --> false (different objects!)
    }
}

Practical Example: Age Calculator

A common real-world use case: calculating someone’s age from their birth date.

import java.time.LocalDate;
import java.time.Month;
import java.time.Period;

public class AgeCalculator {

    public static int calculateAge(LocalDate birthDate) {
        LocalDate today = LocalDate.now();
        if (birthDate.isAfter(today)) {
            throw new IllegalArgumentException("Birth date cannot be in the future");
        }
        return Period.between(birthDate, today).getYears();
    }

    public static LocalDate getNextBirthday(LocalDate birthDate) {
        LocalDate today = LocalDate.now();
        LocalDate nextBirthday = birthDate.withYear(today.getYear());

        // If birthday already passed this year, use next year
        if (nextBirthday.isBefore(today) || nextBirthday.isEqual(today)) {
            nextBirthday = nextBirthday.plusYears(1);
        }
        return nextBirthday;
    }

    public static void main(String[] args) {
        LocalDate birthDate = LocalDate.of(1990, Month.JULY, 15);

        int age = calculateAge(birthDate);
        System.out.println("Birth date: " + birthDate);         // 1990-07-15
        System.out.println("Age: " + age + " years");           // e.g., 35 years

        LocalDate nextBirthday = getNextBirthday(birthDate);
        long daysUntil = LocalDate.now().until(nextBirthday, java.time.temporal.ChronoUnit.DAYS);
        System.out.println("Next birthday: " + nextBirthday);   // e.g., 2026-07-15
        System.out.println("Days until: " + daysUntil);         // e.g., 137
    }
}

3. LocalTime — Time Without Date

LocalTime represents a time without a date and without a time zone. It stores hours, minutes, seconds, and nanoseconds. Use it when you care about what time something happens but not what day: store opening hours, alarm times, daily schedules.

Creating a LocalTime

import java.time.LocalTime;

public class LocalTimeCreation {
    public static void main(String[] args) {

        // Current time
        LocalTime now = LocalTime.now();
        System.out.println("Now: " + now);  // e.g., 14:30:22.123456789

        // Specific time (hour, minute)
        LocalTime nineAM = LocalTime.of(9, 0);
        System.out.println("9 AM: " + nineAM);  // 09:00

        // Hour, minute, second
        LocalTime precise = LocalTime.of(14, 30, 45);
        System.out.println("Precise: " + precise);  // 14:30:45

        // Hour, minute, second, nanosecond
        LocalTime nano = LocalTime.of(14, 30, 45, 123456789);
        System.out.println("Nano: " + nano);  // 14:30:45.123456789

        // Parsing (ISO format: HH:mm:ss)
        LocalTime parsed = LocalTime.parse("08:15:30");
        System.out.println("Parsed: " + parsed);  // 08:15:30

        // Special constants
        System.out.println("Midnight: " + LocalTime.MIDNIGHT);  // 00:00
        System.out.println("Noon:     " + LocalTime.NOON);      // 12:00
        System.out.println("Min:      " + LocalTime.MIN);       // 00:00
        System.out.println("Max:      " + LocalTime.MAX);       // 23:59:59.999999999
    }
}

Getting Time Components

import java.time.LocalTime;

public class LocalTimeComponents {
    public static void main(String[] args) {
        LocalTime time = LocalTime.of(14, 30, 45, 123456789);

        System.out.println("Hour:   " + time.getHour());    // 14
        System.out.println("Minute: " + time.getMinute());  // 30
        System.out.println("Second: " + time.getSecond());  // 45
        System.out.println("Nano:   " + time.getNano());    // 123456789

        // Convert to seconds since midnight
        System.out.println("Seconds of day: " + time.toSecondOfDay());  // 52245

        // Convert to nanoseconds since midnight
        System.out.println("Nanos of day: " + time.toNanoOfDay());
    }
}

Manipulating and Comparing Times

import java.time.LocalTime;

public class LocalTimeManipulation {
    public static void main(String[] args) {
        LocalTime time = LocalTime.of(10, 30);

        // Adding
        System.out.println("Plus 2 hours:   " + time.plusHours(2));     // 12:30
        System.out.println("Plus 45 minutes:" + time.plusMinutes(45));   // 11:15
        System.out.println("Plus 30 seconds:" + time.plusSeconds(30));   // 10:30:30

        // Subtracting
        System.out.println("Minus 3 hours:  " + time.minusHours(3));    // 07:30

        // Time wraps around midnight
        LocalTime lateNight = LocalTime.of(23, 30);
        System.out.println("23:30 + 2 hours: " + lateNight.plusHours(2)); // 01:30

        // Comparing
        LocalTime morning = LocalTime.of(8, 0);
        LocalTime evening = LocalTime.of(20, 0);

        System.out.println(morning.isBefore(evening));  // true
        System.out.println(morning.isAfter(evening));   // false
    }
}

Practical Example: Business Hours Checker

A utility to check if a given time falls within business hours and calculate time until closing.

import java.time.Duration;
import java.time.LocalTime;

public class BusinessHoursChecker {
    private final LocalTime openTime;
    private final LocalTime closeTime;
    private final LocalTime lunchStart;
    private final LocalTime lunchEnd;

    public BusinessHoursChecker() {
        this.openTime = LocalTime.of(9, 0);     // 9:00 AM
        this.closeTime = LocalTime.of(17, 0);   // 5:00 PM
        this.lunchStart = LocalTime.of(12, 0);  // 12:00 PM
        this.lunchEnd = LocalTime.of(13, 0);    // 1:00 PM
    }

    public boolean isOpen(LocalTime time) {
        boolean withinHours = !time.isBefore(openTime) && time.isBefore(closeTime);
        boolean isLunchBreak = !time.isBefore(lunchStart) && time.isBefore(lunchEnd);
        return withinHours && !isLunchBreak;
    }

    public String getStatus(LocalTime time) {
        if (time.isBefore(openTime)) {
            Duration untilOpen = Duration.between(time, openTime);
            return "Closed. Opens in " + untilOpen.toMinutes() + " minutes.";
        }
        if (!time.isBefore(lunchStart) && time.isBefore(lunchEnd)) {
            Duration untilReopen = Duration.between(time, lunchEnd);
            return "Lunch break. Reopens in " + untilReopen.toMinutes() + " minutes.";
        }
        if (!time.isBefore(openTime) && time.isBefore(closeTime)) {
            Duration untilClose = Duration.between(time, closeTime);
            return "Open. Closes in " + untilClose.toHours() + "h " +
                   (untilClose.toMinutes() % 60) + "m.";
        }
        return "Closed for the day. Opens tomorrow at " + openTime;
    }

    public static void main(String[] args) {
        BusinessHoursChecker checker = new BusinessHoursChecker();

        LocalTime[] testTimes = {
            LocalTime.of(7, 30),   // Before opening
            LocalTime.of(10, 15),  // Morning hours
            LocalTime.of(12, 30),  // Lunch break
            LocalTime.of(15, 45),  // Afternoon hours
            LocalTime.of(18, 0)    // After closing
        };

        for (LocalTime time : testTimes) {
            System.out.printf("%s -> Open: %-5s | %s%n",
                time, checker.isOpen(time), checker.getStatus(time));
        }
        // Output:
        // 07:30 -> Open: false | Closed. Opens in 90 minutes.
        // 10:15 -> Open: true  | Open. Closes in 6h 45m.
        // 12:30 -> Open: false | Lunch break. Reopens in 30 minutes.
        // 15:45 -> Open: true  | Open. Closes in 1h 15m.
        // 18:00 -> Open: false | Closed for the day. Opens tomorrow at 09:00
    }
}

4. LocalDateTime — Combined Date and Time

LocalDateTime combines LocalDate and LocalTime into one object. It represents a date-time without a time zone. Use it when you need both the date and the time but the time zone is either irrelevant or implied: appointment timestamps within a single-timezone application, database DATETIME columns, event start times.

Creating a LocalDateTime

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;

public class LocalDateTimeCreation {
    public static void main(String[] args) {

        // Current date and time
        LocalDateTime now = LocalDateTime.now();
        System.out.println("Now: " + now);  // e.g., 2026-02-28T14:30:22.123

        // Specific date and time
        LocalDateTime meeting = LocalDateTime.of(2026, Month.MARCH, 15, 10, 30);
        System.out.println("Meeting: " + meeting);  // 2026-03-15T10:30

        // With seconds
        LocalDateTime precise = LocalDateTime.of(2026, 3, 15, 10, 30, 45);
        System.out.println("Precise: " + precise);  // 2026-03-15T10:30:45

        // From existing LocalDate and LocalTime
        LocalDate date = LocalDate.of(2026, 6, 15);
        LocalTime time = LocalTime.of(14, 0);
        LocalDateTime combined = LocalDateTime.of(date, time);
        System.out.println("Combined: " + combined);  // 2026-06-15T14:00

        // Attach time to a date
        LocalDateTime startOfDay = date.atStartOfDay();
        System.out.println("Start of day: " + startOfDay);  // 2026-06-15T00:00

        LocalDateTime atTime = date.atTime(9, 30);
        System.out.println("At 9:30: " + atTime);  // 2026-06-15T09:30

        // Parse from string
        LocalDateTime parsed = LocalDateTime.parse("2026-03-15T10:30:00");
        System.out.println("Parsed: " + parsed);  // 2026-03-15T10:30
    }
}

Converting Between LocalDate, LocalTime, and LocalDateTime

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;

public class LocalDateTimeConversion {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.of(2026, 3, 15, 10, 30, 45);

        // Extract date and time components
        LocalDate date = dateTime.toLocalDate();
        LocalTime time = dateTime.toLocalTime();
        System.out.println("Date part: " + date);  // 2026-03-15
        System.out.println("Time part: " + time);  // 10:30:45

        // Manipulation works the same way
        LocalDateTime nextMonth = dateTime.plusMonths(1);
        System.out.println("Next month: " + nextMonth);  // 2026-04-15T10:30:45

        LocalDateTime twoHoursLater = dateTime.plusHours(2);
        System.out.println("+2 hours: " + twoHoursLater);  // 2026-03-15T12:30:45

        // Replace components
        LocalDateTime newTime = dateTime.withHour(8).withMinute(0);
        System.out.println("Changed to 8 AM: " + newTime);  // 2026-03-15T08:00:45
    }
}

Practical Example: Appointment Scheduler

A simple appointment system that checks for conflicts and validates time slots.

import java.time.LocalDateTime;
import java.time.Duration;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;

public class AppointmentScheduler {

    static class Appointment {
        String title;
        LocalDateTime start;
        LocalDateTime end;

        Appointment(String title, LocalDateTime start, Duration duration) {
            this.title = title;
            this.start = start;
            this.end = start.plus(duration);
        }

        boolean conflictsWith(Appointment other) {
            return this.start.isBefore(other.end) && this.end.isAfter(other.start);
        }

        @Override
        public String toString() {
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("MMM dd, HH:mm");
            return String.format("%s [%s - %s]", title, start.format(fmt), end.format(fmt));
        }
    }

    private final List appointments = new ArrayList<>();

    public boolean schedule(String title, LocalDateTime start, Duration duration) {
        Appointment newAppt = new Appointment(title, start, duration);

        // Check for conflicts with existing appointments
        for (Appointment existing : appointments) {
            if (existing.conflictsWith(newAppt)) {
                System.out.println("CONFLICT: '" + title + "' overlaps with '" + existing.title + "'");
                return false;
            }
        }

        // Validate business hours (9 AM - 5 PM)
        if (newAppt.start.getHour() < 9 || newAppt.end.getHour() > 17 ||
            (newAppt.end.getHour() == 17 && newAppt.end.getMinute() > 0)) {
            System.out.println("REJECTED: '" + title + "' falls outside business hours (9-5)");
            return false;
        }

        appointments.add(newAppt);
        System.out.println("BOOKED: " + newAppt);
        return true;
    }

    public static void main(String[] args) {
        AppointmentScheduler scheduler = new AppointmentScheduler();

        LocalDateTime monday = LocalDateTime.of(2026, 3, 16, 9, 0);

        scheduler.schedule("Team Standup", monday, Duration.ofMinutes(30));
        // BOOKED: Team Standup [Mar 16, 09:00 - Mar 16, 09:30]

        scheduler.schedule("Code Review", monday.plusHours(1), Duration.ofHours(1));
        // BOOKED: Code Review [Mar 16, 10:00 - Mar 16, 11:00]

        scheduler.schedule("Quick Chat", monday.plusMinutes(15), Duration.ofMinutes(20));
        // CONFLICT: 'Quick Chat' overlaps with 'Team Standup'

        scheduler.schedule("Late Meeting", monday.withHour(16), Duration.ofHours(2));
        // REJECTED: 'Late Meeting' falls outside business hours (9-5)

        scheduler.schedule("Lunch 1:1", monday.withHour(12), Duration.ofHours(1));
        // BOOKED: Lunch 1:1 [Mar 16, 12:00 - Mar 16, 13:00]
    }
}

5. ZonedDateTime — Time Zones

ZonedDateTime is a date-time with a full time zone, such as America/New_York or Europe/London. This is the class you need whenever time zone matters: scheduling meetings across countries, flight departure/arrival times, coordinating deployments across data centers.

Java uses the IANA Time Zone Database (also known as the Olson database). Always use region-based zone IDs like America/New_York rather than abbreviations like EST, because abbreviations are ambiguous (CST could mean Central Standard Time or China Standard Time).

Creating a ZonedDateTime

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.ZoneOffset;

public class ZonedDateTimeCreation {
    public static void main(String[] args) {

        // Current date-time in the system's default zone
        ZonedDateTime now = ZonedDateTime.now();
        System.out.println("Now: " + now);
        // e.g., 2026-02-28T14:30:00-05:00[America/New_York]

        // Current date-time in a specific zone
        ZonedDateTime tokyo = ZonedDateTime.now(ZoneId.of("Asia/Tokyo"));
        System.out.println("Tokyo: " + tokyo);

        // From LocalDateTime + ZoneId
        LocalDateTime ldt = LocalDateTime.of(2026, 3, 15, 10, 30);
        ZonedDateTime newYork = ldt.atZone(ZoneId.of("America/New_York"));
        System.out.println("New York: " + newYork);
        // 2026-03-15T10:30-04:00[America/New_York]  (EDT in March)

        // Using of()
        ZonedDateTime london = ZonedDateTime.of(2026, 3, 15, 10, 30, 0, 0,
            ZoneId.of("Europe/London"));
        System.out.println("London: " + london);

        // Parse from string
        ZonedDateTime parsed = ZonedDateTime.parse(
            "2026-03-15T10:30:00-04:00[America/New_York]");
        System.out.println("Parsed: " + parsed);

        // Common zone IDs
        System.out.println("\nDefault zone: " + ZoneId.systemDefault());
        System.out.println("UTC: " + ZoneId.of("UTC"));
    }
}

Converting Between Time Zones

This is one of the most powerful features of ZonedDateTime: converting the same instant in time to different zones. The withZoneSameInstant() method changes the zone while keeping the same moment in time. The withZoneSameLocal() method keeps the local date-time and changes the zone (rarely what you want).

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class TimeZoneConversion {
    public static void main(String[] args) {

        // A meeting is at 10:00 AM New York time
        ZonedDateTime newYork = ZonedDateTime.of(
            LocalDateTime.of(2026, 6, 15, 10, 0),
            ZoneId.of("America/New_York")
        );

        // What time is that in other cities?
        ZonedDateTime london = newYork.withZoneSameInstant(ZoneId.of("Europe/London"));
        ZonedDateTime tokyo = newYork.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
        ZonedDateTime sydney = newYork.withZoneSameInstant(ZoneId.of("Australia/Sydney"));
        ZonedDateTime losAngeles = newYork.withZoneSameInstant(ZoneId.of("America/Los_Angeles"));

        System.out.println("Meeting time across zones:");
        System.out.println("New York:    " + newYork);     // 10:00 EDT (-04:00)
        System.out.println("London:      " + london);       // 15:00 BST (+01:00)
        System.out.println("Tokyo:       " + tokyo);        // 23:00 JST (+09:00)
        System.out.println("Sydney:      " + sydney);       // 00:00+1 AEST (+10:00)
        System.out.println("Los Angeles: " + losAngeles);   // 07:00 PDT (-07:00)
    }
}

Daylight Saving Time Awareness

ZonedDateTime automatically handles daylight saving time (DST) transitions. This is critical for correctness — naive date-time calculations that ignore DST can be off by an hour.

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.Duration;

public class DaylightSavingDemo {
    public static void main(String[] args) {
        ZoneId eastern = ZoneId.of("America/New_York");

        // Spring Forward: March 8, 2026 at 2:00 AM -> clocks jump to 3:00 AM
        ZonedDateTime beforeSpringForward = ZonedDateTime.of(
            LocalDateTime.of(2026, 3, 8, 1, 30), eastern);
        ZonedDateTime afterSpringForward = beforeSpringForward.plusHours(1);

        System.out.println("Before spring forward: " + beforeSpringForward);
        // 2026-03-08T01:30-05:00[America/New_York]
        System.out.println("After +1 hour:         " + afterSpringForward);
        // 2026-03-08T03:30-04:00[America/New_York]  (skipped 2:30!)

        // Duration correctly accounts for DST
        Duration gap = Duration.between(beforeSpringForward, afterSpringForward);
        System.out.println("Actual duration: " + gap);  // PT1H (still 1 hour of real time)

        // Fall Back: November 1, 2026 at 2:00 AM -> clocks fall back to 1:00 AM
        ZonedDateTime beforeFallBack = ZonedDateTime.of(
            LocalDateTime.of(2026, 11, 1, 0, 30), eastern);
        ZonedDateTime afterFallBack = beforeFallBack.plusHours(2);
        System.out.println("\nBefore fall back: " + beforeFallBack);
        System.out.println("After +2 hours:   " + afterFallBack);
    }
}

Practical Example: Meeting Scheduler Across Time Zones

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.List;

public class MeetingScheduler {

    static class Participant {
        String name;
        ZoneId zone;
        LocalTime workStart;
        LocalTime workEnd;

        Participant(String name, String zoneId) {
            this.name = name;
            this.zone = ZoneId.of(zoneId);
            this.workStart = LocalTime.of(9, 0);
            this.workEnd = LocalTime.of(17, 0);
        }

        boolean isAvailable(ZonedDateTime meetingTime, Duration duration) {
            ZonedDateTime localMeeting = meetingTime.withZoneSameInstant(zone);
            LocalTime meetStart = localMeeting.toLocalTime();
            LocalTime meetEnd = meetStart.plus(duration);
            return !meetStart.isBefore(workStart) && !meetEnd.isAfter(workEnd);
        }
    }

    public static void findMeetingTime(List participants,
                                        LocalDate date, Duration duration) {
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("HH:mm z");

        System.out.println("Finding " + duration.toMinutes() + "-minute slot on " + date);
        System.out.println("---");

        // Try every hour from 0:00 to 23:00 UTC
        for (int hour = 0; hour < 24; hour++) {
            ZonedDateTime candidate = ZonedDateTime.of(date, LocalTime.of(hour, 0),
                ZoneId.of("UTC"));

            boolean allAvailable = participants.stream()
                .allMatch(p -> p.isAvailable(candidate, duration));

            if (allAvailable) {
                System.out.println("AVAILABLE SLOT:");
                for (Participant p : participants) {
                    ZonedDateTime local = candidate.withZoneSameInstant(p.zone);
                    System.out.printf("  %-12s -> %s%n", p.name, local.format(fmt));
                }
                System.out.println();
            }
        }
    }

    public static void main(String[] args) {
        List team = Arrays.asList(
            new Participant("Alice", "America/New_York"),
            new Participant("Bob", "Europe/London"),
            new Participant("Chika", "Asia/Tokyo")
        );

        findMeetingTime(team, LocalDate.of(2026, 6, 15), Duration.ofHours(1));
        // Output: Shows overlapping work hours across all three zones
        // The only common slot is typically around 21:00-23:00 UTC
        // (morning in Tokyo, afternoon in London, morning in New York)
    }
}

6. Instant — Machine-Readable Timestamp

Instant represents a single point on the timeline, measured as nanoseconds since the Unix epoch (January 1, 1970, 00:00:00 UTC). It has no concept of human time zones, months, or days — it is purely a machine timestamp. Use it for logging, measuring elapsed time, recording when events occurred, and storing timestamps in databases.

Creating and Using Instants

import java.time.Instant;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class InstantDemo {
    public static void main(String[] args) {

        // Current instant (always in UTC)
        Instant now = Instant.now();
        System.out.println("Now: " + now);
        // e.g., 2026-02-28T19:30:00.123456789Z  (the Z means UTC)

        // From epoch seconds
        Instant epoch = Instant.EPOCH;
        System.out.println("Epoch: " + epoch);  // 1970-01-01T00:00:00Z

        Instant fromSeconds = Instant.ofEpochSecond(1_000_000_000);
        System.out.println("1 billion seconds: " + fromSeconds);  // 2001-09-09T01:46:40Z

        // From epoch milliseconds (common in legacy APIs and JavaScript)
        Instant fromMillis = Instant.ofEpochMilli(System.currentTimeMillis());
        System.out.println("From millis: " + fromMillis);

        // Getting epoch values
        System.out.println("Epoch seconds: " + now.getEpochSecond());
        System.out.println("Nano adjustment: " + now.getNano());

        // Arithmetic
        Instant oneHourLater = now.plus(Duration.ofHours(1));
        Instant yesterday = now.minus(Duration.ofDays(1));

        // Duration between two instants
        Duration between = Duration.between(yesterday, now);
        System.out.println("Between: " + between);  // PT24H

        // Convert to ZonedDateTime for human-readable display
        ZonedDateTime zdt = now.atZone(ZoneId.of("America/New_York"));
        System.out.println("As New York time: " + zdt);
    }
}

Practical Example: Measuring Execution Time

Instant is the ideal tool for benchmarking and measuring how long operations take.

import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;

public class ExecutionTimer {

    @FunctionalInterface
    interface Task {
        void run();
    }

    public static Duration measure(Task task) {
        Instant start = Instant.now();
        task.run();
        Instant end = Instant.now();
        return Duration.between(start, end);
    }

    public static void main(String[] args) {
        int size = 100_000;

        // Measure ArrayList add performance
        Duration arrayListTime = measure(() -> {
            List list = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                list.add(0, i);  // Insert at beginning (worst case)
            }
        });

        // Measure LinkedList add performance
        Duration linkedListTime = measure(() -> {
            List list = new LinkedList<>();
            for (int i = 0; i < size; i++) {
                list.add(0, i);  // Insert at beginning (best case for linked list)
            }
        });

        System.out.println("Inserting " + size + " elements at index 0:");
        System.out.println("ArrayList:  " + arrayListTime.toMillis() + " ms");
        System.out.println("LinkedList: " + linkedListTime.toMillis() + " ms");
        // Example output:
        // ArrayList:  1245 ms
        // LinkedList: 12 ms
    }
}

7. Period and Duration

Java provides two classes for representing amounts of time:

  • Period -- a date-based amount of time (years, months, days). Think: "2 years, 3 months, and 5 days"
  • Duration -- a time-based amount of time (hours, minutes, seconds, nanoseconds). Think: "2 hours and 30 minutes"

The distinction matters because date-based arithmetic and time-based arithmetic behave differently. Adding "1 month" to January 31 gives February 28 (or 29), but adding "30 days" gives March 2. These are different operations.

Period (Date-Based)

import java.time.LocalDate;
import java.time.Period;

public class PeriodDemo {
    public static void main(String[] args) {

        // Creating Periods
        Period twoYears = Period.ofYears(2);
        Period threeMonths = Period.ofMonths(3);
        Period tenDays = Period.ofDays(10);
        Period custom = Period.of(1, 6, 15);  // 1 year, 6 months, 15 days

        System.out.println("Custom: " + custom);  // P1Y6M15D (ISO 8601)

        // Period between two dates
        LocalDate startDate = LocalDate.of(2024, 1, 1);
        LocalDate endDate = LocalDate.of(2026, 3, 15);
        Period between = Period.between(startDate, endDate);

        System.out.println("Between: " + between);               // P2Y2M14D
        System.out.println("Years:  " + between.getYears());     // 2
        System.out.println("Months: " + between.getMonths());    // 2
        System.out.println("Days:   " + between.getDays());      // 14
        System.out.println("Total months: " + between.toTotalMonths());  // 26

        // Applying a Period to a date
        LocalDate today = LocalDate.of(2026, 2, 28);
        LocalDate future = today.plus(custom);
        System.out.println("Today + " + custom + " = " + future);
        // 2026-02-28 + P1Y6M15D = 2027-09-12

        // Period arithmetic
        Period doubled = custom.multipliedBy(2);
        System.out.println("Doubled: " + doubled);  // P2Y12M30D

        Period negated = custom.negated();
        System.out.println("Negated: " + negated);  // P-1Y-6M-15D

        // Checking if zero
        System.out.println("Is zero: " + Period.ZERO.isZero());  // true
        System.out.println("Is negative: " + negated.isNegative());  // true
    }
}

Duration (Time-Based)

import java.time.Duration;
import java.time.LocalTime;
import java.time.Instant;

public class DurationDemo {
    public static void main(String[] args) {

        // Creating Durations
        Duration twoHours = Duration.ofHours(2);
        Duration thirtyMinutes = Duration.ofMinutes(30);
        Duration fiveSeconds = Duration.ofSeconds(5);
        Duration halfSecond = Duration.ofMillis(500);

        System.out.println("2 hours: " + twoHours);          // PT2H
        System.out.println("30 minutes: " + thirtyMinutes);   // PT30M
        System.out.println("5 seconds: " + fiveSeconds);      // PT5S

        // Duration between two times
        LocalTime start = LocalTime.of(9, 0);
        LocalTime end = LocalTime.of(17, 30);
        Duration workDay = Duration.between(start, end);

        System.out.println("Work day: " + workDay);           // PT8H30M
        System.out.println("Hours:   " + workDay.toHours());  // 8
        System.out.println("Minutes: " + workDay.toMinutes()); // 510
        System.out.println("Seconds: " + workDay.getSeconds()); // 30600

        // Java 9+ part extraction
        System.out.println("Hours part:   " + workDay.toHoursPart());    // 8
        System.out.println("Minutes part: " + workDay.toMinutesPart());  // 30

        // Duration between two Instants
        Instant i1 = Instant.parse("2026-02-28T10:00:00Z");
        Instant i2 = Instant.parse("2026-02-28T14:30:00Z");
        Duration gap = Duration.between(i1, i2);
        System.out.println("Gap: " + gap);  // PT4H30M

        // Arithmetic
        Duration total = twoHours.plus(thirtyMinutes);
        System.out.println("Total: " + total);  // PT2H30M

        Duration doubled = twoHours.multipliedBy(2);
        System.out.println("Doubled: " + doubled);  // PT4H

        // Apply to time
        LocalTime lunchEnd = LocalTime.of(13, 0);
        LocalTime afternoonEnd = lunchEnd.plus(Duration.ofHours(4));
        System.out.println("Afternoon ends: " + afternoonEnd);  // 17:00
    }
}

Period vs Duration -- When to Use Each

Aspect Period Duration
Measures Years, months, days Hours, minutes, seconds, nanos
Works with LocalDate, LocalDateTime LocalTime, LocalDateTime, Instant
Use case Human calendar concepts Precise time measurements
Example "Renew subscription in 1 year" "Session expires in 30 minutes"
ISO format P1Y2M3D PT2H30M15S

Practical Example: Subscription Expiry

import java.time.LocalDate;
import java.time.Period;
import java.time.temporal.ChronoUnit;

public class SubscriptionManager {

    enum Plan {
        TRIAL(Period.ofDays(14)),
        MONTHLY(Period.ofMonths(1)),
        YEARLY(Period.ofYears(1));

        final Period duration;
        Plan(Period duration) { this.duration = duration; }
    }

    static class Subscription {
        String user;
        Plan plan;
        LocalDate startDate;
        LocalDate expiryDate;

        Subscription(String user, Plan plan, LocalDate startDate) {
            this.user = user;
            this.plan = plan;
            this.startDate = startDate;
            this.expiryDate = startDate.plus(plan.duration);
        }

        boolean isActive(LocalDate today) {
            return !today.isAfter(expiryDate);
        }

        long daysRemaining(LocalDate today) {
            if (!isActive(today)) return 0;
            return ChronoUnit.DAYS.between(today, expiryDate);
        }

        Subscription renew() {
            return new Subscription(user, plan, expiryDate);
        }
    }

    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 2, 28);

        Subscription trial = new Subscription("Alice", Plan.TRIAL, today);
        Subscription monthly = new Subscription("Bob", Plan.MONTHLY, today);
        Subscription yearly = new Subscription("Charlie", Plan.YEARLY, today);

        System.out.printf("%-10s | Plan: %-8s | Expires: %s | Days left: %d%n",
            trial.user, trial.plan, trial.expiryDate, trial.daysRemaining(today));
        System.out.printf("%-10s | Plan: %-8s | Expires: %s | Days left: %d%n",
            monthly.user, monthly.plan, monthly.expiryDate, monthly.daysRemaining(today));
        System.out.printf("%-10s | Plan: %-8s | Expires: %s | Days left: %d%n",
            yearly.user, yearly.plan, yearly.expiryDate, yearly.daysRemaining(today));

        // Output:
        // Alice      | Plan: TRIAL    | Expires: 2026-03-14 | Days left: 14
        // Bob        | Plan: MONTHLY  | Expires: 2026-03-28 | Days left: 28
        // Charlie    | Plan: YEARLY   | Expires: 2027-02-28 | Days left: 365

        // Renew Bob's subscription
        Subscription renewed = monthly.renew();
        System.out.println("\nBob renewed: expires " + renewed.expiryDate);
        // Bob renewed: expires 2026-04-28
    }
}

8. DateTimeFormatter -- Parsing and Formatting

DateTimeFormatter is the java.time replacement for SimpleDateFormat. Unlike its predecessor, it is immutable and thread-safe -- you can safely share a single formatter instance across threads.

Predefined Formatters

Java provides several built-in formatters for common ISO 8601 formats:

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;

public class PredefinedFormatters {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2026, 3, 15);
        LocalDateTime dateTime = LocalDateTime.of(2026, 3, 15, 14, 30, 0);
        ZonedDateTime zdt = dateTime.atZone(ZoneId.of("America/New_York"));

        // ISO formatters (most common for APIs and data exchange)
        System.out.println(date.format(DateTimeFormatter.ISO_LOCAL_DATE));
        // 2026-03-15

        System.out.println(dateTime.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
        // 2026-03-15T14:30:00

        System.out.println(zdt.format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
        // 2026-03-15T14:30:00-04:00[America/New_York]

        System.out.println(date.format(DateTimeFormatter.ISO_ORDINAL_DATE));
        // 2026-074  (day 74 of the year)

        System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));
        // 20260315
    }
}

Custom Patterns with ofPattern()

For human-readable formats, you define your own pattern using DateTimeFormatter.ofPattern(). Here are the most commonly used pattern symbols:

Symbol Meaning Example
yyyy 4-digit year 2026
yy 2-digit year 26
MM Month (01-12) 03
MMM Abbreviated month Mar
MMMM Full month name March
dd Day of month (01-31) 15
EEE Abbreviated day Sun
EEEE Full day name Sunday
HH Hour (00-23) 14
hh Hour (01-12) 02
mm Minute (00-59) 30
ss Second (00-59) 45
a AM/PM PM
z Time zone name EDT
Z Zone offset -0400
VV Zone ID America/New_York

Critical warning: MM is months, mm is minutes. Mixing them up is one of the most common date/time bugs. The pattern "yyyy-mm-dd" gives you "2026-30-15" (using minutes instead of months).

import java.time.LocalDateTime;
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class CustomFormatting {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.of(2026, 3, 15, 14, 30, 45);

        // Common custom patterns
        DateTimeFormatter f1 = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        System.out.println(dateTime.format(f1));  // 15/03/2026

        DateTimeFormatter f2 = DateTimeFormatter.ofPattern("MM-dd-yyyy HH:mm");
        System.out.println(dateTime.format(f2));  // 03-15-2026 14:30

        DateTimeFormatter f3 = DateTimeFormatter.ofPattern("EEEE, MMMM dd, yyyy");
        System.out.println(dateTime.format(f3));  // Sunday, March 15, 2026

        DateTimeFormatter f4 = DateTimeFormatter.ofPattern("hh:mm a");
        System.out.println(dateTime.format(f4));  // 02:30 PM

        DateTimeFormatter f5 = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        System.out.println(dateTime.format(f5));  // 2026/03/15 14:30:45

        // Locale-specific formatting
        DateTimeFormatter german = DateTimeFormatter.ofPattern(
            "EEEE, dd. MMMM yyyy", Locale.GERMAN);
        System.out.println(dateTime.format(german));  // Sonntag, 15. März 2026

        DateTimeFormatter french = DateTimeFormatter.ofPattern(
            "dd MMMM yyyy", Locale.FRENCH);
        System.out.println(dateTime.format(french));  // 15 mars 2026

        // With time zone
        ZonedDateTime zdt = dateTime.atZone(ZoneId.of("America/New_York"));
        DateTimeFormatter withZone = DateTimeFormatter.ofPattern(
            "yyyy-MM-dd HH:mm:ss z (VV)");
        System.out.println(zdt.format(withZone));
        // 2026-03-15 14:30:45 EDT (America/New_York)
    }
}

Parsing Strings to Dates

Parsing is the reverse of formatting: converting a String into a date/time object. If the string does not match the expected format, a DateTimeParseException is thrown.

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

public class ParsingDates {
    public static void main(String[] args) {

        // Parse ISO format (no formatter needed)
        LocalDate date = LocalDate.parse("2026-03-15");
        System.out.println("Parsed ISO: " + date);  // 2026-03-15

        // Parse custom format
        DateTimeFormatter slashFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy");
        LocalDate custom = LocalDate.parse("15/03/2026", slashFormat);
        System.out.println("Parsed custom: " + custom);  // 2026-03-15

        DateTimeFormatter usFormat = DateTimeFormatter.ofPattern("MM-dd-yyyy");
        LocalDate usDate = LocalDate.parse("03-15-2026", usFormat);
        System.out.println("Parsed US: " + usDate);  // 2026-03-15

        DateTimeFormatter fullFormat = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
        LocalDateTime dateTime = LocalDateTime.parse("2026/03/15 14:30:45", fullFormat);
        System.out.println("Parsed datetime: " + dateTime);  // 2026-03-15T14:30:45

        // Always handle parse exceptions in production code
        try {
            LocalDate bad = LocalDate.parse("not-a-date");
        } catch (DateTimeParseException e) {
            System.out.println("Parse error: " + e.getMessage());
            // Text 'not-a-date' could not be parsed at index 0
        }

        try {
            // Wrong format: trying to parse US format with slash formatter
            LocalDate wrong = LocalDate.parse("03-15-2026", slashFormat);
        } catch (DateTimeParseException e) {
            System.out.println("Format mismatch: " + e.getMessage());
        }
    }
}

9. Converting Between Types

In real applications, you frequently need to convert between the different date/time types. Here is a comprehensive reference for all common conversions.

import java.time.*;

public class TypeConversions {
    public static void main(String[] args) {

        // ============================================
        // LocalDate <-> LocalDateTime
        // ============================================
        LocalDate date = LocalDate.of(2026, 3, 15);

        // LocalDate -> LocalDateTime (add time)
        LocalDateTime atStartOfDay = date.atStartOfDay();           // 2026-03-15T00:00
        LocalDateTime atSpecificTime = date.atTime(14, 30);         // 2026-03-15T14:30
        LocalDateTime atLocalTime = date.atTime(LocalTime.NOON);    // 2026-03-15T12:00

        // LocalDateTime -> LocalDate (drop time)
        LocalDateTime dateTime = LocalDateTime.of(2026, 3, 15, 14, 30);
        LocalDate dateOnly = dateTime.toLocalDate();                // 2026-03-15
        LocalTime timeOnly = dateTime.toLocalTime();                // 14:30

        // ============================================
        // LocalDateTime <-> ZonedDateTime
        // ============================================

        // LocalDateTime -> ZonedDateTime (add zone)
        ZonedDateTime zoned = dateTime.atZone(ZoneId.of("America/New_York"));
        // 2026-03-15T14:30-04:00[America/New_York]

        // ZonedDateTime -> LocalDateTime (drop zone)
        LocalDateTime local = zoned.toLocalDateTime();              // 2026-03-15T14:30

        // ZonedDateTime -> LocalDate / LocalTime
        LocalDate zonedDate = zoned.toLocalDate();                  // 2026-03-15
        LocalTime zonedTime = zoned.toLocalTime();                  // 14:30

        // ============================================
        // ZonedDateTime <-> Instant
        // ============================================

        // ZonedDateTime -> Instant (to UTC epoch)
        Instant instant = zoned.toInstant();
        System.out.println("Instant: " + instant);
        // 2026-03-15T18:30:00Z  (14:30 EDT = 18:30 UTC)

        // Instant -> ZonedDateTime (add zone)
        ZonedDateTime fromInstant = instant.atZone(ZoneId.of("Asia/Tokyo"));
        System.out.println("Tokyo: " + fromInstant);
        // 2026-03-16T03:30+09:00[Asia/Tokyo]

        // ============================================
        // Legacy Date <-> Modern Types
        // ============================================
        java.util.Date legacyDate = new java.util.Date();

        // java.util.Date -> Instant
        Instant fromLegacy = legacyDate.toInstant();

        // Instant -> java.util.Date
        java.util.Date backToLegacy = java.util.Date.from(fromLegacy);

        // java.util.Date -> LocalDate (via Instant + zone)
        LocalDate fromLegacyDate = legacyDate.toInstant()
            .atZone(ZoneId.systemDefault())
            .toLocalDate();

        // LocalDate -> java.util.Date (via Instant + zone)
        java.util.Date toLegacyDate = java.util.Date.from(
            date.atStartOfDay(ZoneId.systemDefault()).toInstant());

        // java.sql.Date <-> LocalDate (direct conversion)
        java.sql.Date sqlDate = java.sql.Date.valueOf(date);
        LocalDate fromSql = sqlDate.toLocalDate();

        // java.sql.Timestamp <-> LocalDateTime
        java.sql.Timestamp timestamp = java.sql.Timestamp.valueOf(dateTime);
        LocalDateTime fromTimestamp = timestamp.toLocalDateTime();

        System.out.println("All conversions successful!");
    }
}

Conversion Quick Reference

From To Method
LocalDate LocalDateTime date.atStartOfDay() or date.atTime(14, 30)
LocalDateTime LocalDate dateTime.toLocalDate()
LocalDateTime LocalTime dateTime.toLocalTime()
LocalDateTime ZonedDateTime dateTime.atZone(zoneId)
ZonedDateTime LocalDateTime zoned.toLocalDateTime()
ZonedDateTime Instant zoned.toInstant()
Instant ZonedDateTime instant.atZone(zoneId)
java.util.Date Instant legacyDate.toInstant()
Instant java.util.Date Date.from(instant)
java.sql.Date LocalDate sqlDate.toLocalDate()
LocalDate java.sql.Date java.sql.Date.valueOf(date)

10. Common Date Operations

Here are the operations you will use most often in real-world Java applications. Each one comes up regularly in business logic, reporting, and scheduling systems.

First and Last Day of Month

import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class MonthBoundaries {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2026, 3, 15);

        LocalDate firstDay = date.with(TemporalAdjusters.firstDayOfMonth());
        LocalDate lastDay = date.with(TemporalAdjusters.lastDayOfMonth());

        System.out.println("Date:      " + date);       // 2026-03-15
        System.out.println("First day: " + firstDay);   // 2026-03-01
        System.out.println("Last day:  " + lastDay);    // 2026-03-31

        // First and last day of year
        LocalDate firstOfYear = date.with(TemporalAdjusters.firstDayOfYear());
        LocalDate lastOfYear = date.with(TemporalAdjusters.lastDayOfYear());
        System.out.println("First of year: " + firstOfYear);  // 2026-01-01
        System.out.println("Last of year:  " + lastOfYear);   // 2026-12-31

        // First day of next month
        LocalDate firstOfNext = date.with(TemporalAdjusters.firstDayOfNextMonth());
        System.out.println("First of next month: " + firstOfNext);  // 2026-04-01
    }
}

Finding Next/Previous Day of Week

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.TemporalAdjusters;

public class DayOfWeekOperations {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2026, 2, 28);  // Saturday

        // Next Monday (strictly after the given date)
        LocalDate nextMonday = date.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
        System.out.println("Next Monday: " + nextMonday);  // 2026-03-02

        // Next or same Monday (returns same date if it's already Monday)
        LocalDate nextOrSame = date.with(TemporalAdjusters.nextOrSame(DayOfWeek.SATURDAY));
        System.out.println("Next or same Saturday: " + nextOrSame);  // 2026-02-28 (already Sat)

        // Previous Friday
        LocalDate prevFriday = date.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
        System.out.println("Previous Friday: " + prevFriday);  // 2026-02-27

        // First Monday of the month
        LocalDate firstMonday = date.with(
            TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
        System.out.println("First Monday: " + firstMonday);  // 2026-02-02

        // Last Friday of the month
        LocalDate lastFriday = date.with(
            TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY));
        System.out.println("Last Friday: " + lastFriday);  // 2026-02-27
    }
}

Weekend Check and Business Days

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;

public class BusinessDayCalculator {

    public static boolean isWeekend(LocalDate date) {
        DayOfWeek day = date.getDayOfWeek();
        return day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY;
    }

    public static boolean isBusinessDay(LocalDate date) {
        return !isWeekend(date);
        // In production, also check a holiday calendar
    }

    public static LocalDate addBusinessDays(LocalDate start, int days) {
        LocalDate result = start;
        int added = 0;
        while (added < days) {
            result = result.plusDays(1);
            if (isBusinessDay(result)) {
                added++;
            }
        }
        return result;
    }

    public static long countBusinessDays(LocalDate start, LocalDate end) {
        long count = 0;
        LocalDate current = start;
        while (!current.isAfter(end)) {
            if (isBusinessDay(current)) {
                count++;
            }
            current = current.plusDays(1);
        }
        return count;
    }

    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 2, 28);  // Saturday

        System.out.println(today + " is weekend: " + isWeekend(today));  // true

        // Add 5 business days (skip weekends)
        LocalDate deadline = addBusinessDays(today, 5);
        System.out.println("5 business days from " + today + ": " + deadline);
        // 2026-03-06 (Friday)

        // Count business days in March 2026
        LocalDate marchStart = LocalDate.of(2026, 3, 1);
        LocalDate marchEnd = LocalDate.of(2026, 3, 31);
        long businessDays = countBusinessDays(marchStart, marchEnd);
        System.out.println("Business days in March 2026: " + businessDays);  // 22
    }
}

Generating Date Ranges

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class DateRanges {
    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2026, 3, 1);
        LocalDate end = LocalDate.of(2026, 3, 7);

        // Java 9+ datesUntil -- stream of dates from start (inclusive) to end (exclusive)
        List week = start.datesUntil(end.plusDays(1))
            .collect(Collectors.toList());
        System.out.println("Week: " + week);
        // [2026-03-01, 2026-03-02, ..., 2026-03-07]

        // Every Monday in March 2026
        LocalDate marchStart = LocalDate.of(2026, 3, 1);
        LocalDate marchEnd = LocalDate.of(2026, 3, 31);
        List mondays = marchStart.datesUntil(marchEnd.plusDays(1))
            .filter(d -> d.getDayOfWeek() == java.time.DayOfWeek.MONDAY)
            .collect(Collectors.toList());
        System.out.println("Mondays in March: " + mondays);
        // [2026-03-02, 2026-03-09, 2026-03-16, 2026-03-23, 2026-03-30]

        // Days between two dates
        long daysBetween = ChronoUnit.DAYS.between(start, end);
        System.out.println("Days between: " + daysBetween);  // 6
    }
}

11. Legacy Date API -- java.util.Date and Calendar

Before Java 8, the only date/time classes in the standard library were java.util.Date and java.util.Calendar. You will still encounter them in legacy codebases, older libraries, and frameworks that predate Java 8. Here is what you need to know to work with them and migrate away from them.

Why You Should NOT Use Them in New Code

  • Mutable -- Both Date and Calendar can be modified after creation, leading to bugs when objects are shared
  • Confusing API -- Date.getYear() returns year minus 1900, months are 0-indexed (January = 0)
  • Not thread-safe -- SimpleDateFormat is notorious for causing subtle concurrency bugs
  • Poor design -- Date represents both a date and a time, Calendar mixes concerns
import java.util.Date;
import java.util.Calendar;
import java.text.SimpleDateFormat;

public class LegacyDateProblems {
    public static void main(String[] args) throws Exception {

        // Problem 1: Mutable objects
        Date date = new Date();
        Date copy = date;      // Not a copy! Same reference.
        copy.setTime(0);       // This also changes 'date'!
        System.out.println("Original date changed: " + date);
        // Thu Jan 01 00:00:00 UTC 1970  (modified!)

        // Problem 2: Confusing month indexing (0-based)
        Calendar cal = Calendar.getInstance();
        cal.set(2026, 0, 15);  // January is 0, not 1!
        System.out.println("Month confusion: " + cal.getTime());

        // This is WRONG -- creates February 15, not January!
        cal.set(2026, 1, 15);  // 1 = February

        // Problem 3: getYear() returns year minus 1900
        Date now = new Date();
        System.out.println("getYear(): " + now.getYear());  // 126, not 2026!

        // Problem 4: SimpleDateFormat is not thread-safe
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        // DANGER: sharing this across threads causes data corruption
        // Use DateTimeFormatter instead (thread-safe)
    }
}

Migrating from Legacy to Modern

When you encounter legacy date code, convert to java.time as early as possible and convert back only at the boundaries (e.g., when calling an old API that requires Date).

import java.time.*;
import java.util.Date;
import java.util.Calendar;
import java.util.GregorianCalendar;

public class LegacyMigration {
    public static void main(String[] args) {

        // ============================================
        // java.util.Date  <->  java.time
        // ============================================

        // Legacy Date -> Instant -> LocalDate / LocalDateTime / ZonedDateTime
        Date legacyDate = new Date();

        Instant instant = legacyDate.toInstant();
        LocalDate localDate = instant.atZone(ZoneId.systemDefault()).toLocalDate();
        LocalDateTime localDateTime = instant.atZone(ZoneId.systemDefault()).toLocalDateTime();
        ZonedDateTime zonedDateTime = instant.atZone(ZoneId.of("America/New_York"));

        System.out.println("Legacy Date:    " + legacyDate);
        System.out.println("As LocalDate:   " + localDate);
        System.out.println("As LocalDateTime: " + localDateTime);
        System.out.println("As ZonedDateTime: " + zonedDateTime);

        // Modern -> Legacy Date
        Date backToDate = Date.from(
            LocalDate.of(2026, 3, 15)
                .atStartOfDay(ZoneId.systemDefault())
                .toInstant()
        );
        System.out.println("Back to Date: " + backToDate);

        // ============================================
        // java.util.Calendar  <->  java.time
        // ============================================

        // Calendar -> ZonedDateTime (GregorianCalendar has a direct method)
        Calendar calendar = Calendar.getInstance();
        if (calendar instanceof GregorianCalendar) {
            ZonedDateTime fromCal = ((GregorianCalendar) calendar).toZonedDateTime();
            System.out.println("Calendar -> ZonedDateTime: " + fromCal);
        }

        // ZonedDateTime -> Calendar
        ZonedDateTime zdt = ZonedDateTime.of(2026, 3, 15, 10, 30, 0, 0,
            ZoneId.of("America/New_York"));
        GregorianCalendar fromZdt = GregorianCalendar.from(zdt);
        System.out.println("ZonedDateTime -> Calendar: " + fromZdt.getTime());
    }
}

12. Common Mistakes

Even with the improved java.time API, there are pitfalls that catch developers at every experience level. Here are the most common ones and how to avoid them.

Mistake 1: Confusing MM (months) and mm (minutes)

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class MistakeFormatterCase {
    public static void main(String[] args) {
        LocalDateTime dateTime = LocalDateTime.of(2026, 3, 15, 14, 30);

        // WRONG: mm is minutes, not months!
        DateTimeFormatter wrong = DateTimeFormatter.ofPattern("yyyy-mm-dd");
        System.out.println("Wrong: " + dateTime.format(wrong));  // 2026-30-15  (oops!)

        // CORRECT: MM is months
        DateTimeFormatter correct = DateTimeFormatter.ofPattern("yyyy-MM-dd");
        System.out.println("Correct: " + dateTime.format(correct));  // 2026-03-15
    }
}

Mistake 2: Ignoring the Return Value (Immutability Trap)

import java.time.LocalDate;

public class MistakeImmutability {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2026, 3, 15);

        // WRONG: plusDays() returns a NEW object, it does NOT modify 'date'
        date.plusDays(10);  // Return value is thrown away!
        System.out.println("Still original: " + date);  // 2026-03-15

        // CORRECT: capture the return value
        LocalDate future = date.plusDays(10);
        System.out.println("New date: " + future);  // 2026-03-25
    }
}

Mistake 3: Using == Instead of equals() or isEqual()

import java.time.LocalDate;

public class MistakeEquality {
    public static void main(String[] args) {
        LocalDate date1 = LocalDate.parse("2026-03-15");
        LocalDate date2 = LocalDate.parse("2026-03-15");

        // WRONG: == compares object references
        System.out.println("== result: " + (date1 == date2));  // May be false!

        // CORRECT: use equals() or isEqual()
        System.out.println("equals:  " + date1.equals(date2));   // true
        System.out.println("isEqual: " + date1.isEqual(date2));  // true
    }
}

Mistake 4: Not Handling DateTimeParseException

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Optional;

public class MistakeParseException {

    // BAD: lets exception propagate unhandled
    public static LocalDate parseDateUnsafe(String input) {
        return LocalDate.parse(input);  // Throws DateTimeParseException if invalid
    }

    // GOOD: handles the exception gracefully
    public static Optional parseDateSafe(String input, DateTimeFormatter formatter) {
        try {
            return Optional.of(LocalDate.parse(input, formatter));
        } catch (DateTimeParseException e) {
            System.out.println("Invalid date: '" + input + "' - " + e.getMessage());
            return Optional.empty();
        }
    }

    public static void main(String[] args) {
        DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd");

        parseDateSafe("2026-03-15", fmt).ifPresent(d ->
            System.out.println("Parsed: " + d));  // Parsed: 2026-03-15

        parseDateSafe("not-a-date", fmt);   // Invalid date: 'not-a-date' - ...
        parseDateSafe("2026-13-01", fmt);   // Invalid date: '2026-13-01' - ... (no month 13)
        parseDateSafe("2026-02-30", fmt);   // Invalid date: '2026-02-30' - ... (Feb has no 30th)
    }
}

Mistake 5: Using LocalDateTime When You Need ZonedDateTime

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

public class MistakeWrongType {
    public static void main(String[] args) {

        // BAD: Using LocalDateTime for a meeting across time zones
        LocalDateTime meeting = LocalDateTime.of(2026, 6, 15, 10, 0);
        // Is this 10 AM in New York? London? Tokyo? Nobody knows!

        // GOOD: Using ZonedDateTime makes the time zone explicit
        ZonedDateTime properMeeting = ZonedDateTime.of(2026, 6, 15, 10, 0, 0, 0,
            ZoneId.of("America/New_York"));
        // Unambiguous: 10 AM Eastern time

        System.out.println("Ambiguous: " + meeting);
        System.out.println("Clear:     " + properMeeting);
    }
}

Mistake 6: Exposing Mutable Legacy Date Fields

import java.time.LocalDate;
import java.util.Date;

public class MistakeMutableField {

    // BAD: legacy Date is mutable -- callers can modify your internal state
    static class BadEvent {
        private Date eventDate;

        public BadEvent(Date date) { this.eventDate = date; }
        public Date getEventDate() { return eventDate; }  // Exposes mutable reference!
    }

    // GOOD: use LocalDate (immutable) -- no defensive copying needed
    static class GoodEvent {
        private final LocalDate eventDate;

        public GoodEvent(LocalDate date) { this.eventDate = date; }
        public LocalDate getEventDate() { return eventDate; }  // Safe! Immutable.
    }

    public static void main(String[] args) {
        // Demonstrating the problem with mutable Date
        Date mutableDate = new Date();
        BadEvent bad = new BadEvent(mutableDate);
        System.out.println("Before: " + bad.getEventDate());

        bad.getEventDate().setTime(0);  // Caller modifies internal state!
        System.out.println("After:  " + bad.getEventDate());
        // Thu Jan 01 00:00:00 UTC 1970 -- internal state corrupted!

        // No such problem with LocalDate
        GoodEvent good = new GoodEvent(LocalDate.of(2026, 3, 15));
        // good.getEventDate() returns an immutable object -- nothing to corrupt
    }
}

13. Best Practices

These guidelines will help you write correct, maintainable date/time code across any Java project.

# Practice Reason
1 Use java.time for all new code Immutable, thread-safe, and well-designed. Never use java.util.Date or Calendar in new code.
2 Store timestamps as UTC in databases Avoids ambiguity. Convert to local time zones only at the presentation layer.
3 Use ISO 8601 format for APIs yyyy-MM-ddTHH:mm:ssZ is universally understood and sortable. It is the default format for all java.time toString() methods.
4 Use the most specific type LocalDate for dates-only, LocalTime for times-only, ZonedDateTime for user-facing date-times, Instant for machine timestamps.
5 Use IANA zone IDs, not abbreviations "America/New_York" not "EST". Abbreviations are ambiguous and do not handle DST correctly.
6 Always handle DateTimeParseException User input and external data can always be malformed. Never let parse exceptions crash your application.
7 Reuse DateTimeFormatter instances Formatters are immutable and thread-safe. Create them as static final constants and share them.
8 Use Clock for testable code Instead of LocalDate.now(), inject a Clock so you can test with fixed times: LocalDate.now(clock).
9 Prefer isBefore()/isAfter() over compareTo() More readable and less error-prone than checking if compareTo() returns negative/positive.
10 Never use == to compare date objects Always use equals() or isEqual(). The == operator compares references, not values.

Using Clock for Testable Code

Hardcoding LocalDate.now() makes your code impossible to test deterministically. Inject a Clock instead:

import java.time.*;

public class TestableService {

    private final Clock clock;

    // Production constructor uses system clock
    public TestableService() {
        this(Clock.systemDefaultZone());
    }

    // Test constructor accepts any clock
    public TestableService(Clock clock) {
        this.clock = clock;
    }

    public boolean isWeekend() {
        DayOfWeek day = LocalDate.now(clock).getDayOfWeek();
        return day == DayOfWeek.SATURDAY || day == DayOfWeek.SUNDAY;
    }

    public boolean isExpired(LocalDate expiryDate) {
        return LocalDate.now(clock).isAfter(expiryDate);
    }

    public static void main(String[] args) {
        // Production usage
        TestableService prod = new TestableService();
        System.out.println("Is weekend (real): " + prod.isWeekend());

        // Test: freeze time to a known Wednesday
        Clock fixedClock = Clock.fixed(
            LocalDate.of(2026, 3, 11).atStartOfDay(ZoneId.systemDefault()).toInstant(),
            ZoneId.systemDefault()
        );
        TestableService test = new TestableService(fixedClock);
        System.out.println("Is weekend (Wed): " + test.isWeekend());   // false

        // Test: freeze time to a known Saturday
        Clock saturdayClock = Clock.fixed(
            LocalDate.of(2026, 3, 14).atStartOfDay(ZoneId.systemDefault()).toInstant(),
            ZoneId.systemDefault()
        );
        TestableService satTest = new TestableService(saturdayClock);
        System.out.println("Is weekend (Sat): " + satTest.isWeekend());  // true
    }
}

14. Complete Practical Example -- Event Scheduling System

Let us bring everything together with a realistic event scheduling system that demonstrates most of the concepts covered in this tutorial. This system creates events with dates and times, handles time zones, checks for conflicts, formats output, and calculates durations.

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;

public class EventSchedulingSystem {

    // ============================================
    // Event class
    // ============================================
    static class Event {
        private final String name;
        private final ZonedDateTime start;
        private final Duration duration;
        private final String organizer;

        public Event(String name, ZonedDateTime start, Duration duration, String organizer) {
            if (name == null || name.isBlank()) throw new IllegalArgumentException("Name required");
            if (start.isBefore(ZonedDateTime.now())) {
                // Allow past events for demo purposes, but warn
                System.out.println("  [WARN] Event '" + name + "' is in the past.");
            }
            this.name = name;
            this.start = start;
            this.duration = duration;
            this.organizer = organizer;
        }

        public String getName() { return name; }
        public ZonedDateTime getStart() { return start; }
        public ZonedDateTime getEnd() { return start.plus(duration); }
        public Duration getDuration() { return duration; }
        public String getOrganizer() { return organizer; }

        public boolean conflictsWith(Event other) {
            return this.getStart().isBefore(other.getEnd()) &&
                   this.getEnd().isAfter(other.getStart());
        }

        public String formatForZone(ZoneId zone) {
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("EEE, MMM dd yyyy 'at' hh:mm a z");
            ZonedDateTime localStart = start.withZoneSameInstant(zone);
            ZonedDateTime localEnd = getEnd().withZoneSameInstant(zone);
            DateTimeFormatter timeFmt = DateTimeFormatter.ofPattern("hh:mm a z");
            return String.format("%s: %s - %s",
                name, localStart.format(fmt), localEnd.format(timeFmt));
        }

        @Override
        public String toString() {
            return formatForZone(start.getZone());
        }
    }

    // ============================================
    // EventScheduler class
    // ============================================
    static class EventScheduler {
        private final List events = new ArrayList<>();
        private static final DateTimeFormatter DISPLAY_FORMAT =
            DateTimeFormatter.ofPattern("MMM dd, yyyy HH:mm z");

        public boolean addEvent(Event event) {
            for (Event existing : events) {
                if (existing.conflictsWith(event)) {
                    System.out.println("  CONFLICT: '" + event.getName() +
                        "' overlaps with '" + existing.getName() + "'");
                    return false;
                }
            }
            events.add(event);
            System.out.println("  ADDED: " + event.getName());
            return true;
        }

        public List getEventsOnDate(LocalDate date, ZoneId zone) {
            return events.stream()
                .filter(e -> {
                    LocalDate eventDate = e.getStart()
                        .withZoneSameInstant(zone).toLocalDate();
                    return eventDate.equals(date);
                })
                .sorted(Comparator.comparing(Event::getStart))
                .collect(Collectors.toList());
        }

        public List getUpcomingEvents(ZonedDateTime from, int days) {
            ZonedDateTime until = from.plusDays(days);
            return events.stream()
                .filter(e -> !e.getStart().isBefore(from) && e.getStart().isBefore(until))
                .sorted(Comparator.comparing(Event::getStart))
                .collect(Collectors.toList());
        }

        public Duration getTotalScheduledTime() {
            return events.stream()
                .map(Event::getDuration)
                .reduce(Duration.ZERO, Duration::plus);
        }

        public Map> groupByDate(ZoneId zone) {
            return events.stream()
                .collect(Collectors.groupingBy(
                    e -> e.getStart().withZoneSameInstant(zone).toLocalDate(),
                    TreeMap::new,
                    Collectors.toList()
                ));
        }
    }

    // ============================================
    // Main -- demonstration
    // ============================================
    public static void main(String[] args) {
        EventScheduler scheduler = new EventScheduler();
        ZoneId nyZone = ZoneId.of("America/New_York");
        ZoneId londonZone = ZoneId.of("Europe/London");
        ZoneId tokyoZone = ZoneId.of("Asia/Tokyo");

        System.out.println("=== CREATING EVENTS ===");
        System.out.println();

        // Event 1: Team standup (New York time)
        Event standup = new Event("Team Standup",
            ZonedDateTime.of(2026, 6, 15, 9, 0, 0, 0, nyZone),
            Duration.ofMinutes(30), "Alice");
        scheduler.addEvent(standup);

        // Event 2: Architecture review
        Event archReview = new Event("Architecture Review",
            ZonedDateTime.of(2026, 6, 15, 10, 0, 0, 0, nyZone),
            Duration.ofHours(2), "Bob");
        scheduler.addEvent(archReview);

        // Event 3: Try to book overlapping meeting
        Event overlap = new Event("Quick Sync",
            ZonedDateTime.of(2026, 6, 15, 11, 0, 0, 0, nyZone),
            Duration.ofHours(1), "Charlie");
        scheduler.addEvent(overlap);  // CONFLICT with Architecture Review

        // Event 4: Afternoon meeting
        Event deployment = new Event("Deployment Planning",
            ZonedDateTime.of(2026, 6, 15, 14, 0, 0, 0, nyZone),
            Duration.ofMinutes(45), "Alice");
        scheduler.addEvent(deployment);

        // Event 5: Next day event in London time
        Event londonMeeting = new Event("London Client Call",
            ZonedDateTime.of(2026, 6, 16, 10, 0, 0, 0, londonZone),
            Duration.ofHours(1), "Diana");
        scheduler.addEvent(londonMeeting);

        // ============================================
        // Display events for different time zones
        // ============================================
        System.out.println("\n=== MONDAY JUNE 15 SCHEDULE (New York) ===");
        LocalDate monday = LocalDate.of(2026, 6, 15);
        for (Event e : scheduler.getEventsOnDate(monday, nyZone)) {
            System.out.println("  " + e.formatForZone(nyZone));
        }

        System.out.println("\n=== SAME EVENTS VIEWED FROM TOKYO ===");
        for (Event e : scheduler.getEventsOnDate(monday, nyZone)) {
            System.out.println("  " + e.formatForZone(tokyoZone));
        }

        // ============================================
        // Stats
        // ============================================
        System.out.println("\n=== SCHEDULE STATS ===");
        Duration total = scheduler.getTotalScheduledTime();
        System.out.println("Total scheduled: " + total.toHours() + "h " +
            total.toMinutesPart() + "m");

        // Days until an event
        ZonedDateTime now = ZonedDateTime.of(2026, 2, 28, 12, 0, 0, 0, nyZone);
        long daysUntilStandup = ChronoUnit.DAYS.between(now, standup.getStart());
        System.out.println("Days until standup: " + daysUntilStandup);

        // ============================================
        // Period calculation
        // ============================================
        System.out.println("\n=== TIME UNTIL EVENTS ===");
        Period period = Period.between(now.toLocalDate(), standup.getStart().toLocalDate());
        System.out.println("Until standup: " + period.getMonths() + " months, " +
            period.getDays() + " days");

        // ============================================
        // Grouped by date
        // ============================================
        System.out.println("\n=== EVENTS GROUPED BY DATE ===");
        Map> grouped = scheduler.groupByDate(nyZone);
        DateTimeFormatter dateFmt = DateTimeFormatter.ofPattern("EEEE, MMMM dd");
        for (Map.Entry> entry : grouped.entrySet()) {
            System.out.println(entry.getKey().format(dateFmt) + ":");
            for (Event e : entry.getValue()) {
                System.out.println("  - " + e.getName() + " (" +
                    e.getDuration().toMinutes() + " min, by " + e.getOrganizer() + ")");
            }
        }

        // Output:
        // === CREATING EVENTS ===
        //
        //   ADDED: Team Standup
        //   ADDED: Architecture Review
        //   CONFLICT: 'Quick Sync' overlaps with 'Architecture Review'
        //   ADDED: Deployment Planning
        //   ADDED: London Client Call
        //
        // === MONDAY JUNE 15 SCHEDULE (New York) ===
        //   Team Standup: Mon, Jun 15 2026 at 09:00 AM EDT - 09:30 AM EDT
        //   Architecture Review: Mon, Jun 15 2026 at 10:00 AM EDT - 12:00 PM EDT
        //   Deployment Planning: Mon, Jun 15 2026 at 02:00 PM EDT - 02:45 PM EDT
        //
        // === SAME EVENTS VIEWED FROM TOKYO ===
        //   Team Standup: Mon, Jun 15 2026 at 10:00 PM JST - 10:30 PM JST
        //   Architecture Review: Mon, Jun 15 2026 at 11:00 PM JST - 01:00 AM JST
        //   Deployment Planning: Tue, Jun 16 2026 at 03:00 AM JST - 03:45 AM JST
        //
        // === SCHEDULE STATS ===
        // Total scheduled: 4h 15m
        // Days until standup: 107
        //
        // === TIME UNTIL EVENTS ===
        // Until standup: 3 months, 15 days
        //
        // === EVENTS GROUPED BY DATE ===
        // Monday, June 15:
        //   - Team Standup (30 min, by Alice)
        //   - Architecture Review (120 min, by Bob)
        //   - Deployment Planning (45 min, by Alice)
        // Tuesday, June 16:
        //   - London Client Call (60 min, by Diana)
    }
}

Concepts Demonstrated in This Example

# Concept Where It Appears
1 ZonedDateTime creation All events are created with explicit time zones
2 Time zone conversion formatForZone() uses withZoneSameInstant()
3 Duration Event duration, total scheduled time
4 Period Human-readable time until events
5 ChronoUnit Days between now and a future event
6 DateTimeFormatter Custom display formats for dates and times
7 Conflict detection conflictsWith() using isBefore()/isAfter()
8 LocalDate extraction Grouping events by date
9 Immutability All date/time objects are safely shared
10 Stream operations on dates Filtering, sorting, and grouping events

Quick Reference

Task Code
Current date LocalDate.now()
Current time LocalTime.now()
Current date-time LocalDateTime.now()
Current instant (UTC) Instant.now()
Specific date LocalDate.of(2026, 3, 15)
Parse date string LocalDate.parse("2026-03-15")
Format a date date.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"))
Add days date.plusDays(10)
Subtract months date.minusMonths(3)
Compare dates date1.isBefore(date2)
Days between dates ChronoUnit.DAYS.between(date1, date2)
Period between dates Period.between(date1, date2)
Duration between times Duration.between(time1, time2)
Convert zone zdt.withZoneSameInstant(ZoneId.of("Asia/Tokyo"))
First day of month date.with(TemporalAdjusters.firstDayOfMonth())
Last day of month date.with(TemporalAdjusters.lastDayOfMonth())
Next Monday date.with(TemporalAdjusters.next(DayOfWeek.MONDAY))
Is leap year date.isLeapYear()
Legacy Date to Instant legacyDate.toInstant()
Instant to legacy Date Date.from(instant)



Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

Your email address will not be published. Required fields are marked *