Дата и время ·
‹ Предыдущий Следующий ›
⏱ 5 минут чтения Обновлено: 2026-10-18

Класс LocalDate в Java: методы и примеры

Строка date.plusDays(1); компилируется и выполняется без ошибок, но date после неё остаётся прежней. LocalDate неизменяем: plusDays() возвращает новый объект, и если его не присвоить (date = date.plusDays(1);), результат просто теряется.

LocalDate в Java (java.time.LocalDate) — неизменяемый потокобезопасный класс, который хранит дату без времени и часового пояса: год, месяц и день месяца, например 2024-03-15. Он появился в Java 8 в пакете java.time и заменяет java.util.Date и Calendar там, где нужна только календарная дата: день рождения, дата платежа, срок действия документа.

Как создать LocalDate: of(), now(), parse()

Объект LocalDate создаётся только статическими фабричными методами — конструктора у класса нет. LocalDate.of(год, месяц, день) задаёт конкретную дату, LocalDate.now() возвращает текущую дату, а LocalDate.parse("2024-03-15") разбирает строку в формате ISO-8601 (yyyy-MM-dd).

import java.time.LocalDate;
import java.time.Month;

public class CreateLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1);
        LocalDate newYear2002 = LocalDate.of(2002, Month.JANUARY, 1);
        LocalDate fromString = LocalDate.parse("2024-03-15");
        LocalDate day32 = LocalDate.ofYearDay(2001, 32);   // 32-й день года
        LocalDate epoch = LocalDate.ofEpochDay(0);         // 0 дней от 1970-01-01
        LocalDate today = LocalDate.now();

        System.out.println(newYear2001);
        System.out.println(newYear2002);
        System.out.println(fromString);
        System.out.println(day32);
        System.out.println(epoch);
        System.out.println(today);
    }
}
2001-01-01
2002-01-01
2024-03-15
2001-02-01
1970-01-01
2026-10-18   (текущая дата на момент запуска)

LocalDate.now() берёт дату в часовом поясе JVM по умолчанию. Если важно, какой сейчас день в конкретном городе, передайте зону: LocalDate.now(ZoneId.of("Asia/Tokyo")) — в Токио новый день наступает на 6 часов раньше, чем в Москве.

Метод LocalDate.of проверяет дату сразу при создании. Несуществующая дата не «перетекает» на следующий месяц, как в старом Calendar, а вызывает исключение:

LocalDate.of(2023, 2, 29);   // DateTimeException: Invalid date 'February 29' as '2023' is not a leap year
LocalDate.of(2023, 4, 31);   // DateTimeException: Invalid date 'APRIL 31'
LocalDate.of(2023, 13, 1);   // DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 13
LocalDate.parse("2023-1-5"); // DateTimeParseException: Text '2023-1-5' could not be parsed at index 5

Месяцы считаются с 1

В LocalDate январь — это 1, декабрь — 12. В java.util.Calendar январь был 0, и LocalDate.of(2024, 0, 1) по старой привычке выбросит DateTimeException. Чтобы не путаться, используйте перечисление Month: LocalDate.of(2024, Month.JANUARY, 1). Почему старые классы заменили — в уроке Дата и время в Java 8.

Получение года, месяца и дня

Год, месяц и день из LocalDate получают геттерами: getYear(), getMonthValue() (число 1–12), getMonth() (перечисление Month), getDayOfMonth(), getDayOfYear() и getDayOfWeek() (перечисление DayOfWeek). Длину месяца и года и признак високосного года дают lengthOfMonth(), lengthOfYear() и isLeapYear().

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.Month;

public class LocalDateGetExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2024, 3, 15);

        int year = date.getYear();
        int month = date.getMonthValue();
        Month monthAsEnum = date.getMonth();          // как перечисление
        int dayYear = date.getDayOfYear();
        int dayMonth = date.getDayOfMonth();
        DayOfWeek dayWeekEnum = date.getDayOfWeek();  // как перечисление

        System.out.println("Год: " + year);
        System.out.println("Месяц: " + month);
        System.out.println("Название месяца: " + monthAsEnum);
        System.out.println("День в году: " + dayYear);
        System.out.println("День месяца: " + dayMonth);
        System.out.println("День недели: " + dayWeekEnum);
        System.out.println("Дней в месяце: " + date.lengthOfMonth());
        System.out.println("Дней в году: " + date.lengthOfYear());
        System.out.println("Високосный: " + date.isLeapYear());
    }
}
Год: 2024
Месяц: 3
Название месяца: MARCH
День в году: 75
День месяца: 15
День недели: FRIDAY
Дней в месяце: 31
Дней в году: 366
Високосный: true

Тот же результат даёт универсальный метод get(TemporalField) с константами ChronoField. Он удобен, когда поле выбирается динамически:

import java.time.LocalDate;
import java.time.temporal.ChronoField;

public class LocalDateChronoFieldExample {
    public static void main(String[] args) {
        LocalDate date = LocalDate.of(2024, 3, 15);

        System.out.println("Год: " + date.get(ChronoField.YEAR));
        System.out.println("Месяц: " + date.get(ChronoField.MONTH_OF_YEAR));
        System.out.println("День в году: " + date.get(ChronoField.DAY_OF_YEAR));
        System.out.println("День месяца: " + date.get(ChronoField.DAY_OF_MONTH));
        System.out.println("День недели: " + date.get(ChronoField.DAY_OF_WEEK)); // понедельник = 1
        System.out.println("Дней от 1970-01-01: " + date.getLong(ChronoField.EPOCH_DAY));
    }
}
Год: 2024
Месяц: 3
День в году: 75
День месяца: 15
День недели: 5
Дней от 1970-01-01: 19797

Обратите внимание на последнюю строку: EPOCH_DAY нужно читать через getLong(). Вызов date.get(ChronoField.EPOCH_DAY) выбросит UnsupportedTemporalTypeException: Invalid field 'EpochDay' for get() method, use getLong() instead, потому что значение поля может не поместиться в int. Поля времени (ChronoField.HOUR_OF_DAY и т.п.) у LocalDate нет совсем — запрос тоже закончится UnsupportedTemporalTypeException.

Сравнение дат

Две даты LocalDate сравнивают методами isBefore(), isAfter() и isEqual(), которые возвращают boolean. Для сортировки используется compareTo(): класс реализует Comparable. Оператор == для дат не подходит — он сравнивает ссылки, а не значения.

import java.time.LocalDate;
import java.time.Month;

public class CompareLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1);
        LocalDate newYear2002 = LocalDate.of(2002, Month.JANUARY, 1);

        System.out.println("isAfter: " + newYear2001.isAfter(newYear2002));
        System.out.println("isBefore: " + newYear2001.isBefore(newYear2002));
        System.out.println("isEqual: " + newYear2001.isEqual(newYear2002));
        System.out.println("equals: " + newYear2001.equals(LocalDate.parse("2001-01-01")));
        System.out.println("compareTo: " + newYear2001.compareTo(newYear2002));
        System.out.println("compareTo: " + newYear2001.compareTo(LocalDate.of(2005, 6, 1)));
    }
}
isAfter: false
isBefore: true
isEqual: false
equals: true
compareTo: -1
compareTo: -4

Последняя строка показывает неочевидный момент: compareTo() у LocalDate возвращает разницу (сначала по годам, затем по месяцам и дням), а не строго -1, 0 или 1. Поэтому результат сравнивайте с нулём (< 0, > 0), а не с -1. Для LocalDate методы equals() и isEqual() дают одинаковый результат; разница появляется только при сравнении дат из разных календарных систем (например, JapaneseDate) — там isEqual() сравнивает саму точку на временной оси.

Изменение даты методами with()

Методы with() возвращают копию даты, в которой заменено одно поле: withYear(), withMonth(), withDayOfMonth(), withDayOfYear() или универсальный with(ChronoField, значение). Исходный объект не меняется. Для «умных» корректировок — последний день месяца, следующий понедельник — служит with(TemporalAdjuster).

import java.time.DayOfWeek;
import java.time.LocalDate;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAdjusters;

public class WithLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1); // понедельник

        LocalDate newYear2003 = newYear2001.with(ChronoField.YEAR, 2003);
        LocalDate newYear2004 = newYear2001.withYear(2004);
        LocalDate december2001 = newYear2001.withMonth(12);
        LocalDate february2001 = newYear2001.withDayOfYear(32);
        LocalDate xmas2001 = newYear2001.withMonth(12).withDayOfMonth(25);

        LocalDate lastDay = newYear2001.with(TemporalAdjusters.lastDayOfMonth());
        LocalDate nextMonday = newYear2001.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
        LocalDate firstOfNext = newYear2001.with(TemporalAdjusters.firstDayOfNextMonth());

        LocalDate feb = LocalDate.of(2001, 1, 31).withMonth(2);

        System.out.println("newYear2003: " + newYear2003);
        System.out.println("newYear2004: " + newYear2004);
        System.out.println("december2001: " + december2001);
        System.out.println("february2001: " + february2001);
        System.out.println("xmas2001: " + xmas2001);
        System.out.println("lastDay: " + lastDay);
        System.out.println("nextMonday: " + nextMonday);
        System.out.println("firstOfNext: " + firstOfNext);
        System.out.println("feb: " + feb);
    }
}
newYear2003: 2003-01-01
newYear2004: 2004-01-01
december2001: 2001-12-01
february2001: 2001-02-01
xmas2001: 2001-12-25
lastDay: 2001-01-31
nextMonday: 2001-01-08
firstOfNext: 2001-02-01
feb: 2001-02-28

Два нюанса из вывода. TemporalAdjusters.next(MONDAY) ищет понедельник строго после текущей даты, поэтому от понедельника 1 января получилось 8 января; если подходит и сам текущий день, используйте nextOrSame(). А withMonth(2) от 31 января не падает, а сдвигает день на последний допустимый — 28 февраля. Зато withDayOfMonth(30) для февральской даты выбросит DateTimeException: Invalid date 'FEBRUARY 30': здесь вы явно просите несуществующий день.

Методы plus() и minus()

Чтобы прибавить или вычесть дни, недели, месяцы или годы, используют plusDays(), plusWeeks(), plusMonths(), plusYears(), парные им minus...() и универсальные plus(количество, ChronoUnit) / minus(количество, ChronoUnit). Каждый вызов возвращает новую дату.

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class PlusMinusLocalDateExample {
    public static void main(String[] args) {
        LocalDate newYear2001 = LocalDate.of(2001, 1, 1);

        // Добавление
        LocalDate newYear2005 = newYear2001.plusYears(4);
        LocalDate march2001 = newYear2001.plusMonths(2);
        LocalDate jan15Year2001 = newYear2001.plusDays(14);
        LocalDate jan22Year2001 = newYear2001.plusWeeks(3);
        LocalDate newYear2006 = newYear2001.plus(5, ChronoUnit.YEARS);

        // Вычитание
        LocalDate newYear2000 = newYear2001.minusYears(1);
        LocalDate nov2000 = newYear2001.minusMonths(2);
        LocalDate dec30Year2000 = newYear2001.minusDays(2);
        LocalDate dec25Year2000 = newYear2001.minusWeeks(1);
        LocalDate newYear1999 = newYear2001.minus(2, ChronoUnit.YEARS);

        System.out.println("newYear2005: " + newYear2005);
        System.out.println("march2001: " + march2001);
        System.out.println("jan15Year2001: " + jan15Year2001);
        System.out.println("jan22Year2001: " + jan22Year2001);
        System.out.println("newYear2006: " + newYear2006);

        System.out.println("newYear2000: " + newYear2000);
        System.out.println("nov2000: " + nov2000);
        System.out.println("dec30Year2000: " + dec30Year2000);
        System.out.println("dec25Year2000: " + dec25Year2000);
        System.out.println("newYear1999: " + newYear1999);
    }
}
newYear2005: 2005-01-01
march2001: 2001-03-01
jan15Year2001: 2001-01-15
jan22Year2001: 2001-01-22
newYear2006: 2006-01-01
newYear2000: 2000-01-01
nov2000: 2000-11-01
dec30Year2000: 2000-12-30
dec25Year2000: 2000-12-25
newYear1999: 1999-01-01

Что выведет этот код? Конец месяца — место, где арифметика дат ведёт себя не так, как ожидают:

LocalDate jan31 = LocalDate.of(2001, 1, 31);

System.out.println(jan31.plusMonths(1));               // 2001-02-28
System.out.println(jan31.plusMonths(1).plusMonths(1)); // 2001-03-28
System.out.println(jan31.plusMonths(2));               // 2001-03-31

После первого plusMonths(1) день «обрезается» до 28, и второй вызов уже не знает, что исходно было 31-е. Если считаете, например, ежемесячные даты платежей, всегда прибавляйте N месяцев к исходной дате, а не по одному месяцу к предыдущему результату.

Единицы времени LocalDate не поддерживает: методов plusHours() у класса нет, а date.plus(1, ChronoUnit.HOURS) выбросит UnsupportedTemporalTypeException: Unsupported unit: Hours. Для даты со временем нужен LocalDateTime. Прибавить сразу «1 год 2 месяца 3 дня» можно объектом Period: date.plus(Period.of(1, 2, 3)) — подробно в уроке Класс Period.

Сколько дней между двумя датами

Количество дней между двумя LocalDate возвращает ChronoUnit.DAYS.between(начало, конец) или эквивалентный начало.until(конец, ChronoUnit.DAYS). Результат имеет тип long, конечная дата в подсчёт не входит, а если конец раньше начала, число будет отрицательным.

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class DaysBetweenExample {
    public static void main(String[] args) {
        LocalDate start = LocalDate.of(2001, 1, 1);
        LocalDate end = LocalDate.of(2001, 3, 1);

        System.out.println(ChronoUnit.DAYS.between(start, end));   // 59
        System.out.println(start.until(end, ChronoUnit.DAYS));     // 59
        System.out.println(ChronoUnit.MONTHS.between(start, end)); // 2
        System.out.println(ChronoUnit.DAYS.between(end, start));   // -59
    }
}

Если нужна разница в виде «N лет, M месяцев, K дней», используйте Period.between() — см. урок Использование класса Period.

Разбор и форматирование строки

LocalDate.toString() и LocalDate.parse(String) всегда работают с форматом ISO-8601 yyyy-MM-dd. Для любого другого формата, например 15.03.2024, нужен DateTimeFormatter: его передают в parse(текст, formatter) и в format(formatter).

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

public class LocalDateFormatExample {
    public static void main(String[] args) {
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd.MM.yyyy");

        LocalDate date = LocalDate.parse("15.03.2024", formatter);
        System.out.println(date);                   // 2024-03-15
        System.out.println(date.format(formatter)); // 15.03.2024
    }
}

Символы шаблона, локали и разбор названий месяцев разобраны в уроке Класс DateTimeFormatter.

Конвертация LocalDate в java.util.Date и java.sql.Date

LocalDate в java.util.Date напрямую не превращается: у даты нет времени и зоны, а Date — это момент времени. Поэтому сначала выбирают зону и время суток: Date.from(localDate.atStartOfDay(zone).toInstant()). Обратно: date.toInstant().atZone(zone).toLocalDate(). С java.sql.Date проще — есть готовые java.sql.Date.valueOf(localDate) и sqlDate.toLocalDate().

import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;

public class LocalDateConvertExample {
    public static void main(String[] args) {
        ZoneId zone = ZoneId.systemDefault();
        LocalDate localDate = LocalDate.of(2024, 3, 15);

        // LocalDate -> java.util.Date (полночь в зоне zone)
        Date utilDate = Date.from(localDate.atStartOfDay(zone).toInstant());

        // java.util.Date -> LocalDate
        LocalDate back = utilDate.toInstant().atZone(zone).toLocalDate();
        LocalDate back9 = LocalDate.ofInstant(utilDate.toInstant(), zone); // Java 9+

        // LocalDate <-> java.sql.Date
        java.sql.Date sqlDate = java.sql.Date.valueOf(localDate);
        LocalDate fromSql = sqlDate.toLocalDate();

        System.out.println(utilDate);
        System.out.println(back);
        System.out.println(back9);
        System.out.println(sqlDate);
        System.out.println(fromSql);
    }
}
Fri Mar 15 00:00:00 MSK 2024   (при зоне по умолчанию Europe/Moscow)
2024-03-15
2024-03-15
2024-03-15
2024-03-15
Направление Код Нюанс
LocalDate → java.util.Date Date.from(ld.atStartOfDay(zone).toInstant()) Время станет 00:00 в выбранной зоне
java.util.Date → LocalDate d.toInstant().atZone(zone).toLocalDate() В Java 9+ короче: LocalDate.ofInstant(d.toInstant(), zone)
LocalDate → java.sql.Date java.sql.Date.valueOf(ld) Зона не нужна
java.sql.Date → LocalDate sqlDate.toLocalDate() sqlDate.toInstant() выбрасывает UnsupportedOperationException
Миллисекунды → LocalDate Instant.ofEpochMilli(ms).atZone(zone).toLocalDate() Про Instant — в уроке Класс Instant
JDBC 4.2+ rs.getObject("col", LocalDate.class), ps.setObject(1, ld) Конвертация через java.sql.Date не нужна

Одна зона в обе стороны

Конвертируйте туда и обратно с одной и той же ZoneId. Если записать полночь 15 марта по Москве (UTC+3), а читать в ZoneOffset.UTC, получится 14 марта, 21:00 — то есть LocalDate 2024-03-14, на день раньше.

Шпаргалка по методам LocalDate

Все методы LocalDate делятся на пять групп: создание, чтение полей, сравнение, получение изменённой копии и конвертация. Методы, которые «меняют» дату, на самом деле возвращают новый объект.

Метод Что делает Граничный случай / ошибка
of(y, m, d) Создаёт дату DateTimeException для несуществующей даты
now(), now(ZoneId) Текущая дата Без зоны берётся зона JVM по умолчанию
parse(text) Разбирает строку yyyy-MM-dd DateTimeParseException для «2023-1-5»
getYear(), getMonthValue(), getDayOfMonth() Год, месяц (1–12), день Месяц с 1, а не с 0
getDayOfWeek() День недели (DayOfWeek) Как число через ChronoField: понедельник = 1
lengthOfMonth(), isLeapYear() Дней в месяце, високосный ли год Февраль 2024 — 29, февраль 2023 — 28
isBefore(), isAfter(), isEqual() Сравнение NullPointerException, если аргумент null
withMonth(), withDayOfMonth(), with(TemporalAdjuster) Копия с заменённым полем withMonth(2) от 31-го даёт 28/29-е
plusDays() … minusYears() Копия, сдвинутая на N единиц plus(1, HOURS) → UnsupportedTemporalTypeException
atStartOfDay(), atTime() Переход к LocalDateTime atStartOfDay(zone) возвращает ZonedDateTime
toEpochDay() Число дней от 1970-01-01 Для дат до 1970 года отрицательное

На чём чаще всего ошибаются

Большинство ошибок с LocalDate связаны с тремя вещами: неизменяемостью объекта, нумерацией месяцев и тем, что у даты нет времени. Ниже — типичные промахи и исправления.

Ошибка Что происходит Как правильно
date.plusDays(1); Результат теряется, date не меняется date = date.plusDays(1);
LocalDate.of(2024, 0, 1) DateTimeException: месяцы 1–12 LocalDate.of(2024, Month.JANUARY, 1)
d1 == d2 Сравнение ссылок, обычно false d1.isEqual(d2) или d1.equals(d2)
d1.compareTo(d2) == -1 Может вернуть -4 и условие не сработает d1.compareTo(d2) < 0 или d1.isBefore(d2)
LocalDate.parse("15.03.2024") DateTimeParseException LocalDate.parse(s, DateTimeFormatter.ofPattern("dd.MM.yyyy"))
Ежемесячная дата через цепочку plusMonths(1) 31-е «съезжает» на 28-е навсегда start.plusMonths(n) от исходной даты

Часто задаваемые вопросы

Чем LocalDate отличается от LocalDateTime и java.util.Date?

LocalDate хранит только дату (2024-03-15), LocalDateTime — дату и время без зоны (2024-03-15T10:30), а java.util.Date — момент времени в миллисекундах от 1970 года и к тому же изменяемый. Для дня рождения или срока оплаты подходит LocalDate, для времени события — LocalDateTime или Instant. Как переводить одно в другое — в разделе Конвертация.

Потокобезопасен ли LocalDate? Можно ли хранить его в static final поле?

Да. LocalDate неизменяем, поэтому один объект можно без синхронизации использовать из любого числа потоков и смело объявлять константой, например static final LocalDate RELEASE = LocalDate.of(2014, 3, 18);. В отличие от SimpleDateFormat, потокобезопасен и DateTimeFormatter.

Как отсортировать список дат LocalDate?

LocalDate реализует Comparable, поэтому достаточно list.sort(Comparator.naturalOrder()) или Collections.sort(list) — от ранней даты к поздней. Обратный порядок: list.sort(Comparator.reverseOrder()). Найти самую позднюю дату: Collections.max(list).

Как протестировать код с LocalDate.now(), если дата каждый день разная?

Передавайте в код объект Clock и вызывайте LocalDate.now(clock). В продакшене это Clock.systemDefaultZone(), а в тесте — фиксированные часы: Clock.fixed(Instant.parse("2024-03-15T10:00:00Z"), ZoneOffset.UTC). Тогда LocalDate.now(clock) всегда вернёт 2024-03-15.

Практика по теме

Закрепите урок на задачах — у каждой есть уровень и оценка времени.

Все задачи модуля →

Видео объяснение

Предпочитаете видеоформат? Посмотрите этот урок с примерами и объяснениями.

Комментарии

Зарегистрируйтесь или войдите, чтобы иметь возможность оставить комментарий.