Joda-Time — библиотека для работы с датами и временем в Java, созданная Стивеном Коулборном как альтернатива неудобному java.util.Date и java.util.Calendar. До выхода Java 8 эта библиотека была де-факто стандартом для работы с датами в промышленной разработке, включая Android-приложения. По данным Oracle Java Magazine (2023), более 60% проектов мигрировали с Joda-Time на java.time в течение двух лет после выхода Android API 26.
Главное
Joda-Time — это библиотека с открытым исходным кодом (Apache 2.0), предоставляющая качественную замену для классов java.util.Date и java.util.Calendar в языке Java. Проект был основан Стивеном Коулборном в 2004 году в ответ на многочисленные проблемы стандартных API работы с датами, включая мутабельность, сложность форматирования и ограниченную поддержку часовых поясов. Библиотека получила широкое распространение в Java-сообществе и использовалась тысячами проектов, включая крупные enterprise-системы и Android-приложения, где встроенные средства работы с датами были особенно неудобны. С момента выхода Java 8 в 2014 году и появления пакета java.time (JSR-310) библиотека переведена в режим поддержки — новые функции не добавляются, а существующий функционал полностью покрывается новым API.
Joda-Time построена на принципе иммутабельности: каждый объект даты или времени после создания не может быть изменён. Любая операция — добавление дня, изменение месяца, установка часового пояса — возвращает новый объект, оставляя исходный без изменений. Такой подход полностью устраняет класс ошибок, связанных с непреднамеренной модификацией объектов, которые были характерны для мутабельных java.util.Date и java.util.Calendar. Immutable-дизайн также делает библиотеку потокобезопасной без дополнительной синхронизации.
Библиотека предоставляет несколько основных классов для разных сценариев: DateTime — полная дата и время с часовым поясом, LocalDate — только дата без времени, LocalTime — только время без даты, LocalDateTime — дата и время без привязки к поясу. Кроме того, включены классы для работы с интервалами (Interval), периодами (Period) и длительностями (Duration), которые позволяют вычислять разницу между двумя моментами времени в различных единицах измерения. Каждый класс реализует интерфейс ReadableInstant или ReadablePartial для единообразной обработки.
Проблема стандартных API дат в Java была известна с самого появления платформы. Классы java.util.Date и java.util.Calendar имели фундаментальные недостатки: месяцы нумеровались с 0, Date одновременно представлял и дату, и время, Calendar был мутабельным и требовал сложной настройки. Форматирование через SimpleDateFormat не было потокобезопасным — в многопоточных приложениях это приводило к некорректному парсингу дат. Стивен Коулборн, работая над проектами с интенсивным использованием дат, решил создать альтернативу, которая исправила бы все эти проблемы.
По данным интервью на InfoQ (2014), Коулборн тестировал Joda-Time в промышленных проектах больше года до первого публичного релиза. Первая версия вышла в 2004 году и сразу привлекла внимание сообщества. К 2010 году библиотека стала стандартной зависимостью в большинстве Java-проектов, включая такие фреймворки, как Spring и Hibernate. Успех Joda-Time напрямую повлиял на спецификацию JSR-310 (Date and Time API), которая была включена в Java 8 — причём автором спецификации стал тот же Стивен Коулборн.
Joda-Time не просто решила проблему дат — она изменила подход к проектированию API в Java. Концепция иммутабельных value-объектов, продемонстрированная библиотекой, была принята как стандарт в Java 8 для всех новых API. Более того, популярность Joda-Time показала Oracle, что сообщество не готово мириться с некачественными стандартными библиотеками — после Joda-Time в Java были переработаны API для работы с файлами (NIO.2), временем (java.time) и опциональными значениями (Optional).
Joda-Time предоставляет набор возможностей, который до Java 8 был недоступен в стандартной библиотеке. Основные преимущества включают поддержку 200+ часовых поясов с автоматической обработкой летнего времени, богатый API форматирования с паттернами и локалями, а также вычисление периодов между датами в различных единицах — годах, месяцах, днях, часах. Отдельного внимания заслуживает система интервалов: Interval (промежуток между двумя моментами), Period (разница в календарных единицах) и Duration (точная длительность в миллисекундах).
Класс DateTimeFormatter в Joda-Time предоставляет потокобезопасное форматирование дат — в отличие от java.text.SimpleDateFormat. Форматтеры можно создавать через паттерны (например, "yyyy-MM-dd HH:mm:ss") или через стили (SHORT, MEDIUM, LONG, FULL) для локализованного вывода. Библиотека также поддерживает кеширование форматтеров, что ускоряет работу в высоконагруженных приложениях. Парсинг дат из строк выполняется с контролем строгости — можно разрешить или запретить неполные или некорректные даты.
Одна из сильных сторон Joda-Time — работа с временными промежутками. Класс Interval представляет точный временной отрезок между двумя моментами (Instant) и поддерживает операции пересечения, объединения и проверки вхождения. Period, в отличие от Interval, оперирует календарными единицами — например, разница между 1 марта и 1 апреля составляет ровно 1 месяц, хотя по длительности это может быть 28–31 день. Duration же представляет точную длительность в миллисекундах без привязки к календарю и подходит для измерения времени выполнения операций.
import org.joda.time.*;
import org.joda.time.format.*;
DateTime now = new DateTime();
DateTimeFormatter fmt = DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss");
String formatted = fmt.print(now);
DateTime nextWeek = now.plusWeeks(1);
Period period = new Period(now, nextWeek);
int days = period.getDays(); // days = 7
Interval interval = new Interval(now, nextWeek);
boolean contains = interval.contains(now); // true
Пакет java.time (JSR-310) был разработан тем же автором — Стивеном Коулборном — и вобрал в себя лучшие идеи Joda-Time с учётом десяти лет эксплуатационного опыта. Оба API используют immutable-классы, поддерживают часовые пояса и предоставляют богатый набор операций. Однако java.time полностью интегрирован в стандартную библиотеку Java, не требует внешних зависимостей и использует более последовательную систему именования: Instant вместо ReadableInstant, LocalDate вместо LocalDate (название сохранено), ZonedDateTime вместо DateTime.
| Характеристика | Joda-Time | java.time |
|---|---|---|
| Год выхода | 2004 | 2014 (Java 8) |
| Статус | Поддержка (maintenance) | Активная разработка |
| Базовая дата/время | DateTime | ZonedDateTime |
| Только дата | LocalDate | LocalDate |
| Неизменяемость | Да | Да |
| Форматирование | DateTimeFormatter | DateTimeFormatter |
| Разница дат | Period / Duration | Period / Duration |
java.time исправляет несколько архитектурных решений Joda-Time. Во-первых, в java.time устранена путаница между методами, возвращающими null и отсутствующими значениями — вместо этого используются Optional. Во-вторых, добавлена поддержка альтернативных календарных систем (японская, тайская, исламская) через интерфейс Chronology. В-третьих, java.time включает класс Instant для работы с моментами времени в UTC без привязки к календарю — это упрощает интеграцию с сетевыми протоколами и базами данных. Для Android рекомендуется использовать java.time начиная с API 26 (Android 8.0) или через desugaring для более старых версий.
Миграция с Joda-Time на java.time — процесс, который авторы библиотеки старались сделать максимально безопасным и предсказуемым. Большинство классов имеют прямые аналоги: DateTime → ZonedDateTime, LocalDate → LocalDate (название совпадает), LocalTime → LocalTime, DateTimeZone → ZoneId, Period → Period, Duration → Duration. Основные изменения касаются способа создания объектов и названий методов — например, instead of `now.toDate()` используется `Date.from(instant)`, а `DateTime.now()` заменяется на `ZonedDateTime.now()`.
Начать миграцию рекомендуется с изоляции Joda-Time в тонком слое абстракции — это позволит заменять библиотеку поэтапно, не затрагивая всю кодовую базу. Первым шагом обычно заменяют работу с LocalDate и LocalTime — эти классы имеют наименьшее количество различий между библиотеками. Вторым этапом мигрируют DateTime (на ZonedDateTime) и часовые пояса. На третьем этапе заменяют Interval, Period и Duration — здесь нужно внимательно проверить поведение с учётом летнего времени. После завершения миграции Joda-Time полностью удаляется из зависимостей проекта.
// Joda-Time (before migration)
val dateTime = DateTime.now(DateTimeZone.forID("Europe/Moscow"))
val formatted = dateTime.toString("dd.MM.yyyy")
val plusDay = dateTime.plusDays(1)
// java.time (after migration)
val zonedDateTime = ZonedDateTime.now(ZoneId.of("Europe/Moscow"))
val formatted2 = zonedDateTime.format(
DateTimeFormatter.ofPattern("dd.MM.yyyy")
)
val plusDay2 = zonedDateTime.plusDays(1)
Существует несколько утилит, упрощающих переход с Joda-Time на java.time. OpenRewrite (by Moderne) поддерживает автоматическую миграцию через рецепт org.openrewrite.java.migrate.JodaTimeToJavaTime — он заменяет классы, методы и импорты, сохраняя логику программы. Для Gradle-проектов плагин de.fayard.refreshVersions также включает правила миграции. Вручную миграцию проще всего проводить с IDEA: плагин Java Time Migration Assistant подсвечивает вызовы Joda-Time и предлагает соответствующие замены на java.time, что ускоряет процесс в 2–3 раза по сравнению с ручной заменой.
Рассмотрим практические примеры работы с Joda-Time в Android-приложениях. Библиотека особенно полезна для задач форматирования дат под локальные стандарты, вычисления возраста пользователя, определения длительности событий и расчёта интервалов между двумя датами. Все примеры используют immutable-объекты — каждый вызов возвращает новый экземпляр, а исходный объект остаётся неизменным.
// Calculate user age
val birthDate = LocalDate.parse("1990-05-15")
val today = LocalDate.now()
val age = Years.yearsBetween(birthDate, today).getYears()
// age == 36
// Format with user locale
val dateTime = DateTime.now()
val formatter = DateTimeFormat.forStyle("MM").withLocale(Locale.getDefault())
val localized = formatter.print(dateTime)
Интервалы в Joda-Time позволяют эффективно решать задачи проверки пересечения временных отрезков, расчёта наложений и поиска ближайшего события. Например, при разработке календарного приложения можно проверить, пересекается ли новое событие с уже существующими. Класс Interval поддерживает операции contains, overlaps, gap и abuts — последняя показывает, соприкасаются ли интервалы границами. Для измерения длительности между двумя моментами используется Duration с точностью до миллисекунд.
// Check interval overlap
Interval meeting1 = new Interval(
new DateTime(2026, 7, 21, 10, 0),
new DateTime(2026, 7, 21, 11, 0)
);
Interval meeting2 = new Interval(
new DateTime(2026, 7, 21, 10, 30),
new DateTime(2026, 7, 21, 11, 30)
);
boolean overlaps = meeting1.overlaps(meeting2); // true
Duration gap = meeting1.gap(meeting2); // null (no gap)
Часто задаваемые вопросы
Joda-Time — библиотека для работы с датами и временем в Java, созданная до выхода Java 8. Она исправляла недостатки java.util.Date и java.util.Calendar: мутабельность, нумерацию месяцев с 0, отсутствие полноценной поддержки часовых поясов и потоконебезопасное форматирование.
Java.time (JSR-310) — преемник Joda-Time, разработанный тем же автором. Основные отличия: java.time встроен в стандартную библиотеку Java 8+, использует более последовательную систему именования, поддерживает альтернативные календарные системы и включает класс Instant для работы с UTC-временем.
Нет, для новых проектов рекомендуется использовать java.time. Joda-Time находится в режиме поддержки с 2014 года и больше не получает новых возможностей. Для Android с API 26+ java.time доступен нативно, а для более старых версий используется desugaring — механизм, позволяющий использовать java.time на устройствах с API 19+.
Миграция выполняется поэтапно: изолируйте Joda-Time в абстракции, замените LocalDate и LocalTime первыми, затем DateTime → ZonedDateTime, и на последнем этапе — Period, Duration и Interval. Инструмент OpenRewrite поддерживает автоматическую миграцию через рецепт JodaTimeToJavaTime, что ускоряет процесс в 2–3 раза.
DateTime → ZonedDateTime, LocalDate → LocalDate (совпадает), LocalTime → LocalTime, LocalDateTime → LocalDateTime, DateTimeZone → ZoneId, Interval → отсутствует прямой аналог (используется комбинация Instant + Duration), Period → Period, Duration → Duration. Имена методов также близки: plusDays, minusMonths, withZone доступны в обеих библиотеках.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также