Рубрики
Без рубрики

Добавьте часовой пояс в LocalDateTime с помощью ZonedDateTime в Java 8

Используйте класс ZonedDateTime для хранения дат и времени с указанием часового пояса. Помеченный java.

Первоначально опубликовано в моем блоге по адресу www.codebyamir.com

Класс LocalDateTime , представленный в Java 8, хранит дату и время, но не часовой пояс. Поэтому нам нужно что-то еще при разработке приложений, которые должны знать о разных часовых поясах.

К счастью, мы можем использовать класс ZonedDateTime для представления дат и времени с информацией о часовом поясе. Класс содержит статические методы, которые позволяют нам легко выполнять преобразования дат и вычисления.

Объекты ZonedDateTime неизменяемы, поэтому все статические методы возвращают новый экземпляр.

Получить текущую дату и время в местном часовом поясе

Дата в зоне.сейчас()

ZonedDateTime now = ZonedDateTime.now();
// 2018-05-02T15:45:20.981-04:00[America/New_York]

Получить текущую дату и время в другом часовом поясе

Дата и время зоны.сейчас(идентификатор зоны)

ZonedDateTime nowInParis = ZonedDateTime.now(ZoneId.of("Europe/Paris"));
// 2018-05-02T21:45:20.982+02:00[Europe/Paris]

Значения Идентификатора часового пояса

Список значений идентификатора часового пояса хорошо задокументирован здесь .

Мы также можем получить их с помощью метода:

Set zoneIds = ZoneId.getAvailableZoneIds();

Всегда используйте значения часового пояса в формате Регион/Город (Америка/Нью-Йорк) и избегайте коротких сокращений (EST), поскольку они неоднозначны и нестандартны.

Создайте ZonedDateTime из строки

ZonedDateTime.синтаксический анализ(строка даты)

ZonedDateTime departureTime = ZonedDateTime.parse("2018-07-01T10:00:00Z[America/New_York]");
ZonedDateTime arrivalTime = ZonedDateTime.parse("2018-07-01T22:00:00Z[Europe/London]");

Преобразовать локальное время в зональное время

Мы можем преобразовать LocalDateTime в ZonedDateTime несколькими различными способами.

LocalDateTime ldt = LocalDateTime.parse("2018-07-01T08:00");
ZoneId zoneId = ZoneId.of("Europe/Paris");

Дата и время зоны.из(ldt, идентификатор зоны)

ZonedDateTime zdt = ZonedDateTime.of(ldt, zoneId); 
// 2018-07-01T08:00+02:00[Europe/Paris]

atZone(идентификатор зоны)

ZonedDateTime zdt = ldt.atZone(zoneId); 
// 2018-07-01T08:00+02:00[Europe/Paris]

Обратите внимание, что это не относится к преобразованию часового пояса в наш LocalDateTime . Он просто добавляет часовой пояс к любой дате и времени, которые были сохранены в исходном объекте.

Сравнение объектов ZonedDateTime

находится перед(здт)

boolean departureBeforeArrival = departureTime.isBefore(arrivalTime);

находится после(zdt)

boolean arrivalAfterDeparture = arrivalTime.isAfter(departureTime);

Равнозначный(zdt)

При этом сравниваются фактические даты и время, а не ссылки на объекты.

LocalDateTime ldt = LocalDateTime.parse("2018-07-01T08:00");

ZonedDateTime zdtParis = ZonedDateTime.of(ldt, ZoneId.of("Europe/Paris"));
// 2018-07-01T08:00+02:00[Europe/Paris]

ZonedDateTime zdtNewYork = ZonedDateTime.of(ldt, ZoneId.of("America/New_York"));
// 2018-07-01T08:00-04:00[America/New_York]

boolean equal = zdtParis.isEqual(zdtNewYork); // false

Найдите разницу во времени между объектами ZonedDateTime

ChronoUnit в java.time.temporal позволяет нам вычислять разницу во времени между объектами ZonedDateTime :

long flightTimeInHours = ChronoUnit.HOURS.between(departureTime, arrivalTime); // 7
long flightTimeInMinutes = ChronoUnit.MINUTES.between(departureTime, arrivalTime); // 420

Создайте ZonedDateTime с новым часовым поясом из существующего объекта ZonedDateTime

Учитывая ZonedDateTime объект в часовом поясе A, мы можем создать другой ZonedDateTime объект в часовом поясе B, который представляет один и тот же момент времени.

С постоянными зонами(идентификатор зоны)

ZonedDateTime nowInLocalTimeZone = ZonedDateTime.now();
// 2018-05-02T20:10:27.896-04:00[America/New_York]

ZonedDateTime nowInParis = nowInLocalTimeZone.withZoneSameInstant(ZoneId.of("Europe/Paris"));
// 2018-05-03T02:10:27.896+02:00[Europe/Paris]

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

Он должен возвращать значение true, если до времени вылета вылетающего ИЛИ обратного рейса остается менее 24 часов.

Мы могли бы написать что-то вроде этого:

package com.example.demo;

import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class BoardingPassService {
  private static final int MIN_HOUR_DIFF_BOARDING_PASS = -24;

  public boolean canIssueBoardingPass(Ticket ticket) {
    if (ticket == null || ticket.getOutboundFlight() == null || ticket.getReturnFlight() == null) {
      return false;
    }

    ZonedDateTime now = ZonedDateTime.now();    
    ZonedDateTime outboundDepartureTime = ticket.getOutboundFlight().getDepartureTime();
    ZonedDateTime returnDepartureTime = ticket.getReturnFlight().getDepartureTime();

    long diffInHours = ChronoUnit.HOURS.between(outboundDepartureTime, now);

    boolean outboundDepartureInAllowableRange = (diffInHours >= MIN_HOUR_DIFF_BOARDING_PASS && diffInHours <= 0);

    diffInHours = ChronoUnit.HOURS.between(returnDepartureTime, now);

    boolean returnDepartureInAllowableRange = (diffInHours >= MIN_HOUR_DIFF_BOARDING_PASS && diffInHours <= 0);

    if (outboundDepartureInAllowableRange || returnDepartureInAllowableRange) {
      return true;
    }

    return false;
  }
}

Это прекрасно работает, но трудно поддается модульному тестированию из-за ZonedDateTime.now() . Мы не можем издеваться над ним, потому что это статический метод.

java.время. Часы

Новые методы() в java.time все принимают аргумент Clock , который мы можем использовать для рефакторинга нашего кода, чтобы его можно было проверить.

Вот тестируемая версия класса:

package com.example.demo;

import java.time.Clock;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;

public class BoardingPassService {

  private final Clock clock; 
  private static final int MIN_HOUR_DIFF_BOARDING_PASS = -24;

  public BoardingPassService(Clock clock) {
    this.clock = clock;
  }

  public boolean canIssueBoardingPass(Ticket ticket) {
    if (ticket == null || ticket.getOutboundFlight() == null || ticket.getReturnFlight() == null) {
      return false;
    }

    ZonedDateTime now = ZonedDateTime.now(clock);   
    ZonedDateTime outboundDepartureTime = ticket.getOutboundFlight().getDepartureTime();
    ZonedDateTime returnDepartureTime = ticket.getReturnFlight().getDepartureTime();

    long diffInHours = ChronoUnit.HOURS.between(outboundDepartureTime, now);

    boolean outboundDepartureInAllowableRange = (diffInHours >= MIN_HOUR_DIFF_BOARDING_PASS && diffInHours <= 0);

    diffInHours = ChronoUnit.HOURS.between(returnDepartureTime, now);

    boolean returnDepartureInAllowableRange = (diffInHours >= MIN_HOUR_DIFF_BOARDING_PASS && diffInHours <= 0);

    if (outboundDepartureInAllowableRange || returnDepartureInAllowableRange) {
      return true;
    }

    return false;
  }
}

В строке 12 мы добавили конструктор с одним аргументом, который принимает объект Clock . Затем мы можем использовать часы на линии 21.

В вызывающем коде мы бы передали Clock.systemDefaultZone() конструктору, чтобы использовать текущее системное время.

В наших тестах мы создадим часы с фиксированным временем, используя Clock.fixed() и передадим их.

Модульные тесты

package com.example.demo;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import java.time.Clock;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;

import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

public class BoardingPassServiceTest {
  private Ticket ticket;
  private BoardingPassService boardingPassService;

  private final LocalDateTime REFERENCE_DATE_TIME = LocalDateTime.of(2018, 7, 1, 10, 0); // 2018-07-01 at 10:00am
  private final ZoneId defaultZone = ZoneId.systemDefault();
  private final Clock FIXED_CLOCK = Clock.fixed(REFERENCE_DATE_TIME.atZone(defaultZone).toInstant(), defaultZone);

  @BeforeEach 
  public void setUp() {
    ticket = new Ticket();
    boardingPassService = new BoardingPassService(FIXED_CLOCK);
  }

  @Test
  public void testCanIssueBoardingPass_Null() {
    assertFalse(boardingPassService.canIssueBoardingPass(null));
  }

  @Test
  public void testCanIssueBoardingPass_OutboundFlightWithinTimeRange() {
    Flight outboundFlight = new Flight();
    outboundFlight.setDepartureTime(ZonedDateTime.parse("2018-07-02T07:30:00Z[America/Chicago]"));

    Flight returnFlight = new Flight();
    returnFlight.setDepartureTime(ZonedDateTime.parse("2018-07-09T11:30:00Z[America/New_York]"));

    ticket.setOutboundFlight(outboundFlight);
    ticket.setReturnFlight(returnFlight);

    assertTrue(boardingPassService.canIssueBoardingPass(ticket));
  }

  @Test
  public void testCanIssueBoardingPass_ReturnFlightWithinTimeRange() {
    Flight outboundFlight = new Flight();
    outboundFlight.setDepartureTime(ZonedDateTime.parse("2018-06-20T07:30:00Z[America/Chicago]"));

    Flight returnFlight = new Flight();
    returnFlight.setDepartureTime(ZonedDateTime.parse("2018-07-02T09:30:00Z[America/New_York]"));

    ticket.setOutboundFlight(outboundFlight);
    ticket.setReturnFlight(returnFlight);

    assertTrue(boardingPassService.canIssueBoardingPass(ticket));
  }

  @Test
  public void testCanIssueBoardingPass_NoFlightWithinTimeRange() {
    Flight outboundFlight = new Flight();
    outboundFlight.setDepartureTime(ZonedDateTime.parse("2018-09-01T07:30:00Z[America/Chicago]"));

    Flight returnFlight = new Flight();
    returnFlight.setDepartureTime(ZonedDateTime.parse("2018-09-08T11:30:00Z[America/New_York]"));

    ticket.setOutboundFlight(outboundFlight);
    ticket.setReturnFlight(returnFlight);

    assertFalse(boardingPassService.canIssueBoardingPass(ticket));
  }
}

Оригинал: “https://dev.to/codebyamir/add-a-timezone-to-localdatetime-with-zoneddatetime-in-java-8-608”