BeginnerJava · Lesson 8 of 8

Dates & Times with java.time

LocalDate, Period, ChronoUnit, formatting and parsing, time zones with ZonedDateTime, and Duration.

java.time (Java 8+) is the modern date and time API, and its objects are immutable. LocalDate is a calendar date with no time or zone, ideal for birthdays, deadlines and exam days. Months are 1 to 12 here, unlike older APIs. Arithmetic methods such as plusWeeks and plusMonths return a new date, and month arithmetic clamps sensibly: 31 January plus one month is 28 February.

ChronoUnit.DAYS.between(a, b) counts days; Period.between gives years, months and days, which is how you compute an age. DateTimeFormatter.ofPattern("dd/MM/yyyy") parses and formats dates the way people write them, and parsing is strict: 2026-02-31 throws DateTimeParseException instead of quietly becoming March. TemporalAdjusters find dates such as the first Monday of a month.

For a moment in time, use ZonedDateTime (a moment in a place, such as Africa/Dar_es_Salaam) or Instant (a moment in UTC, ideal for storing and logging). withZoneSameInstant shows the same moment in another zone. Duration measures elapsed time in hours, minutes and seconds.

Dates.javaJava
import java.time.DayOfWeek;
import java.time.Duration;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Period;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAdjusters;
import java.util.Locale;

public class Dates {
    public static void main(String[] args) {
        // A LocalDate is a calendar date with no time and no time zone. Months are 1-12.
        LocalDate registered = LocalDate.of(2026, 3, 14);
        LocalDate exams = LocalDate.parse("2026-11-02");              // ISO format
        System.out.println(ChronoUnit.DAYS.between(registered, exams) + " days until exams");   // 233

        // Objects are immutable: plusWeeks returns a new date
        LocalDate deadline = registered.plusWeeks(6);
        var pretty = DateTimeFormatter.ofPattern("EEEE d MMMM yyyy", Locale.UK);
        System.out.println("Fee deadline: " + deadline.format(pretty));          // Saturday 25 April 2026

        // Month arithmetic clamps to the end of the month
        System.out.println(LocalDate.of(2026, 1, 31).plusMonths(1));             // 2026-02-28

        // Age with Period: years, months and days between two dates
        LocalDate born = LocalDate.parse("14/03/2008", DateTimeFormatter.ofPattern("dd/MM/yyyy"));
        Period age = Period.between(born, LocalDate.of(2026, 10, 5));
        System.out.printf("Age: %d years, %d months, %d days%n", age.getYears(), age.getMonths(), age.getDays());

        // Strict parsing rejects dates that don't exist
        try {
            LocalDate.parse("2026-02-31");
        } catch (DateTimeParseException e) {
            System.out.println("Invalid: " + e.getParsedString());
        }

        // Adjusters find dates for you: the first Monday in July
        LocalDate termStart = LocalDate.of(2026, 7, 1).with(TemporalAdjusters.firstInMonth(DayOfWeek.MONDAY));
        System.out.println("Term starts " + termStart + " (" + termStart.getDayOfWeek() + ")");

        // Time zones: ZonedDateTime for a moment in a place, Instant for a moment in UTC
        ZoneId eat = ZoneId.of("Africa/Dar_es_Salaam");
        ZonedDateTime paid = ZonedDateTime.of(LocalDateTime.of(registered, LocalTime.of(9, 30)), eat);
        Instant instant = paid.toInstant();
        System.out.println(paid + " = " + instant);                             // ...+03:00[...] = ...06:30:00Z
        System.out.println("In London: " + paid.withZoneSameInstant(ZoneId.of("Europe/London")).toLocalTime());

        // Durations measure elapsed time
        Duration exam = Duration.between(LocalTime.of(8, 0), LocalTime.of(10, 30));
        System.out.println("Paper length: " + exam.toHours() + "h " + exam.toMinutesPart() + "m");
    }
}

Key points

  • LocalDate for dates, ZonedDateTime/Instant for moments; all are immutable.
  • ChronoUnit.between and Period.between measure gaps; plusMonths clamps to the month's end.
  • DateTimeFormatter parses strictly and formats for people.

Exercise

Print a fee plan: split TSh 250,000 into 3 monthly instalments starting 31 January 2026, as whole shillings (the last one takes the remainder). Due dates that fall on a Saturday or Sunday move to the next Monday. Print each date like "Mon 2 Feb 2026" with the amount, then the total.

Show solution

Try the exercise yourself first — then compare your approach with this one.

Each due date is computed from the original date with plusMonths(i). Adding one month at a time would drift: 31 Jan → 28 Feb → 28 Mar. nextSchoolDay uses an arrow switch on the DayOfWeek enum to push weekend dates to Monday. Money stays in a long of whole shillings, and the last instalment absorbs the remainder so the total is exact; %,d adds the thousands separators.

FeePlan.javaJava
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;

public class FeePlan {
    // Due dates that fall on a weekend move to the following Monday.
    static LocalDate nextSchoolDay(LocalDate date) {
        return switch (date.getDayOfWeek()) {
            case SATURDAY -> date.plusDays(2);
            case SUNDAY -> date.plusDays(1);
            default -> date;
        };
    }

    // Amounts are whole shillings in a long; the last instalment takes the remainder.
    static void printPlan(long total, int count, LocalDate firstDue) {
        var fmt = DateTimeFormatter.ofPattern("EEE d MMM yyyy", Locale.UK);
        long base = total / count;
        long sum = 0;
        for (int i = 0; i < count; i++) {
            long amount = i == count - 1 ? total - base * (count - 1) : base;
            // Always add months to the ORIGINAL date, so 31 Jan -> 28 Feb -> 31 Mar (not 28 Mar)
            LocalDate due = nextSchoolDay(firstDue.plusMonths(i));
            System.out.printf("%-16s TSh %,9d%n", due.format(fmt), amount);
            sum += amount;
        }
        System.out.printf("%-16s TSh %,9d%n", "Total", sum);
    }

    public static void main(String[] args) {
        printPlan(250_000, 3, LocalDate.of(2026, 1, 31));
    }
}

Check your understanding

  1. What is LocalDate.of(2026, 1, 31).plusMonths(1)?

  2. Which class should you use to store when a payment happened, for logs and databases?

  3. How do you compute someone's age in whole years?

  4. What does LocalDate.parse("2026-02-31") do?

Ask AI