Date & Time

Java · Lesson 26 of 32 6 min read Updated

Due dates for library books, train departure times, a customer's age, an online class at 7:30 pm in India that is 2:00 pm in London: almost every real application handles dates and times. Getting them right is harder than it looks, because of leap years, months of different lengths and time zones.

Java's answer is the java.time package, added in Java 8. Its classes are clear, immutable and safe to share between threads. In this lesson you will create dates and times, do arithmetic with them, format and parse them, work with time zones, and see why the older Date and Calendar classes should be avoided.

The main classes

ClassHoldsExample
LocalDatea date without time2026-11-14
LocalTimea time without date06:10
LocalDateTimedate and time, no time zone2026-11-13T06:10
ZonedDateTimedate, time and time zone2026-12-04T19:30+05:30[Asia/Kolkata]
Instantan exact moment on the global timeline (UTC)2026-10-30T04:00:00Z
Periodan amount in years, months, days19 years, 2 months, 15 days
Durationan amount in hours, minutes, seconds1 hour 30 minutes

"Local" means without a time zone: a birthday or an exam date is the same calendar date everywhere. Use ZonedDateTime or Instant when the exact moment matters across the world.

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

public class Basics {
    public static void main(String[] args) {
        LocalDate childrensDay = LocalDate.of(2026, Month.NOVEMBER, 14);
        LocalDate exam = LocalDate.parse("2027-03-15");
        LocalTime trainTime = LocalTime.of(6, 10);
        LocalDateTime departure = LocalDateTime.of(childrensDay.minusDays(1), trainTime);

        System.out.println(childrensDay);
        System.out.println(childrensDay.getDayOfWeek());
        System.out.println(childrensDay.getDayOfYear());
        System.out.println(exam.getMonth() + " " + exam.getYear());
        System.out.println(departure);
        System.out.println(childrensDay.isLeapYear());
        System.out.println(LocalDate.of(2028, 2, 1).lengthOfMonth());
        System.out.println(childrensDay.getDayOfWeek() == DayOfWeek.SATURDAY);
    }
}
Output
2026-11-14
SATURDAY
318
MARCH 2027
2026-11-13T06:10
false
29
true

Create values with of(...) or parse(...), which reads the standard ISO format yyyy-MM-dd. Months are numbered 1 to 12 (or use the Month enum), and DayOfWeek is an enum too. To get the current date or time, call LocalDate.now() or LocalDateTime.now(); the result changes every run, so the examples here use fixed dates.

Date arithmetic

Every java.time object is immutable. Methods such as plusDays, minusMonths and with return a new object and leave the original unchanged, just like String methods.

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

public class Maths {
    public static void main(String[] args) {
        LocalDate issued = LocalDate.of(2026, 10, 30);
        LocalDate due = issued.plusDays(14);
        LocalDate returned = LocalDate.of(2026, 11, 19);

        System.out.println("Due: " + due);
        System.out.println("Issued unchanged: " + issued);

        long lateDays = ChronoUnit.DAYS.between(due, returned);
        System.out.println("Late by " + lateDays + " days, fine Rs " + lateDays * 2);
        System.out.println("Returned after due? " + returned.isAfter(due));

        LocalDate birthday = LocalDate.of(2007, 8, 15);
        Period age = Period.between(birthday, issued);
        System.out.println("Age: " + age.getYears() + " years, " + age.getMonths()
                + " months, " + age.getDays() + " days");

        System.out.println(LocalDate.of(2026, 1, 31).plusMonths(1));
        System.out.println(issued.with(TemporalAdjusters.lastDayOfMonth()));
        System.out.println(issued.with(TemporalAdjusters.next(java.time.DayOfWeek.MONDAY)));
    }
}
Output
Due: 2026-11-13
Issued unchanged: 2026-10-30
Late by 6 days, fine Rs 12
Returned after due? true
Age: 19 years, 2 months, 15 days
2026-02-28
2026-10-31
2026-11-02
  • ChronoUnit.DAYS.between(a, b) counts whole days between two dates; isBefore, isAfter and isEqual compare them.
  • Period.between gives the difference as years, months and days, which is exactly how ages are stated.
  • Adding a month to 31 January gives 28 February: java.time picks the last valid day instead of overflowing into March.
  • TemporalAdjusters answers questions like "last day of this month" or "next Monday".

Formatting and parsing

toString() always gives the ISO format (2026-08-15). For other formats, create a DateTimeFormatter with a pattern: dd day, MM month number, MMM short month name, MMMM full month name, uuuu or yyyy year, EEEE day name, HH 24-hour, hh 12-hour, mm minutes and a for AM/PM. Pass a Locale when names are involved, so the output language does not depend on the computer's settings.

Java
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.util.Locale;

public class Format {
    public static void main(String[] args) {
        DateTimeFormatter indian = DateTimeFormatter.ofPattern("dd-MM-uuuu")
                                                    .withResolverStyle(ResolverStyle.STRICT);
        DateTimeFormatter friendly = DateTimeFormatter.ofPattern("EEEE, d MMMM yyyy", Locale.ENGLISH);
        DateTimeFormatter ticket = DateTimeFormatter.ofPattern("dd MMM yyyy, hh:mm a", Locale.ENGLISH);

        LocalDate date = LocalDate.parse("15-08-2026", indian);
        System.out.println(date);
        System.out.println(date.format(friendly));
        System.out.println(LocalDateTime.of(2026, 11, 7, 18, 45).format(ticket));
        System.out.println(date.format(indian));

        try {
            LocalDate.parse("31-02-2026", indian);
        } catch (DateTimeParseException e) {
            System.out.println("Invalid: " + e.getMessage());
        }

        DateTimeFormatter lenient = DateTimeFormatter.ofPattern("dd-MM-yyyy");
        System.out.println(LocalDate.parse("31-02-2026", lenient));
    }
}
Output
2026-08-15
Saturday, 15 August 2026
07 Nov 2026, 06:45 PM
15-08-2026
Invalid: Text '31-02-2026' could not be parsed: Invalid date 'FEBRUARY 31'
2026-02-28

Look closely at the last line. A formatter made with just ofPattern uses a "smart" mode that quietly changes 31 February into 28 February. When you parse dates typed by users, that hides mistakes. A strict formatter rejects the impossible date with DateTimeParseException. In strict mode, use uuuu for the year (yyyy means year-of-era, which strict mode cannot resolve without an era).

Time zones, Instant and Duration

A meeting set for 7:30 pm in Pune happens at a different clock time in London. ZonedDateTime pairs a date-time with a zone ID such as Asia/Kolkata, and converts between zones correctly, including daylight saving time in countries that use it. India does not, and is always UTC+05:30.

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

public class Zones {
    public static void main(String[] args) {
        ZoneId india = ZoneId.of("Asia/Kolkata");
        ZoneId london = ZoneId.of("Europe/London");

        ZonedDateTime callInPune = ZonedDateTime.of(
                LocalDateTime.of(2026, 12, 4, 19, 30), india);
        ZonedDateTime callInLondon = callInPune.withZoneSameInstant(london);

        System.out.println(callInPune);
        System.out.println(callInLondon);
        System.out.println(callInLondon.toLocalTime());

        Instant start = Instant.parse("2026-10-30T04:00:00Z");
        Instant end = start.plusSeconds(5400);
        Duration taken = Duration.between(start, end);
        System.out.println(taken + " = " + taken.toMinutes() + " minutes");
        System.out.println(Duration.ofMinutes(135).toHoursPart() + "h "
                + Duration.ofMinutes(135).toMinutesPart() + "m");
    }
}
Output
2026-12-04T19:30+05:30[Asia/Kolkata]
2026-12-04T14:00Z[Europe/London]
14:00
PT1H30M = 90 minutes
2h 15m

Instant is a point on the universal timeline, written in UTC with a Z. Servers usually store timestamps as Instant (or UTC) and convert to the user's zone only for display. Duration measures time in seconds and nanoseconds; its toString uses the ISO format PT1H30M (a period of time of 1 hour 30 minutes).

Common mistakes

  • Ignoring the return value. date.plusDays(7); on its own does nothing useful; write date = date.plusDays(7);.
  • Using LocalDateTime for global events. It has no zone; use ZonedDateTime or Instant for moments that must line up across countries.
  • Lenient parsing of user input. Use ResolverStyle.STRICT with uuuu so 31 February is rejected.
  • Confusing mm and MM. MM is the month; mm is minutes. HH is 24-hour time; hh needs a for AM/PM.
  • Using Period for hours or Duration for months. Period counts calendar units; Duration counts exact time.

Key points

  • Use java.time: LocalDate, LocalTime, LocalDateTime, ZonedDateTime and Instant.
  • Create values with of, parse or now; all objects are immutable.
  • plusDays, minusMonths, with(TemporalAdjusters...), isBefore and ChronoUnit.DAYS.between handle date arithmetic.
  • Period measures years, months and days; Duration measures hours, minutes and seconds.
  • DateTimeFormatter.ofPattern formats and parses; use a Locale for names and strict mode for user input.
  • Avoid Date, Calendar and SimpleDateFormat in new code; convert them at the edges.

Exercise

A gym in Jaipur sells 3-month memberships. Write Membership.java that takes a start date typed as "20-10-2026" (parse it strictly), calculates the end date (start plus 3 months minus 1 day), prints both in the format 20 Oct 2026, prints how many days the membership lasts, and prints which day of the week it ends on. Also check "31-11-2026" and print a friendly error.

Show solution
Java
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoUnit;
import java.util.Locale;

public class Membership {
    static final DateTimeFormatter INPUT =
            DateTimeFormatter.ofPattern("dd-MM-uuuu").withResolverStyle(ResolverStyle.STRICT);
    static final DateTimeFormatter OUTPUT =
            DateTimeFormatter.ofPattern("dd MMM uuuu", Locale.ENGLISH);

    static void show(String text) {
        try {
            LocalDate start = LocalDate.parse(text, INPUT);
            LocalDate end = start.plusMonths(3).minusDays(1);
            long days = ChronoUnit.DAYS.between(start, end) + 1;
            System.out.println("Start: " + start.format(OUTPUT));
            System.out.println("End:   " + end.format(OUTPUT) + " (" + end.getDayOfWeek() + ")");
            System.out.println("Length: " + days + " days");
        } catch (DateTimeParseException e) {
            System.out.println("'" + text + "' is not a valid date");
        }
    }

    public static void main(String[] args) {
        show("20-10-2026");
        show("31-11-2026");
    }
}
Output
Start: 20 Oct 2026
End:   19 Jan 2027 (TUESDAY)
Length: 92 days
'31-11-2026' is not a valid date

DAYS.between does not count the last day, so 1 is added to count both the first and last days of the membership. November has only 30 days, so the strict formatter rejects the second date.

Quick Quiz

1. Which class should store a student's date of birth?

2. What does date.plusDays(5) do to date?

3. In Calendar, what does month number 10 mean?

4. Which type measures "1 hour 30 minutes"?

Learn Java with a mentor

Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call