Date & Time
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
| Class | Holds | Example |
|---|---|---|
LocalDate | a date without time | 2026-11-14 |
LocalTime | a time without date | 06:10 |
LocalDateTime | date and time, no time zone | 2026-11-13T06:10 |
ZonedDateTime | date, time and time zone | 2026-12-04T19:30+05:30[Asia/Kolkata] |
Instant | an exact moment on the global timeline (UTC) | 2026-10-30T04:00:00Z |
Period | an amount in years, months, days | 19 years, 2 months, 15 days |
Duration | an amount in hours, minutes, seconds | 1 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.
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);
}
}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.
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)));
}
}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,isAfterandisEqualcompare them.Period.betweengives the difference as years, months and days, which is exactly how ages are stated.- Adding a month to 31 January gives 28 February:
java.timepicks the last valid day instead of overflowing into March. TemporalAdjustersanswers 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.
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));
}
}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.
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");
}
}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; writedate = date.plusDays(7);. - Using
LocalDateTimefor global events. It has no zone; useZonedDateTimeorInstantfor moments that must line up across countries. - Lenient parsing of user input. Use
ResolverStyle.STRICTwithuuuuso 31 February is rejected. - Confusing
mmandMM.MMis the month;mmis minutes.HHis 24-hour time;hhneedsafor AM/PM. - Using
Periodfor hours orDurationfor months.Periodcounts calendar units;Durationcounts exact time.
Key points
- Use
java.time:LocalDate,LocalTime,LocalDateTime,ZonedDateTimeandInstant. - Create values with
of,parseornow; all objects are immutable. plusDays,minusMonths,with(TemporalAdjusters...),isBeforeandChronoUnit.DAYS.betweenhandle date arithmetic.Periodmeasures years, months and days;Durationmeasures hours, minutes and seconds.DateTimeFormatter.ofPatternformats and parses; use aLocalefor names and strict mode for user input.- Avoid
Date,CalendarandSimpleDateFormatin 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
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");
}
}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?
A birth date is a calendar date with no time or zone, so LocalDate fits.
2. What does date.plusDays(5) do to date?
java.time objects are immutable; the method returns a new object.
3. In Calendar, what does month number 10 mean?
Legacy Calendar months start at 0, one of the reasons to prefer java.time.
4. Which type measures "1 hour 30 minutes"?
Duration measures time-based amounts; Period measures years, months and days.
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
