«1. Введение

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

Основные концепции, реализованные в этой библиотеке, были представлены в ядре 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 года.

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

Joda-Time также предлагает поддержку восьми календарных систем, в то время как Java предлагает только две: григорианскую — 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. Продолжительность — представляет продолжительность в миллисекундах между двумя точками времени. к отдельным компонентам объекта даты и времени, таким как годы, месяц, дни и т. д. Интервал — представляет временной интервал между двумя моментами

Другими важными функциями являются синтаксические анализаторы и средства форматирования даты. Их можно найти в пакете 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"));

«

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

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

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

    Для представления пользовательских даты и времени Joda-Time предоставляет нам несколько конструкторов. Мы можем указать следующие объекты:
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);

Instant объект Java Date строковое представление даты и времени с использованием частей даты и времени в формате ISO: год, месяц, день, час, минута, секунда, миллисекунда ~~ ~

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

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

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

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

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

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

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

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

Продолжительность представляет собой время в миллисекундах между двумя моментами времени или, в данном случае, это могут быть два момента времени. Мы будем использовать это, когда нам нужно добавить или вычесть определенное количество времени из другого 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 библиотека будет учитывать високосные годы.

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

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

Интервал, как следует из названия, представляет интервал даты и времени между двумя фиксированными точками времени, представленными двумя объектами 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(). Другие методы, начинающиеся с префикса with, могут использоваться для установки компонентов этой даты или времени:

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.