java.time vs. legacy Date/Calendar
The old java.util.Date/Calendar API has two infamous, real-bug-generating properties: months are
0-indexed (Calendar.JANUARY == 0), and Date objects are mutable -- two references to what you
thought was "the same date" can be silently changed out from under each other. java.time (Java 8+) fixes
both: months are a proper Month enum (January really is 1), and every type is immutable -- every
"modifying" method returns a new instance.
import java.util.*;
Calendar cal = Calendar.getInstance();
cal.set(2024, Calendar.JANUARY, 15);
System.out.println("Calendar.JANUARY == " + Calendar.JANUARY + " (0-indexed months -- a classic off-by-one bug source)");
Date mutable = new Date();
Date sameRef = mutable;
mutable.setTime(0);
System.out.println("Date is mutable -- \"sameRef\" changed too, since it's the SAME object: " + sameRef.getTime());
import java.time.*;
LocalDate date = LocalDate.of(2024, Month.JANUARY, 15); // Month.JANUARY is 1, and it's a real enum, not a raw int
System.out.println("LocalDate: " + date);
LocalDate nextMonth = date.plusMonths(1);
System.out.println("original date is UNCHANGED (immutable): " + date);
System.out.println("plusMonths returns a brand new instance: " + nextMonth);
Duration measures machine-precision time spans (for Instants); Period measures calendar-aware spans (years/months/days) -- deliberately different types, because "1 month" isn't a fixed number of seconds:
Instant now = Instant.now();
Instant later = now.plusSeconds(3600);
System.out.println("Duration between instants: " + Duration.between(now, later));
Period period = Period.between(LocalDate.of(2024, 1, 1), LocalDate.of(2024, 3, 15));
System.out.println("Period (calendar-aware): " + period.getMonths() + " months, " + period.getDays() + " days");
LocalDate leapDayPlusYear = LocalDate.of(2024, 2, 29).plusYears(1);
System.out.println("Feb 29 2024 + 1 year (2025 isn't a leap year): " + leapDayPlusYear);