Dates & Times (LocalDate, LocalDateTime)

Reviewed & published by Brayan K

Dates look simple and turn out to be a minefield — leap years, month lengths, time zones. Java's modern java.time package handles all of it with clean, immutable classes. This lesson covers the three you'll use daily: LocalDate, LocalTime, and LocalDateTime.

Part of the free Java course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

Before You Start

You'll need comfort with method calls and imports — everything here lives in java.time. Knowing that objects can be immutable will make the key idea click instantly.

What You'll Learn

The Big Idea — A Stamped Calendar Page

💡 Analogy: A LocalDate is like a single page torn from a calendar — it says "17 June 2026" and nothing else. You can't erase and rewrite it; if you want "one week later" you grab a fresh page. That's immutability: every plusDays hands you a new page and leaves the original exactly as it was. This is what makes java.time safe to pass around without anyone secretly changing your dates.

There are three everyday building blocks: LocalDate (date only), LocalTime (time only), and LocalDateTime (both). "Local" means no time zone — perfect for birthdays, opening hours, and appointments where the zone is understood. We'll stick to these three.

1️⃣ Creating Dates and Times

Use the static of(...) factory methods to build a specific value, or now() for the current moment. Months are 1-based here (June is 6) — a welcome fix from the old Date class where January was 0. You can also use the readable Month.JUNE enum.

LocalDate.of(2026, 6, 17)      // 17 June 2026
LocalDate.now()                // today
LocalTime.of(14, 30)           // 14:30
LocalDateTime.of(2026, 6, 17, 14, 30)

Once you have an object, getters pull out the pieces: getYear(), getMonth(), getDayOfMonth(), getDayOfWeek(), and more.

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

public class Main {
    public static void main(String[] args) {
        // Build a specific date (year, month, day)
        LocalDate d = LocalDate.of(2026, 6, 17);
        System.out.println("Date: " + d);                    // 2026-06-17

        // Months can be named for clarity
        LocalDate launch = LocalDate.of(2026, Month.DECEMBER, 25);
        System.out.println("Launch: " + launch);

        // A time of day
        LocalTime t = LocalTime.of(14, 30, 0);
        System.out.println("Time: " + t);                    // 14:30

        // Date + time together
        LocalDateTime dt = LocalDateTime.of(2026, 6, 17, 14, 30);
        System.out.println("DateTime: " + dt);               // 2026-06-17T14:30

        // Pull pieces out
        System.out.println("Year : " + d.getYear());
        System.out.println("Month: " + d.getMonth() + " (" + d.getMonthValue() + ")");
        System.out.println("Day  : " + d.getDayOfMonth());
        System.out.println("Weekday: " + d.getDayOfWeek());
    }
}

2️⃣ Immutability — the One Rule to Remember

Every java.time object is immutable. A method like plusDays(10) does not change the date — it returns a brand-new date and leaves the original untouched. This is wonderful for safety but trips up everyone at least once.

import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 6, 17);

        // java.time objects are IMMUTABLE. Every "change" returns a NEW object;
        // the original is never touched.
        LocalDate nextWeek = today.plusWeeks(1);
        LocalDate lastMonth = today.minusMonths(1);
        LocalDate inTenDays = today.plusDays(10);

        System.out.println("today    : " + today);     // unchanged!
        System.out.println("nextWeek : " + nextWeek);
        System.out.println("lastMonth: " + lastMonth);
        System.out.println("inTenDays: " + inTenDays);

        // Methods chain because each returns a new LocalDate
        LocalDate result = today.plusYears(1).minusDays(2).withDayOfMonth(1);
        System.out.println("chained  : " + result);
    }
}

3️⃣ Comparing and Measuring Gaps

Compare two dates with isBefore, isAfter, and isEqual. To measure the gap, you have two tools:

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

public class Main {
    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2026, 1, 1);
        LocalDate end   = LocalDate.of(2026, 6, 17);

        // Compare dates
        System.out.println("start before end? " + start.isBefore(end));
        System.out.println("end after start?  " + end.isAfter(start));
        System.out.println("same date?        " + start.isEqual(end));

        // Period = human-readable difference (years / months / days)
        Period p = Period.between(start, end);
        System.out.printf("Period: %d months, %d days%n",
                          p.getMonths(), p.getDays());

        // ChronoUnit = total count in one unit
        long totalDays = ChronoUnit.DAYS.between(start, end);
        System.out.println("Total days between: " + totalDays);
    }
}

4️⃣ Formatting and Parsing

DateTimeFormatter converts in both directions: format turns a date object into a custom String, and parse turns a String back into a date object. You define the shape with a pattern of letters.

LetterMeansExample
yyyy4-digit year2026
MM / MMMMMonth number / name06 / June
ddDay of month17
EEEEWeekday nameWednesday
HH:mm24-hour time14:05
'text'Literal text'at'
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class Main {
    public static void main(String[] args) {
        LocalDateTime dt = LocalDateTime.of(2026, 6, 17, 14, 5);

        // FORMAT: object -> nicely formatted String
        DateTimeFormatter human =
            DateTimeFormatter.ofPattern("EEEE, d MMMM yyyy 'at' HH:mm");
        System.out.println(dt.format(human));

        DateTimeFormatter shortFmt =
            DateTimeFormatter.ofPattern("dd/MM/yyyy");
        System.out.println(dt.format(shortFmt));

        // PARSE: String -> object (the reverse)
        LocalDateTime parsed =
            LocalDateTime.parse("2026-12-25T09:00:00");
        System.out.println("Parsed: " + parsed);
        System.out.println("Parsed weekday: " + parsed.getDayOfWeek());
    }
}

🧩 Reorder Challenge

These lines compute and print how many days remain until a holiday. They've been shuffled.

A) long days = ChronoUnit.DAYS.between(today, holiday);
B) LocalDate today = LocalDate.of(2026, 6, 17);
C) System.out.println(days + " days until the holiday");
D) LocalDate holiday = LocalDate.of(2026, 12, 25);

Correct order: B, D, A, C

LocalDate today = LocalDate.of(2026, 6, 17);
LocalDate holiday = LocalDate.of(2026, 12, 25);
long days = ChronoUnit.DAYS.between(today, holiday);
System.out.println(days + " days until the holiday");
// Output:  191 days until the holiday

Create both dates first, measure the gap with ChronoUnit.DAYS.between (earlier date first so the result is positive), then print.

🧠 Quick Recall

Predict the output before opening each answer.

LocalDate d = LocalDate.of(2026, 6, 17);
d.plusDays(5);
System.out.println(d);

Answer: 2026-06-17 — unchanged. plusDays(5) returns a new date that is thrown away because the result isn't captured. LocalDate is immutable. To move the date you'd need d = d.plusDays(5);.

LocalDate a = LocalDate.of(2026, 1, 31);
LocalDate b = a.plusMonths(1);
System.out.println(b);

Answer: 2026-02-28. There's no 31 February, so plusMonths clamps to the last valid day of the target month. (2026 isn't a leap year, so February ends on the 28th.) java.time handles month-length edge cases for you.

LocalDate start = LocalDate.of(2026, 6, 1);
LocalDate end   = LocalDate.of(2026, 6, 17);
System.out.println(ChronoUnit.DAYS.between(start, end));

Answer: 16. between counts whole days from start to end — June 17 minus June 1 is 16 days. It's exclusive of the start and inclusive of the end's calendar position, so the gap, not the number of dates, is 16.

🎯 Your Turn #1 — 18th birthday

Given a birth date, compute and print the 18th-birthday date.

import java.time.LocalDate;

public class Main {
    public static void main(String[] args) {
        // YOUR TURN: given a person's birth date, compute the date of their
        // 18th birthday and print it. (Hint: plusYears.)
        // Expected output:  18th birthday: 2026-04-09
        LocalDate birth = LocalDate.of(2008, 4, 9);

        LocalDate eighteenth = birth;   // fix this line

        System.out.println("18th birthday: " + eighteenth);
    }
}

🎯 Your Turn #2 — Countdown

Count the days between today and a deadline using ChronoUnit.

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

public class Main {
    public static void main(String[] args) {
        // YOUR TURN: count how many days until a deadline.
        // Use ChronoUnit.DAYS.between(...).
        // Expected output:  Days until deadline: 14
        LocalDate today    = LocalDate.of(2026, 6, 17);
        LocalDate deadline = LocalDate.of(2026, 7, 1);

        long daysLeft = 0;   // compute this

        System.out.println("Days until deadline: " + daysLeft);
    }
}

🧩 Mini-Challenge — Age calculator

Write an age method that returns whole years between a birth date and today using Period.

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

public class Main {
    // GOAL: write age(birth, today) that returns a person's age in whole years.
    // Then write a formatted line. Use Period.between(...).getYears().
    //
    // Expected output:
    //   Born 1990-02-15, today 2026-06-17 -> age 36
    //   Born 2010-12-01, today 2026-06-17 -> age 15

    static int age(LocalDate birth, LocalDate today) {
        // return the whole years between birth and today
        return 0;   // your code here
    }

    public static void main(String[] args) {
        LocalDate today = LocalDate.of(2026, 6, 17);
        LocalDate a = LocalDate.of(1990, 2, 15);
        LocalDate b = LocalDate.of(2010, 12, 1);
        System.out.println("Born " + a + ", today " + today + " -> age " + age(a, today));
        System.out.println("Born " + b + ", today " + today + " -> age " + age(b, today));
    }
}

Common Errors

Pro Tips

📋 Quick Reference

TaskSyntaxNotes
Specific dateLocalDate.of(2026, 6, 17)Month is 1-based
Today / nowLocalDate.now()Current date
Add timed = d.plusDays(7)Returns a new object
Comparea.isBefore(b)Also isAfter, isEqual
Days betweenChronoUnit.DAYS.between(a, b)Total count, one unit
Human gapPeriod.between(a, b)Years/months/days
Formatd.format(formatter)DateTimeFormatter.ofPattern

🎉 Lesson Complete!

You can now build dates and times with java.time, you understand why every object is immutable (and the bug that causes), and you can add, compare, measure, format, and parse with confidence. You're using the same modern API every professional Java codebase relies on.

Practice quiz

In LocalDate.of(2026, 6, 17), how is the month value interpreted?

  • 0-based, so 6 is July
  • It is the day, not the month
  • 1-based, so 6 is June
  • It must be a Month enum

Answer: 1-based, so 6 is June. java.time months are 1-based: 6 means June. This fixes the old Date class where January was 0.

After LocalDate d = LocalDate.of(2026,6,17); d.plusDays(5); what does d hold?

  • 2026-06-17
  • 2026-06-22
  • It throws an exception
  • null

Answer: 2026-06-17. java.time objects are immutable; plusDays returns a NEW date that is discarded here, so d is unchanged. Verified on Java 21.

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

  • 2026-02-31
  • 2026-03-03
  • It throws DateTimeException
  • 2026-02-28

Answer: 2026-02-28. There is no 31 February, so plusMonths clamps to the last valid day; 2026 is not a leap year, giving 2026-02-28. Verified on Java 21.

Which class should you use for a date with no time, like a birthday?

  • LocalDateTime
  • LocalDate
  • LocalTime
  • Instant

Answer: LocalDate. LocalDate holds a date with no time-of-day, perfect for birthdays and deadlines.

What does ChronoUnit.DAYS.between(LocalDate.of(2026,6,1), LocalDate.of(2026,6,17)) return?

  • 16
  • 17
  • 15
  • 1

Answer: 16. between counts whole days from start to end: June 17 minus June 1 is 16. Verified on Java 21.

Which pair measures a human breakdown of years/months/days versus a total count in one unit?

  • Duration vs Period
  • Instant vs LocalDate
  • Period vs ChronoUnit.DAYS.between
  • ChronoUnit vs LocalTime

Answer: Period vs ChronoUnit.DAYS.between. Period.between gives a years/months/days breakdown; ChronoUnit.DAYS.between gives a single total count of days.

In a DateTimeFormatter pattern, what is the difference between MM and mm?

  • MM is minutes, mm is month
  • MM is month, mm is minutes
  • Both mean month
  • Both mean minutes

Answer: MM is month, mm is minutes. Pattern letters are case-sensitive: uppercase MM is the month, lowercase mm is minutes — a classic source of bugs.

Why prefer java.time over the old Date and Calendar classes?

  • java.time is mutable and faster
  • Date and Calendar are immutable and thread-safe
  • Date supports time zones and java.time does not
  • java.time classes are immutable, thread-safe, and use 1-based months

Answer: java.time classes are immutable, thread-safe, and use 1-based months. The modern java.time API is immutable, thread-safe, clearly named, and uses natural 1-based months, unlike the error-prone legacy classes.

What is the correct way to actually move a date forward by one day?

  • date.plusDays(1);
  • date = date.plusDays(1);
  • date.addDays(1);
  • date.setDay(date.getDay()+1);

Answer: date = date.plusDays(1);. Because dates are immutable you must capture the returned value: date = date.plusDays(1).

Period measures date-based units; what does Duration measure?

  • Years, months, and days
  • Only whole days
  • Hours, minutes, seconds, and nanos
  • Time zones

Answer: Hours, minutes, seconds, and nanos. Duration measures time-based units (hours/minutes/seconds/nanos) and pairs with LocalTime/Instant, while Period covers years/months/days.

Continue this course

Frequently asked questions

Why use java.time instead of the old Date and Calendar classes?

The old java.util.Date and Calendar are notoriously error-prone: they're mutable (so they can be changed behind your back), Date months are 0-based (January is 0), and the API is confusing. The modern java.time package (added in Java 8) fixes all of this — its classes are immutable, thread-safe, clearly named, and use natural values (June is month 6). Always reach for java.time in new code.

What's the difference between LocalDate, LocalTime, and LocalDateTime?

LocalDate is a date with no time (2026-06-17) — use it for birthdays or deadlines. LocalTime is a time with no date (14:30) — use it for opening hours. LocalDateTime combines both (2026-06-17T14:30) — use it for timestamps within a known zone. 'Local' means no time zone is attached; when you need zone awareness use ZonedDateTime, and for a universal machine timestamp use Instant.

Why didn't my date change after calling plusDays?

Because java.time objects are immutable. Methods like plusDays, minusMonths, and withYear never modify the object — they return a brand-new object with the change applied. You must capture the result: write date = date.plusDays(1), not just date.plusDays(1). Ignoring the return value is the single most common java.time mistake.

What's the difference between Period and Duration?

Period measures time in date-based units — years, months, and days — and is paired with LocalDate. Duration measures time in time-based units — hours, minutes, seconds, nanos — and is paired with LocalTime/Instant. So 'how many months until launch' is a Period, while 'how many seconds did the task take' is a Duration. For a total count in a single unit (e.g. total days), use ChronoUnit.DAYS.between(...).

How do I turn a date into a custom string and back?

Use DateTimeFormatter. To format, build a formatter with a pattern and call date.format(formatter): DateTimeFormatter.ofPattern("dd/MM/yyyy"). To parse a string back, call the type's parse method, optionally with a formatter: LocalDate.parse("17/06/2026", formatter). Pattern letters are case-sensitive — MM is month, mm is minutes — which trips people up constantly.

Related lessons