☕ Java
LocalDate
LocalDate is the java.time class (introduced in Java 8 as part of the redesigned date-time API replacing the deprecated java.util.Date/Calendar) representing a date without a time component or a time zone — just a year, month, and day on the ISO-8601 calendar. This entry covers why LocalDate deliberately carries no time-of-day or zone information, how it is created and parsed, how it is manipulated via its immutable, fluent with*/plus*/minus* methods, how two LocalDate instances are compared and the period between them measured, and the common pitfalls of treating it as if it secretly carried a time or zone after all.
What LocalDate Is — and Deliberately Is Not
LocalDate models exactly three pieces of information: a year, a month, and a day-of-month, on the ISO-8601 calendar system, and nothing else — no hour, minute, second, or time zone is stored or implied anywhere in the object. This is a deliberate narrowing compared to the old java.util.Date, which despite its name actually stored a full instant in time (milliseconds since the epoch, implicitly UTC) and therefore always carried a time-of-day whether or not that made sense for the use case — a Date meant to represent "someone's birthday" still secretly had hours, minutes, and seconds attached to it, all defaulting to whatever the JVM's default time zone happened to produce at construction, which is precisely the kind of hidden state that caused entire classes of subtle bugs (date comparisons silently failing because of mismatched time components, birthdays shifting by a day depending on the server's time zone, and so on).
LocalDate fixes this by being genuinely incapable of representing a time-of-day at all — there is no method that returns an hour or a millisecond, because the class simply does not have that information to give. This makes LocalDate the correct type specifically for date-only concepts that exist independently of any clock or location: birthdays, anniversaries, due dates, holidays, a hotel reservation's check-in date — concepts where "what time of day" and "in what time zone" are simply not meaningful questions to ask in the first place. Like every class in java.time, LocalDate is also immutable: every method that appears to "modify" a LocalDate (plusDays, withYear, and so on) actually returns a brand-new LocalDate instance, leaving the original completely untouched, which makes LocalDate inherently thread-safe and eliminates an entire category of bugs where one part of a program unexpectedly mutates a date object another part of the program is still relying on.
Java
// ── The old way — java.util.Date secretly carries a time AND a zone ──
Date oldBirthday = new Date(2024 - 1900, 5, 15); // bizarre constructor — year offset!
System.out.println(oldBirthday);
// Mon Jun 15 00:00:00 <JVM-DEFAULT-ZONE> 2024 — a time-of-day and zone you never asked for
// ── LocalDate — genuinely just a date, nothing more ─────────────────
LocalDate birthday = LocalDate.of(2024, 6, 15);
System.out.println(birthday); // 2024-06-15
// birthday.getHour(); // ← does not exist — LocalDate has no such method to call
// ── Immutability — every "mutating" method returns a NEW instance ───
LocalDate original = LocalDate.of(2024, 1, 1);
LocalDate later = original.plusMonths(1);
System.out.println(original); // 2024-01-01 — completely unchanged
System.out.println(later); // 2024-02-01 — a separate, new LocalDate objectCreating and Parsing LocalDate
LocalDate offers several factory methods rather than public constructors — like the rest of java.time, its constructors are private, and instances are obtained exclusively through static factory methods, which both communicates intent more clearly at the call site and allows the implementation to validate input and potentially reuse cached instances. LocalDate.of(year, month, day) is the most direct way to build a specific date, accepting either an int month (1-12) or a Month enum constant for extra type-safety and readability. LocalDate.now() captures the current date as read from the system clock and the JVM's default time zone — a detail worth noting even though LocalDate itself has no zone, since "what day is it right now" is itself a zone-dependent question (it can simultaneously be June 15th in Tokyo and June 14th in Los Angeles), so now() implicitly resolves that ambiguity using whichever zone the system clock is set to, and a zone-aware overload, LocalDate.now(ZoneId), exists for when the default zone is not the right one to use.
LocalDate.parse(text) parses a String in the standard ISO-8601 date format (yyyy-MM-dd) by default, throwing DateTimeParseException if the text does not match that exact pattern. For any other textual format, LocalDate.parse(text, formatter) accepts a DateTimeFormatter describing the expected pattern, and the same DateTimeFormatter is used in reverse, via localDate.format(formatter), to produce a String in a custom layout — DateTimeFormatter.ofPattern("dd/MM/yyyy") for one common example, alongside several built-in formatter constants like DateTimeFormatter.ISO_LOCAL_DATE for standard patterns.
Java
// ── Factory methods — no public constructor exists ──────────────────
LocalDate date1 = LocalDate.of(2024, 6, 15); // int month
LocalDate date2 = LocalDate.of(2024, Month.JUNE, 15); // Month enum — clearer at call sites
// ── now() — depends on the system clock AND the default time zone ───
LocalDate today = LocalDate.now();
LocalDate todayInTokyo = LocalDate.now(ZoneId.of("Asia/Tokyo"));
// these two can disagree by a day around midnight, since "today" is zone-relative
// ── Parsing — default ISO-8601 format, or a custom DateTimeFormatter ─
LocalDate parsed = LocalDate.parse("2024-06-15"); // default: yyyy-MM-dd
// LocalDate.parse("15/06/2024"); // throws DateTimeParseException — wrong format
DateTimeFormatter customFormat = DateTimeFormatter.ofPattern("dd/MM/yyyy");
LocalDate customParsed = LocalDate.parse("15/06/2024", customFormat);
// ── Formatting — the SAME formatter, used in reverse ─────────────────
String formatted = date1.format(customFormat); // "15/06/2024"
String iso = date1.format(DateTimeFormatter.ISO_LOCAL_DATE); // "2024-06-15"Manipulating, Comparing, and Measuring Between Dates
LocalDate's fluent manipulation methods fall into two families: plus*/minus* methods (plusDays, plusWeeks, plusMonths, plusYears, and their minus equivalents) that shift a date by a relative amount, automatically rolling over month and year boundaries and accounting for variable month lengths and leap years correctly, and with* methods (withYear, withMonth, withDayOfMonth) that produce a new date with one specific field replaced while leaving the others alone, which is distinct from "adding" — withDayOfMonth(1) does not add one day, it replaces the day-of-month field outright, commonly used to jump to the first day of the current month. Both families return a new LocalDate, consistent with immutability.
Comparing two LocalDate instances is done with isBefore(other), isAfter(other), isEqual(other), or compareTo(other) (also fine to use plain equals() for equality, since LocalDate properly overrides it) — all of these compare purely on the calendar date, with no time or zone component to complicate the comparison, which is exactly the simplicity that made LocalDate worth introducing in the first place. For measuring the distance between two dates, Period.between(start, end) produces a date-based amount expressed in years, months, and days (e.g., "2 years, 3 months, 10 days"), which is the conceptually correct unit for date-to-date spans (as opposed to Duration, which measures time-based amounts in seconds/nanoseconds and is meant for LocalTime/Instant, not LocalDate). ChronoUnit.DAYS.between(start, end) is the more direct way to get a flat count of days between two dates when a single number, rather than a broken-down years/months/days Period, is what's actually needed.
Java
LocalDate date = LocalDate.of(2024, 1, 31);
// ── plus*/minus* — relative shifts that correctly handle month/year rollover
LocalDate nextMonth = date.plusMonths(1); // 2024-02-29 — Jan 31 + 1 month, leap year aware!
LocalDate nextYear = date.plusYears(1); // 2025-01-31
// ── with* — REPLACE one field outright, this is NOT addition ─────────
LocalDate firstOfMonth = date.withDayOfMonth(1); // 2024-01-01 — jump to start of month
LocalDate differentYear = date.withYear(2030); // 2030-01-31 — only year field changes
// ── Comparisons — purely calendar-based, no time/zone to complicate it
LocalDate a = LocalDate.of(2024, 6, 15);
LocalDate b = LocalDate.of(2024, 12, 25);
System.out.println(a.isBefore(b)); // true
System.out.println(a.equals(LocalDate.of(2024, 6, 15))); // true
// ── Period — date-based span, broken into years/months/days ──────────
Period gap = Period.between(a, b);
System.out.println(gap); // P6M10D
System.out.println(gap.getMonths()); // 6
System.out.println(gap.getDays()); // 10
// ── ChronoUnit — a flat count in a single unit, when that's all you need
long daysBetween = ChronoUnit.DAYS.between(a, b); // 193Related Topics in Date Time API
LocalTime
LocalTime is the java.time class representing a time-of-day without any date or time zone attached — just an hour, minute, second, and nanosecond on a 24-hour clock. This entry covers what LocalTime intentionally omits and why, how to create and parse LocalTime values with varying precision, how arithmetic on LocalTime wraps around midnight instead of rolling into a different day, and how LocalTime is meant to be combined with LocalDate (via LocalDateTime) rather than used as a date substitute.
LocalDateTime
LocalDateTime is the java.time class combining a LocalDate and a LocalTime into a single date-and-time-of-day value that still, deliberately, carries no time zone or UTC-offset information. This entry covers why LocalDateTime is best understood as a composite of the two simpler types rather than a primitive type in its own right, how it is created from and decomposed back into its LocalDate/LocalTime parts, why the absence of a zone makes it unsuitable for representing one unambiguous, real-world instant, and how it relates to and converts into the zone-aware types (ZonedDateTime, Instant) when an actual moment in time is what's needed.
ZonedDateTime
ZonedDateTime is the java.time class representing a LocalDateTime combined with a ZoneId, fully resolved against the time zone's rules to also carry a specific UTC offset — making it the type capable of representing one genuinely unambiguous real-world instant while still preserving human-readable, zone-local wall-clock fields. This entry covers the relationship between ZoneId and the resolved offset, how daylight saving transitions create gaps and overlaps that ZonedDateTime must resolve, how zone-aware arithmetic differs from plain LocalDateTime arithmetic across those transitions, and when to prefer ZonedDateTime over the simpler offset-only OffsetDateTime or the zone-independent Instant.
Period
Period is the java.time class representing a date-based amount of time expressed in years, months, and days — a human-calendar quantity like '2 years, 3 months, and 10 days' rather than a fixed number of elapsed seconds. This entry covers how Period differs fundamentally from Duration in what unit of measurement it operates in, how Period.between() computes a breakdown rather than a single total, why adding a Period to a date is calendar-aware rather than fixed-length, and the common pitfall of expecting a Period's individual fields to sum to a meaningful total without normalization.