Перейти к основному содержимому

Введение в Joda-Time

· 11 мин. чтения

1. Введение

Joda-Time — это наиболее широко используемая библиотека обработки даты и времени до выпуска Java 8. Ее цель состояла в том, чтобы предложить интуитивно понятный API для обработки даты и времени, а также решить проблемы проектирования, существовавшие в Java Date/Time API.

Основные концепции, реализованные в этой библиотеке, были представлены в ядре JDK с выпуском версии Java 8. Новый API даты и времени находится в пакете java.time ( JSR-310 ). Обзор этих возможностей можно найти в этой статье .

После выпуска Java 8 авторы считают проект в основном законченным и советуют по возможности использовать Java 8 API.

2. Зачем использовать Joda-Time?

API даты/времени до Java 8 создавал множество проблем при проектировании.

Среди проблем — тот факт, что классы Date и SimpleDateFormatter не являются потокобезопасными. Чтобы решить эту проблему, Joda-Time использует неизменяемые классы для обработки даты и времени.

Класс Date не представляет фактическую дату, а вместо этого указывает момент времени с точностью до миллисекунды. Год в дате начинается с 1900 года, в то время как в большинстве операций с датами обычно используется время эпохи, которое начинается с 1 января 1970 года.

Кроме того, смещение дня, месяца и года Date противоречит здравому смыслу. Дни начинаются с 0, а месяц начинается с 1. Чтобы получить доступ к любому из них, мы должны использовать класс Calendar . Joda-Time предлагает чистый и удобный API для обработки дат и времени.

Joda-Time также предлагает поддержку восьми календарных систем , в то время как Java предлагает всего 2: григорианскую — java.util.GregorianCalendar и японскую — java.util.JapaneseImperialCalendar .

3. Настройка

Чтобы включить функциональность библиотеки Joda-Time, нам нужно добавить следующую зависимость от Maven Central :

<dependency>
<groupId>joda-time</groupId>
<artifactId>joda-time</artifactId>
<version>2.10</version>
</dependency>

4. Обзор библиотеки

Joda-Time моделирует концепцию даты и времени , используя классы в пакете org.joda.time .

Среди этих классов наиболее часто используются:

  • LocalDate — представляет дату без времени
  • LocalTime — представляет время без часового пояса.
  • LocalDateTime — представляет дату и время без часового пояса.
  • Instant — представляет точный момент времени в миллисекундах из эпохи Java 1970-01-01T00:00:00Z.
  • Продолжительность — представляет собой продолжительность в миллисекундах между двумя моментами времени.
  • Period — аналогично Duration , но разрешает доступ к отдельным компонентам объекта даты и времени, таким как годы, месяц, дни и т. д.
  • Интервал – представляет временной интервал между двумя моментами

Другими важными функциями являются синтаксические анализаторы и средства форматирования даты . Их можно найти в пакете org.joda.time.format .

Календарную систему и классы часовых поясов можно найти в пакетах org.joda.time.chrono и org.joda.time.tz.

Давайте рассмотрим несколько примеров, в которых мы используем ключевые функции Joda-Time для обработки даты и времени.

5. Представление даты и времени

5.1. Текущая дата и время

Текущую дату без информации о времени можно получить с помощью метода now ( ) класса LocalDate :

LocalDate currentDate = LocalDate.now();

Когда нам нужно только текущее время без информации о дате, мы можем использовать класс LocalTime :

LocalTime currentTime = LocalTime.now();

Чтобы получить представление текущей даты и времени без учета часового пояса, мы можем использовать LocalDateTime :

LocalDateTime currentDateAndTime = LocalDateTime.now();

Теперь, используя currentDateAndTime , мы можем преобразовать его в дату и время моделирования других типов объектов.

Мы можем получить объект DateTime (который учитывает часовой пояс) с помощью метода toDateTime() . Когда время не нужно, мы можем преобразовать его в LocalDate с помощью метода toLocalDate() , а когда нам нужно только время, мы можем использовать toLocalTime() для получения объекта LocalTime :

DateTime dateTime = currentDateAndTime.toDateTime();
LocalDate localDate = currentDateAndTime.toLocalDate();
LocalTime localTime = currentDateAndTime.toLocalTime();

Все вышеперечисленные методы имеют перегруженный метод, который принимает объект DateTimeZone , чтобы помочь нам представить дату или время в указанном часовом поясе:

LocalDate currentDate = LocalDate.now(DateTimeZone.forID("America/Chicago"));

Кроме того, Joda-Time предлагает отличную интеграцию с Java Date and Time API. Конструкторы принимают объект java.util.Date , а также мы можем использовать метод toDate() для возврата объекта java.util.Date :

LocalDateTime currentDateTimeFromJavaDate = new LocalDateTime(new Date());
Date currentJavaDate = currentDateTimeFromJavaDate.toDate();

5.2. Пользовательская дата и время

Для представления пользовательских даты и времени Joda-Time предоставляет нам несколько конструкторов. Мы можем указать следующие объекты:

  • Мгновение _ ``
  • объект даты Java
  • Строковое представление даты и времени в формате ISO.
  • части даты и времени: год, месяц, день, час, минута, секунда, миллисекунда
Date oneMinuteAgoDate = new Date(System.currentTimeMillis() - (60 * 1000));
Instant oneMinutesAgoInstant = new Instant(oneMinuteAgoDate);

DateTime customDateTimeFromInstant = new DateTime(oneMinutesAgoInstant);
DateTime customDateTimeFromJavaDate = new DateTime(oneMinuteAgoDate);
DateTime customDateTimeFromString = new DateTime("2018-05-05T10:11:12.123");
DateTime customDateTimeFromParts = new DateTime(2018, 5, 5, 10, 11, 12, 123);

Другой способ, которым мы можем определить пользовательскую дату и время, — это анализ заданного строкового представления даты и времени в формате ISO:

DateTime parsedDateTime = DateTime.parse("2018-05-05T10:11:12.123");

Мы также можем анализировать пользовательские представления даты и времени, определив собственный DateTimeFormatter :

DateTimeFormatter dateTimeFormatter
= DateTimeFormat.forPattern("MM/dd/yyyy HH:mm:ss");
DateTime parsedDateTimeUsingFormatter
= DateTime.parse("05/05/2018 10:11:12", dateTimeFormatter);

6. Работа с датой и временем

6.1. Использование мгновенного

Instant представляет количество миллисекунд с 1970-01-01T00 :00:00Z до заданного момента времени. Например, текущий момент времени можно получить с помощью конструктора по умолчанию или метода now() :

Instant instant = new Instant();
Instant.now();

Чтобы создать Instant для пользовательского момента времени, мы можем использовать либо один из конструкторов, либо методы ofEpochMilli() и ofEpochSecond() :

Instant instantFromEpochMilli
= Instant.ofEpochMilli(milliesFromEpochTime);
Instant instantFromEpocSeconds
= Instant.ofEpochSecond(secondsFromEpochTime);

Конструкторы принимают строку , представляющую дату и время в формате ISO, дату Java или длинное значение, представляющее количество миллисекунд от 1970-01-01T00:00:00Z:

Instant instantFromString
= new Instant("2018-05-05T10:11:12");
Instant instantFromDate
= new Instant(oneMinuteAgoDate);
Instant instantFromTimestamp
= new Instant(System.currentTimeMillis() - (60 * 1000));

Когда дата и время представлены в виде строки , у нас есть возможность проанализировать строку , используя желаемый формат:

Instant parsedInstant
= Instant.parse("05/05/2018 10:11:12", dateTimeFormatter);

Теперь, когда мы знаем, что представляет собой Instant и как мы можем его создать, давайте посмотрим, как его можно использовать.

Для сравнения с объектами Instant мы можем использовать compareTo() , потому что он реализует интерфейс Comparable , но также мы можем использовать методы API Joda-Time, предоставленные в интерфейсе ReadableInstant , который также реализует Instant :

assertTrue(instantNow.compareTo(oneMinuteAgoInstant) > 0);
assertTrue(instantNow.isAfter(oneMinuteAgoInstant));
assertTrue(oneMinuteAgoInstant.isBefore(instantNow));
assertTrue(oneMinuteAgoInstant.isBeforeNow());
assertFalse(oneMinuteAgoInstant.isEqual(instantNow));

Еще одна полезная функция заключается в том, что Instant можно преобразовать в объект DateTime или событие Java Date :

DateTime dateTimeFromInstant = instant.toDateTime();
Date javaDateFromInstant = instant.toDate();

Когда нам нужно получить доступ к частям даты и времени, таким как год, час и т. д., мы можем использовать метод get() и указать DateTimeField :

int year = instant.get(DateTimeFieldType.year());
int month = instant.get(DateTimeFieldType.monthOfYear());
int day = instant.get(DateTimeFieldType.dayOfMonth());
int hour = instant.get(DateTimeFieldType.hourOfDay());

Теперь, когда мы рассмотрели класс Instant , давайте посмотрим на несколько примеров того, как мы можем использовать Duration , Period и Interval .

6.2. Использование продолжительности , периода и интервала

Duration представляет собой время в миллисекундах между двумя моментами времени или, в данном случае, это могут быть два Instant . Мы будем использовать это, когда нам нужно добавить или вычесть определенное количество времени из другого Instant без учета хронологии и часовых поясов :

long currentTimestamp = System.currentTimeMillis();
long oneHourAgo = currentTimestamp - 24*60*1000;
Duration duration = new Duration(oneHourAgo, currentTimestamp);
Instant.now().plus(duration);

Кроме того, мы можем определить, сколько дней, часов, минут, секунд или миллисекунд представляет продолжительность:

long durationInDays = duration.getStandardDays();
long durationInHours = duration.getStandardHours();
long durationInMinutes = duration.getStandardMinutes();
long durationInSeconds = duration.getStandardSeconds();
long durationInMilli = duration.getMillis();

Основное различие между Period и Duration заключается в том, что Period определяется компонентами даты и времени (годы, месяцы, часы и т. д.) и не представляет собой точное количество миллисекунд . При использовании даты и времени в расчетах периода будет учитываться часовой пояс и переход на летнее время .

Например, добавление периода 1 месяц к 1 февраля приведет к представлению даты 1 марта. При использовании Period библиотека будет учитывать високосные годы.

Если мы будем использовать Duration , результат будет неправильным, потому что Duration представляет собой фиксированное количество времени, которое не принимает во внимание хронологию или часовые пояса:

Period period = new Period().withMonths(1);
LocalDateTime datePlusPeriod = localDateTime.plus(period);

Interval , как следует из названия, представляет интервал даты и времени между двумя фиксированными точками времени, представленными двумя объектами Instant :

Interval interval = new Interval(oneMinuteAgoInstant, instantNow);

Класс полезен, когда нам нужно проверить, перекрываются ли два интервала или вычислить разрыв между ними. Метод перекрытия() вернет перекрывающийся интервал или ноль , если они не перекрываются:

Instant startInterval1 = new Instant("2018-05-05T09:00:00.000");
Instant endInterval1 = new Instant("2018-05-05T11:00:00.000");
Interval interval1 = new Interval(startInterval1, endInterval1);

Instant startInterval2 = new Instant("2018-05-05T10:00:00.000");
Instant endInterval2 = new Instant("2018-05-05T11:00:00.000");
Interval interval2 = new Interval(startInterval2, endInterval2);

Interval overlappingInterval = interval1.overlap(interval2);

Разницу между интервалами можно вычислить с помощью метода gap() , а когда мы хотим узнать, равен ли конец интервала началу другого интервала, мы можем использовать метод abuts() :

assertTrue(interval1.abuts(new Interval(
new Instant("2018-05-05T11:00:00.000"),
new Instant("2018-05-05T13:00:00.000"))));

6.3. Операции с датой и временем

Некоторые из наиболее распространенных операций — сложение, вычитание и преобразование даты и времени. Библиотека предоставляет специальные методы для каждого из классов LocalDate , LocalTime , LocalDateTime и DateTime . Важно отметить, что эти классы являются неизменяемыми, поэтому каждый вызов метода будет создавать новый объект своего типа.

Возьмем LocalDateTime за текущий момент и попробуем изменить его значение:

LocalDateTime currentLocalDateTime = LocalDateTime.now();

Чтобы добавить дополнительный день к currentLocalDateTime , мы используем метод plusDays() :

LocalDateTime nextDayDateTime = currentLocalDateTime.plusDays(1);

Мы также можем использовать метод plus() , чтобы добавить период или продолжительность к нашему currentLocalDateTime:

Period oneMonth = new Period().withMonths(1);
LocalDateTime nextMonthDateTime = currentLocalDateTime.plus(oneMonth);

Методы аналогичны для других компонентов даты и времени, например, plusYears() для добавления дополнительных лет, plusSeconds() для добавления дополнительных секунд и так далее.

Чтобы вычесть день из нашего currentLocalDateTime , мы можем использовать метод minusDays() :

LocalDateTime previousDayLocalDateTime
= currentLocalDateTime.minusDays(1);

Кроме того, выполняя вычисления с датой и временем, мы также можем установить отдельные части даты или времени. Например, установить час равным 10 можно с помощью метода withHourOfDay() . Другие методы, которые начинаются с префикса «с» , могут использоваться для установки компонентов этой даты или времени:

LocalDateTime currentDateAtHour10 = currentLocalDateTime
.withHourOfDay(0)
.withMinuteOfHour(0)
.withSecondOfMinute(0)
.withMillisOfSecond(0);

Еще один важный аспект заключается в том, что мы можем преобразовать тип класса даты и времени в другой. Для этого мы можем использовать специальные методы, предоставляемые библиотекой:

  • toDateTime() — преобразует LocalDateTime в объект DateTime .
  • toLocalDate() — преобразует LocalDateTime в объект LocalDate.
  • toLocalTime() — преобразует LocalDateTime в объект LocalTime.
  • toDate() — преобразует LocalDateTime в объект даты Java.

7. Работа с часовыми поясами

Joda-Time позволяет нам легко работать с разными часовыми поясами и переключаться между ними. У нас есть абстрактный класс DateTimeZone , который используется для представления всех аспектов, касающихся часового пояса.

Часовой пояс по умолчанию, используемый Joda-Time, выбирается из системного свойства user.timezone Java. API библиотеки позволяет нам указать, индивидуально для каждого класса или расчета, какой часовой пояс следует использовать. Например, мы можем создать объект LocalDateTime.

Когда мы знаем, что будем использовать определенный часовой пояс во всем приложении, мы можем установить часовой пояс по умолчанию:

DateTimeZone.setDefault(DateTimeZone.UTC);

Отныне все операции с датой и временем, если не указано иное, будут представлены в часовом поясе UTC.

Чтобы увидеть все доступные часовые пояса, мы можем использовать метод getAvailableIDs():

DateTimeZone.getAvailableIDs()

Когда нам нужно представить дату или время в определенном часовом поясе, мы можем использовать любой из классов LocalTime , LocalDate , LocalDateTime , DateTime и указать в конструкторе объект DateTimeZone :

DateTime dateTimeInChicago
= new DateTime(DateTimeZone.forID("America/Chicago"));
DateTime dateTimeInBucharest
= new DateTime(DateTimeZone.forID("Europe/Bucharest"));
LocalDateTime localDateTimeInChicago
= new LocalDateTime(DateTimeZone.forID("America/Chicago"));

Кроме того, при преобразовании между этими классами мы можем указать желаемый часовой пояс. Метод toDateTime() принимает объект DateTimeZone , а toDate() принимает объект java.util.TimeZone:

DateTime convertedDateTime
= localDateTimeInChicago.toDateTime(DateTimeZone.forID("Europe/Bucharest"));
Date convertedDate
= localDateTimeInChicago.toDate(TimeZone.getTimeZone("Europe/Bucharest"));

8. Заключение

Joda-Time — это фантастическая библиотека, основная цель которой — исправить проблемы в JDK, связанные с операциями даты и времени. Вскоре она стала де-факто библиотекой для обработки даты и времени, а недавно основные концепции из нее были представлены в Java 8.

Важно отметить, что автор считает его «почти законченным проектом» и рекомендует перенести существующий код для использования реализации Java 8.

Исходный код статьи доступен на GitHub .