Класс Calendar в Java
Устаревший класс java.util.Calendar и GregorianCalendar — принцип работы и связь с java.time.
java.util.Calendar — вторая половина устаревшей пары классов для работы с датой и временем. Если java.util.Date является лишь обёрткой над long-значением миллисекунд с начала эпохи, то Calendar — это класс, который знает, на какой год, месяц и день приходится данная миллисекунда. Он был добавлен в Java 1.1, чтобы вынести календарную арифметику из Date — именно поэтому большинство методов доступа к полям Date помечены как устаревшие.
В современном коде следует использовать java.time. Тем не менее Calendar по-прежнему встречается: в старых кодовых путях, в библиотеках, написанных до Java 8, и в JDBC-драйверах, которые не были обновлены. Вам нужно уметь его читать, преобразовывать и двигаться дальше. На этой странице рассматривается, как создать Calendar, безопасно читать его поля (обратите внимание на нумерацию месяцев с нуля), выполнять календарную арифметику и, что важнее всего, переходить к современному API.
Calendar — абстрактный класс
Calendar сам по себе является абстрактным классом. Вы никогда не вызываете new Calendar(...). Экземпляр получается через фабричный метод:
Calendar cal = Calendar.getInstance();Он возвращает конкретный подкласс — почти всегда GregorianCalendar — с уже загруженным текущим временем, часовым поясом по умолчанию и локалью по умолчанию. При необходимости можно задать параметры явно:
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC"), Locale.US);У GregorianCalendar есть и публичные конструкторы, однако принято использовать фабричный метод.
Константы полей
Calendar предоставляет доступ ко всему через целочисленные константы полей и обобщённый метод get(int field):
| Поле | Значение | Диапазон |
|---|---|---|
Calendar.YEAR | Год | полный год, например 2026 |
Calendar.MONTH | Месяц | 0–11 (January = 0) |
Calendar.DAY_OF_MONTH | День месяца | 1–31 |
Calendar.DAY_OF_WEEK | День недели | 1–7 (Sunday = 1) |
Calendar.HOUR_OF_DAY | Час | 0–23 |
Calendar.MINUTE | Минута | 0–59 |
Calendar.SECOND | Секунда | 0–59 |
Calendar.MILLISECOND | Миллисекунда | 0–999 |
В этой таблице скрываются две ловушки: MONTH отсчитывается с нуля (Calendar.JANUARY == 0), а DAY_OF_WEEK начинается с воскресенья. Ошибки на единицу в устаревшем коде почти всегда возникают именно здесь.
Установка значений и арифметика
set принимает поле и значение; add выполняет арифметику с учётом календаря и переносит переполнение в более старшие поля:
Calendar cal = Calendar.getInstance();
cal.set(2026, Calendar.MAY, 29); // Y, M, D — month is zero-based
cal.add(Calendar.DAY_OF_MONTH, 5); // → 2026-06-03roll делает то же самое поле за полем, но не переносит значение в более старшие поля — roll(DAY_OF_MONTH, 5) для 30 мая даст 4 мая, а не 4 июня. Это редко бывает нужным.
Экземпляры Calendar являются изменяемыми, поэтому передача такого объекта в метод означает передачу живой ссылки. Клонируйте объект перед тем, как раскрывать его вовне, так же как вы поступаете с Date.
Переход к java.time
Единственная причина, по которой сегодня стоит работать с Calendar, — это выйти из него. Для этого существуют два метода:
Instant when = cal.toInstant();
ZoneId zone = cal.getTimeZone().toZoneId();
ZonedDateTime zdt = when.atZone(zone);Начиная с ZonedDateTime, вам доступен весь современный API. Обратный путь:
Calendar cal = GregorianCalendar.from(zdt); // Java 8+GregorianCalendar.from(ZonedDateTime) — поддерживаемый способ конвертации. Избегайте двойного преобразования через Date.
Пример
В примере ниже показаны два действия, которые вы будете выполнять с Calendar в реальном коде: чтение полей из устаревшего экземпляра и переход к java.time для любых нетривиальных операций.
Что следует вынести из результатов выполнения:
Calendar.MONTHдля мая выводит4. Нумерация месяцев с нуля — главный источник ошибок в устаревшем коде для работы с датами.getInstance(TimeZone)привязывает экземпляр к часовому поясу — в отличие отDate, у которого часового пояса нет.add(MONTH, 1)учитывает длину месяца; вы получаете правильный день следующего месяца, даже если месяцы не все по 30 дней.cal.toInstant().atZone(cal.getTimeZone().toZoneId())— однострочный переход кjava.time.GregorianCalendar.from(ZonedDateTime)позволяет вернутьCalendarв старый API без потери информации о часовом поясе.
Что дальше
На этом завершается Часть 14 — Дата и время. Следующая — Часть 15, Многопоточность и параллелизм, которая начинается с раздела Многопоточность в Java — модели, которая повлияла на все остальные API в этой книге.