Java Dates and Time — The java.time API (Complete Guide)
Java 8 introduced the java.time package — a modern, immutable, thread-safe date/time API that replaces the broken java.util.Date and Calendar.
Why Not java.util.Date?
| Problem | java.util.Date |
java.time |
|---|---|---|
| Mutable | ✅ Bug-prone | ❌ Immutable |
| Thread-safe | ❌ | ✅ |
| Time zones | Confusing | Clean ZoneId API |
| Month indexing | 0-based (Jan=0) | 1-based (Jan=1) |
| Year handling | Year – 1900 | Normal year |
Always use java.time for new code.
LocalDate — Date Without Time
import java.time.LocalDate;
LocalDate today = LocalDate.now(); // 2026-04-20
LocalDate specific = LocalDate.of(2026, 1, 15); // 2026-01-15
LocalDate parsed = LocalDate.parse("2026-12-25");
today.getYear() // 2026
today.getMonth() // APRIL
today.getMonthValue() // 4
today.getDayOfMonth() // 20
today.getDayOfWeek() // MONDAY
today.getDayOfYear() // 110
today.isLeapYear() // false
Manipulating Dates
LocalDate tomorrow = today.plusDays(1);
LocalDate lastMonth = today.minusMonths(1);
LocalDate nextYear = today.plusYears(1);
today.withMonth(12) // 2026-12-20
today.withDayOfMonth(1) // 2026-04-01
All methods return a new LocalDate (immutable).
LocalTime — Time Without Date
import java.time.LocalTime;
LocalTime now = LocalTime.now(); // 14:30:45.123
LocalTime lunch = LocalTime.of(12, 30); // 12:30
LocalTime precise = LocalTime.of(14, 30, 45); // 14:30:45
now.getHour() // 14
now.getMinute() // 30
now.getSecond() // 45
LocalTime later = now.plusHours(2).plusMinutes(15);
LocalDateTime — Date + Time
import java.time.LocalDateTime;
LocalDateTime now = LocalDateTime.now();
LocalDateTime meeting = LocalDateTime.of(2026, 4, 20, 14, 30);
// Combine date and time
LocalDate date = LocalDate.of(2026, 4, 20);
LocalTime time = LocalTime.of(14, 30);
LocalDateTime combined = LocalDateTime.of(date, time);
ZonedDateTime — With Time Zone
import java.time.ZonedDateTime;
import java.time.ZoneId;
ZonedDateTime nowUtc = ZonedDateTime.now(ZoneId.of("UTC"));
ZonedDateTime nowNY = ZonedDateTime.now(ZoneId.of("America/New_York"));
ZonedDateTime nowIST = ZonedDateTime.now(ZoneId.of("Asia/Kolkata"));
// Convert between zones
ZonedDateTime tokyoTime = nowNY.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
List Available Zones
ZoneId.getAvailableZoneIds().stream()
.filter(z -> z.startsWith("America"))
.sorted()
.forEach(System.out::println);
Instant — Machine Timestamp
Represents a point on the timeline (UTC), typically for timestamps and logging:
import java.time.Instant;
Instant now = Instant.now(); // 2026-04-20T09:30:45.123Z
long epoch = now.toEpochMilli(); // milliseconds since 1970-01-01
Instant from = Instant.ofEpochMilli(epoch);
Duration — Time-Based Amount
import java.time.Duration;
Duration d = Duration.ofHours(2).plusMinutes(30);
d.toMinutes() // 150
Duration between = Duration.between(
LocalTime.of(9, 0), LocalTime.of(17, 30));
between.toHours() // 8
Period — Date-Based Amount
import java.time.Period;
Period p = Period.of(1, 2, 3); // 1 year, 2 months, 3 days
Period between = Period.between(
LocalDate.of(2024, 1, 1),
LocalDate.of(2026, 4, 20));
between.getYears() // 2
between.getMonths() // 3
between.getDays() // 19
DateTimeFormatter — Parsing and Formatting
Built-in Formatters
import java.time.format.DateTimeFormatter;
LocalDate date = LocalDate.now();
date.format(DateTimeFormatter.ISO_LOCAL_DATE) // "2026-04-20"
Custom Patterns
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd MMM yyyy");
String formatted = date.format(fmt); // "20 Apr 2026"
// Parse from string
LocalDate parsed = LocalDate.parse("20 Apr 2026", fmt);
DateTime Patterns
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm:ss");
LocalDateTime now = LocalDateTime.now();
System.out.println(now.format(dtf)); // "20/04/2026 14:30:45"
| Pattern | Meaning | Example |
|---|---|---|
yyyy |
Year | 2026 |
MM |
Month (number) | 04 |
MMM |
Month (short) | Apr |
MMMM |
Month (full) | April |
dd |
Day | 20 |
HH |
Hour (24h) | 14 |
hh |
Hour (12h) | 02 |
mm |
Minute | 30 |
ss |
Second | 45 |
a |
AM/PM | PM |
EEEE |
Day name | Monday |
Comparing Dates
LocalDate a = LocalDate.of(2026, 1, 1);
LocalDate b = LocalDate.of(2026, 12, 31);
a.isBefore(b) // true
a.isAfter(b) // false
a.isEqual(a) // true
a.compareTo(b) // negative
Days Between Dates
import java.time.temporal.ChronoUnit;
long days = ChronoUnit.DAYS.between(
LocalDate.of(2026, 1, 1),
LocalDate.of(2026, 4, 20));
// 109
Practical Examples
Age Calculator
public static int calculateAge(LocalDate birthDate) {
return Period.between(birthDate, LocalDate.now()).getYears();
}
calculateAge(LocalDate.of(1990, 6, 15)) // 35
Next Business Day
public static LocalDate nextBusinessDay(LocalDate date) {
LocalDate next = date.plusDays(1);
while (next.getDayOfWeek() == DayOfWeek.SATURDAY ||
next.getDayOfWeek() == DayOfWeek.SUNDAY) {
next = next.plusDays(1);
}
return next;
}
Common Mistakes
| Mistake | Problem | Fix |
|---|---|---|
Using java.util.Date |
Mutable, confusing API | Use java.time |
| Ignoring time zones | Wrong times across regions | Use ZonedDateTime |
LocalDateTime for timestamps |
No time zone info | Use Instant |
| Not storing dates as UTC | Inconsistent data | Store UTC, display local |
Key Takeaways
- Use
LocalDatefor dates,LocalTimefor times,LocalDateTimefor both. - Use
ZonedDateTimewhen time zones matter. - Use
Instantfor machine timestamps. - Use
Durationfor time amounts,Periodfor date amounts. - Use
DateTimeFormatterfor custom parsing and formatting. - All
java.timeclasses are immutable and thread-safe.
What’s Next?
Next: Wrapper Classes — autoboxing, unboxing, Integer.parseInt(), and when to use wrappers vs primitives.