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

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

1. Вступление

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

The central concepts implemented in this library were introduced in the JDK core with the release of the Java 8 version. Новый API даты и времени находится в пакетеjava.time (JSR-310). Обзор этих функций можно найти в этомarticle.

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

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

API даты / времени, предшествующий Java 8, представлял множество проблем проектирования.

Среди проблем - то, что классыDate иSimpleDateFormatter не являются потокобезопасными. Чтобы решить эту проблему,Joda-Time uses immutable classes for handling date and time.

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

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

Joda-Time также предлагаетsupport for eight calendar systems, в то время как Java предлагает только 2: григорианский -java.util.GregorianCalendar и японский -java.util.JapaneseImperialCalendar.

 3. Настроить

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


    joda-time
    joda-time
    2.10

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

Joda-Time моделируетconcept of date and time, используя классы из пакетаorg.joda.time.

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

  • LocalDate - представляет дату без времени

  • LocalTime - представляет время без часового пояса

  • LocalDateTime - представляет дату и время без часового пояса

  • Instant - представляет собой точный момент времени в миллисекундах из эпохи Java 1970-01-01T00: 00: 00Z

  • Duration - представляет собой продолжительность в миллисекундах между двумя точками времени

  • Period - аналогDuration, но разрешающий доступ к отдельным компонентам объекта даты и времени, таким как годы, месяц, дни и т. д.

  • Interval - представляет интервал времени между двумя моментами

Другими важными характеристиками являютсяdate parsers and formatters. Их можно найти в пакетеorg.joda.time.format.

Конкретные классыcalendar system and time zone можно найти в пакетахorg.joda.time.chrono and org.joda.time.tz.

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

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

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

The current date, without time information, можно получить с помощью методаnow() из the LocalDate class:

LocalDate currentDate = LocalDate.now();

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

LocalTime currentTime = LocalTime.now();

Чтобы получитьrepresentation of the current date and time without considering the time zone, we can use LocalDateTime:

LocalDateTime currentDateAndTime = LocalDateTime.now();

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

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

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

All the above methods have an overloaded method which accepts a DateTimeZone object, чтобы помочь нам представить дату или время в указанном часовом поясе:

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

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

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

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

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

  • anInstant

  • объект JavaDate

  • представление даты и времениString в формате 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);

Другой способ определить дату и время - это проанализировать заданное представление даты и времениString в формате 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

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);

Конструкторы принимаютString, представляющие дату и время в формате ISO, значение JavaDate илиlong, представляющее количество миллисекунд с 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));

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

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 can be converted to a DateTime object or event a 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,Period иInterval

Duration представляет время в миллисекундах между двумя моментами времени, или в этом случае это может быть дваInstants. We’ll use this when we need to add or subtract a specific amount of time to or from another Instant without considering chronology and time zones:

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 is defined in terms of its date and time components (years, months, hours, etc.) and doesn’t represent an exact number of milliseconds. При использованииPeriod вычислений даты и времениwill consider the time zone and daylight saving.

Например, добавление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);

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

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(), чтобы добавитьPeriod илиDuration к нашему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 в объект JavaDate

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

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

The default time zone used by Joda-Time is selected from the user.timezone Java system property. 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, связанные с датой и временем. Вскоре она стала библиотекойde facto для обработки даты и времени, а недавно основные концепции из нее были представлены в Java 8.

Важно отметить, что автор считает это“to be a largely finished project” и рекомендует перенести существующий код на использование реализации Java 8.

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