LocalDate
java.time.LocalDate represents a calendar date (year, month, and day) with no time of day and no time zone. It is the right type for birthdays, due dates, holidays, invoice dates, course start dates, and any value people think of as "a day" rather than "a moment." LocalDate is immutable, thread-safe, and follows the ISO calendar used in most of the world.
Creating a LocalDate
import java.time.*;
LocalDate today = LocalDate.now(); // current date in the default zone
LocalDate inZone = LocalDate.now(ZoneId.of("Asia/Kolkata")); // "today" in a specific zone
LocalDate d = LocalDate.of(2026, 11, 12); // year, month (1-12), day
LocalDate d2 = LocalDate.of(2026, Month.NOVEMBER, 12); // month as an enum
LocalDate parsed = LocalDate.parse("2026-11-12"); // ISO-8601 text
LocalDate dayOfYear = LocalDate.ofYearDay(2026, 256); // 2026-09-13
LocalDate epoch = LocalDate.ofEpochDay(0); // 1970-01-01
Months are numbered 1 to 12 (unlike the legacy Calendar, where January was 0). Invalid dates such as February 30 throw DateTimeException when created.
"Today" depends on the time zone: when it is 11 pm on the 12th in New York, it is already the 13th in Chennai. Server code that needs "today" for a particular user should pass that user's ZoneId to now.
Reading Fields
LocalDate d = LocalDate.of(2026, 11, 12);
d.getYear(); // 2026
d.getMonth(); // NOVEMBER (a Month enum)
d.getMonthValue(); // 11
d.getDayOfMonth(); // 12
d.getDayOfWeek(); // THURSDAY (a DayOfWeek enum)
d.getDayOfYear(); // 316
d.lengthOfMonth(); // 30
d.lengthOfYear(); // 365
d.isLeapYear(); // false
Month and DayOfWeek are enums, so they can be compared with ==, used in switch, and stored in EnumSet/EnumMap.
Adding and Subtracting
d.plusDays(10); // 2026-11-22
d.plusWeeks(2); // 2026-11-26
d.plusMonths(3); // 2027-02-12
d.minusYears(1); // 2025-11-12
d.plus(Period.ofMonths(6));
Results are always valid dates. When the target month is shorter, the day is clamped to that month's last day:
LocalDate.of(2026, 3, 31).minusMonths(1); // 2026-02-28
LocalDate.of(2024, 2, 29).plusYears(1); // 2025-02-28
This is usually what people expect, but it means adding a month and then subtracting a month does not always return the original date. Repeatedly adding one month to January 31 drifts to the 28th and stays there; compute each date from the original start instead (start.plusMonths(n)).
Replacing Fields and Using Adjusters
with... methods return a copy with one field changed:
d.withDayOfMonth(1); // 2026-11-01
d.withMonth(2); // 2026-02-12
d.withYear(2030); // 2030-11-12
TemporalAdjusters answer common calendar questions:
import java.time.temporal.TemporalAdjusters;
d.with(TemporalAdjusters.firstDayOfMonth()); // 2026-11-01
d.with(TemporalAdjusters.lastDayOfMonth()); // 2026-11-30
d.with(TemporalAdjusters.next(DayOfWeek.MONDAY)); // 2026-11-16
d.with(TemporalAdjusters.nextOrSame(DayOfWeek.THURSDAY)); // 2026-11-12
d.with(TemporalAdjusters.firstInMonth(DayOfWeek.FRIDAY)); // 2026-11-06
d.with(TemporalAdjusters.lastInMonth(DayOfWeek.FRIDAY)); // 2026-11-27
d.with(TemporalAdjusters.firstDayOfNextMonth()); // 2026-12-01
These replace the error-prone manual arithmetic that billing, payroll, and scheduling code otherwise needs.
Comparing Dates
LocalDate due = LocalDate.of(2026, 12, 1);
d.isBefore(due); // true
d.isAfter(due); // false
d.isEqual(due); // false
d.equals(due); // false
d.compareTo(due) < 0; // true
LocalDate implements Comparable, so lists of dates can be sorted directly and dates can be used as TreeMap keys for date-ordered data.
Measuring the Gap Between Dates
import java.time.temporal.ChronoUnit;
LocalDate newYear = LocalDate.of(2027, 1, 1);
d.until(newYear); // P1M20D: 1 month and 20 days
d.until(newYear, ChronoUnit.DAYS); // 50
ChronoUnit.DAYS.between(d, newYear); // 50
ChronoUnit.WEEKS.between(d, newYear); // 7
Period.between(LocalDate.of(1998, 2, 14), d).getYears(); // 28: age in full years
The end date is exclusive: from the 12th to the 13th is one day. The result is negative if the end is before the start.
Ranges of Dates
datesUntil (Java 9+) produces a stream of consecutive dates, end exclusive:
d.datesUntil(d.plusDays(5))
.map(x -> x.getDayOfWeek())
.toList(); // [THURSDAY, FRIDAY, SATURDAY, SUNDAY, MONDAY]
d.datesUntil(d.plusMonths(1), Period.ofWeeks(1)).toList(); // every 7 days
This makes it easy to build calendars, generate reports per day, or find dates matching a rule.
Related Types
| Class | Represents | Example |
|---|---|---|
YearMonth | A month of a year, without a day | Card expiry 2028-07, monthly reports |
MonthDay | A day of a month, without a year | Annual events: birthdays, festivals |
Year | A year | Year.of(2028).isLeap() → true |
YearMonth.of(2026, 2).lengthOfMonth(); // 28
MonthDay.of(2, 14).atYear(2027); // 2027-02-14
Using these types documents intent: a credit-card expiry is genuinely a YearMonth, not a date with an arbitrary day.
Converting LocalDate
d.atTime(9, 30); // LocalDateTime 2026-11-12T09:30
d.atStartOfDay(ZoneId.of("Asia/Kolkata")); // ZonedDateTime at midnight in that zone
d.toEpochDay(); // 20769 days since 1970-01-01
d.format(DateTimeFormatter.ofPattern("dd/MM/yyyy")); // "12/11/2026"
Use atStartOfDay(zone) rather than atTime(0, 0) when converting to a moment: in a few time zones, a daylight-saving change means midnight does not exist on some dates, and atStartOfDay handles that correctly.
Practical Example: Business-Day Due Dates and Payroll
An invoicing system gives customers five business days to pay, skipping weekends and public holidays. Payroll runs on the last business day of each month.
import java.time.*;
import java.time.temporal.TemporalAdjusters;
import java.util.*;
public class LD {
static final Set<LocalDate> HOLIDAYS = Set.of(LocalDate.of(2026, 11, 26), LocalDate.of(2026, 12, 25));
static boolean isBusinessDay(LocalDate d) {
DayOfWeek dow = d.getDayOfWeek();
return dow != DayOfWeek.SATURDAY && dow != DayOfWeek.SUNDAY && !HOLIDAYS.contains(d);
}
static LocalDate addBusinessDays(LocalDate start, int days) {
LocalDate d = start;
int added = 0;
while (added < days) {
d = d.plusDays(1);
if (isBusinessDay(d)) added++;
}
return d;
}
public static void main(String[] args) {
LocalDate invoiceDate = LocalDate.of(2026, 11, 20);
LocalDate due = addBusinessDays(invoiceDate, 5);
LocalDate monthEnd = invoiceDate.with(TemporalAdjusters.lastDayOfMonth());
System.out.println("Invoice " + invoiceDate + " (" + invoiceDate.getDayOfWeek() + ") due " + due + " (" + due.getDayOfWeek() + ")");
System.out.println("Month-end close: " + monthEnd + ", business day? " + isBusinessDay(monthEnd));
List<LocalDate> payroll = new ArrayList<>();
for (int m = 1; m <= 12; m++) {
LocalDate last = LocalDate.of(2026, m, 1).with(TemporalAdjusters.lastDayOfMonth());
while (!isBusinessDay(last)) last = last.minusDays(1);
payroll.add(last);
}
System.out.println(payroll.subList(9, 12));
}
}
Output:
Invoice 2026-11-20 (FRIDAY) due 2026-11-30 (MONDAY)
Month-end close: 2026-11-30, business day? true
[2026-10-30, 2026-11-30, 2026-12-31]
isBusinessDay combines the DayOfWeek enum with a Set of holiday dates; because LocalDate implements equals and hashCode by value, HOLIDAYS.contains(d) works with any LocalDate object for the same day. addBusinessDays steps forward one day at a time and counts only business days: from Friday 20 November it counts Monday 23rd, Tuesday 24th, Wednesday 25th, skips the holiday on the 26th, counts Friday 27th, skips the weekend, and lands on Monday 30th. The payroll loop uses lastDayOfMonth and then steps backward past weekends: October 31, 2026 is a Saturday, so payroll moves to Friday the 30th. Because every operation returns a new LocalDate, the loops simply reassign d and last at each step.