- Overview
- Code Examples
- Get current date and time in the local timezone
- ZonedDateTime.now()
- Get current date and time in a different timezone
- ZonedDateTime.now(zoneId)
- TimeZone Id Values
- Create ZonedDateTime from a String
- ZonedDateTime.parse(dateString)
- Convert LocalDateTime to ZonedDateTime
- ZonedDateTime.of(ldt, zoneId)
- atZone(zoneId)
- Compare ZonedDateTime objects
- isBefore(zdt)
- isAfter(zdt)
- isEqual(zdt)
- Find the time difference between ZonedDateTime objects
- Create ZonedDateTime with new timezone from existing ZonedDateTime object
- withZoneSameInstant(zoneId)
- Unit Testing Considerations
- java.time.Clock
- Unit Tests
- Java 8. Руководство. 2 часть.
- Аннотации
- Это все
Overview
The LocalDateTime class introduced in Java 8 stores the date and time but not the timezone. So we need something else when developing applications that need to be aware of different timezones.
Fortunately, we can use the ZonedDateTime class to represent dates and times with timezone information. The class contains static methods that allow us to perform date conversions and calculations easily.
Code Examples
ZonedDateTime objects are immutable so the static methods all return a new instance.
Get current date and time in the local timezone
ZonedDateTime.now()
ZonedDateTime now = ZonedDateTime.now(); // 2018-05-02T15:45:20.981-04:00[America/New_York]
Get current date and time in a different timezone
ZonedDateTime.now(zoneId)
ZonedDateTime nowInParis = ZonedDateTime.now(ZoneId.of("Europe/Paris")); // 2018-05-02T21:45:20.982+02:00[Europe/Paris]
TimeZone Id Values
The list of TimeZone id values is documented well here.
We can also retrieve them using a method:
Set zoneIds = ZoneId.getAvailableZoneIds();
Always use timezone values in the Region/City format ( America/New_York ) and avoid the short abbreviations ( EST ) because they are ambiguous and non-standard.
Create ZonedDateTime from a String
ZonedDateTime.parse(dateString)
ZonedDateTime departureTime = ZonedDateTime.parse("2018-07-01T10:00:00Z[America/New_York]"); ZonedDateTime arrivalTime = ZonedDateTime.parse("2018-07-01T22:00:00Z[Europe/London]");
Convert LocalDateTime to ZonedDateTime
We can convert a LocalDateTime to ZonedDateTime a couple different ways.
LocalDateTime ldt = LocalDateTime.parse("2018-07-01T08:00"); ZoneId zoneId = ZoneId.of("Europe/Paris");
ZonedDateTime.of(ldt, zoneId)
ZonedDateTime zdt = ZonedDateTime.of(ldt, zoneId); // 2018-07-01T08:00+02:00[Europe/Paris]
atZone(zoneId)
ZonedDateTime zdt = ldt.atZone(zoneId); // 2018-07-01T08:00+02:00[Europe/Paris]
Note that this doesn’t apply any timezone conversion to our LocalDateTime. It simply adds the timezone to whatever date and time was stored in the source object.
Compare ZonedDateTime objects
isBefore(zdt)
boolean departureBeforeArrival = departureTime.isBefore(arrivalTime);
isAfter(zdt)
boolean arrivalAfterDeparture = arrivalTime.isAfter(departureTime);
isEqual(zdt)
This compares the actual dates and times and not the object references.
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
Find the time difference between ZonedDateTime objects
ChronoUnit in java.time.temporal allows us to calculate the time difference between ZonedDateTime objects:
long flightTimeInHours = ChronoUnit.HOURS.between(departureTime, arrivalTime); // 7 long flightTimeInMinutes = ChronoUnit.MINUTES.between(departureTime, arrivalTime); // 420
Create ZonedDateTime with new timezone from existing ZonedDateTime object
Given a ZonedDateTime object in timezone A, we can create another ZonedDateTime object in timezone B which represents the same point in time.
withZoneSameInstant(zoneId)
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]
Unit Testing Considerations
Suppose we need a method that determines if we can issue a boarding pass to an airline passenger based on some business logic.
It should return true when the departure time of the outbound OR return flight is less than 24 hours away.
We could write something like this:
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 = MIN_HOUR_DIFF_BOARDING_PASS && diffInHours return false; > >
This works fine but is difficult to unit test because of line 14. We cannot mock ZonedDateTime.now() because it’s a static method.
java.time.Clock
The now() methods in java.time all accept a Clock argument which we can use to refactor our code so it’s testable.
Here’s the testable version of the class:
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 = MIN_HOUR_DIFF_BOARDING_PASS && diffInHours return false; > >
On line 12, we added a single argument constructor which accepts a Clock object. We can then use the clock on line 21.
In the calling code, we’d pass Clock.systemDefaultZone() to the the constructor to use the current system time.
In our tests, we’ll create a fixed time clock using Clock.fixed() and pass that.
Unit Tests
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)); >>
Java 8. Руководство. 2 часть.
Java 8 содержит совершенно новые API даты и времени в пакете java.time. Новый Date API сравним с библиотекой Joda-Time, однако это не то же самое. Следующие примеры охватят наиболее важные части нового API.
Clock
Clock дает доступ к текущим дате и времени. Clocks знают о часовых поясах и потому могут быть использованы вместо System.currentTimeMillis() для возврата текущего времени в милисекундах. Такую точность во времени также представляет класс Instant. Instants могут быть использованы для создания унаследованных объектов java.util.Date. Clock clock = Clock.systemDefaultZone(); long millis = clock.millis(); Instant instant = clock.instant(); Date legacyDate = Date.from(instant); // legacy java.util.Date
Timezones
Timezones представлены абстрактным классом ZoneId. Они легко могут быть доступны с помощью статистических методов-фабрик. Timezones определяют смещения, которые важны для преобразования между мгновенными и локальными датой и временем. System.out.println(ZoneId.getAvailableZoneIds()); // prints all available timezone ids ZoneId zone1 = ZoneId.of(«Europe/Berlin»); ZoneId zone2 = ZoneId.of(«Brazil/East»); System.out.println(zone1.getRules()); System.out.println(zone2.getRules()); // ZoneRules[currentStandardOffset=+01:00] // ZoneRules[currentStandardOffset=-03:00]
LocalTime
LocalTime отображает время без временной зоны, например 17:30:15. В следующем примере создаются два объекта локального времени для временных зон, определенных выше. Затем мы сравниваем эти два объекта и вычисляем между ними разность в часах и минутах. LocalTime now1 = LocalTime.now(zone1); LocalTime now2 = LocalTime.now(zone2); System.out.println(now1.isBefore(now2)); // false long hoursBetween = ChronoUnit.HOURS.between(now1, now2); long minutesBetween = ChronoUnit.MINUTES.between(now1, now2); System.out.println(hoursBetween); // -3 System.out.println(minutesBetween); // -239 LocalTime поставляется с различными методами-фабриками для упрощения создания экземпляров, включая парсинг строк. LocalTime late = LocalTime.of(23, 59, 59); System.out.println(late); // 23:59:59 DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedTime(FormatStyle.SHORT) .withLocale(Locale.GERMAN); LocalTime leetTime = LocalTime.parse(«13:37», germanFormatter); System.out.println(leetTime); // 13:37
LocalDate
LocalDate отображает конкретную дату, например 2014-03-11. Экземпляры являются неизменными и работают аналогично LocalTime. Пример демонстрирует как расчитать новую дату, путем добавления или вычитания дней, месяцев или лет. Имейте в виду, что каждая операция над объектом, возвращает новый объект. LocalDate today = LocalDate.now(); LocalDate tomorrow = today.plus(1, ChronoUnit.DAYS); LocalDate yesterday = tomorrow.minusDays(2); LocalDate independenceDay = LocalDate.of(2014, Month.JULY, 4); DayOfWeek dayOfWeek = independenceDay.getDayOfWeek(); System.out.println(dayOfWeek); // FRIDAY Парсинг LocalDate из строки такой же простой как и парсинг LocalTime: DateTimeFormatter germanFormatter = DateTimeFormatter .ofLocalizedDate(FormatStyle.MEDIUM) .withLocale(Locale.GERMAN); LocalDate xmas = LocalDate.parse(«24.12.2014», germanFormatter); System.out.println(xmas); // 2014-12-24
LocalDateTime
LocalDateTime отображает дату-время. Это комбинация даты и времени приведенных выше, в одном экземпляре. Экземпляры LocalDateTime неизменны и работают аналогично LocalTime и LocalDate. Мы можем использовать методы для извлечения нужных нам значений свойств экземпляров: LocalDateTime sylvester = LocalDateTime.of(2014, Month.DECEMBER, 31, 23, 59, 59); DayOfWeek dayOfWeek = sylvester.getDayOfWeek(); System.out.println(dayOfWeek); // WEDNESDAY Month month = sylvester.getMonth(); System.out.println(month); // DECEMBER long minuteOfDay = sylvester.getLong(ChronoField.MINUTE_OF_DAY); System.out.println(minuteOfDay); // 1439 Вместе с дополнительной информацией о временной зоне, экземпляр может быть конвертирован в instant. Instants могут быть легко конвертированы в старые типы наследуемые от java.util.Date. Instant instant = sylvester .atZone(ZoneId.systemDefault()) .toInstant(); Date legacyDate = Date.from(instant); System.out.println(legacyDate); // Wed Dec 31 23:59:59 CET 2014 Форматирование даты-время работает также как и форматирование даты или времени. Вместо использования заранее определенных форматов даты, мы можем использовать форматы определенные вручную. DateTimeFormatter formatter = DateTimeFormatter .ofPattern(«MMM dd, yyyy — HH:mm»); LocalDateTime parsed = LocalDateTime.parse(«Nov 03, 2014 — 07:13», formatter); String string = formatter.format(parsed); System.out.println(string); // Nov 03, 2014 — 07:13 В отличии от java.text.NumberFormat, новый DateTimeFormatter неизменяемый и потокобезопасен. Для большей информации о синтаксисе написания форматов, читать здесь.
Аннотации
Аннотации в Java 8 могут быть повторяемыми. Давайте посмотрим на пример, чтобы представить как это. Для начала мы определим обертку для аннотаций, которая хранит массив действующих аннотаций: @interface Hints < Hint[] value(); >@Repeatable(Hints.class) @interface Hint < String value(); >Java 8 enables us to use multiple annotations of the same type by declaring the annotation @Repeatable. Variant 1: Using the container annotation (old school) (Java 8 позволяет нам использовать множественную аннотацию того же типа, объявляя аннотацию @Repeatable.) Вариант 1: Использование контейнера аннотаций(старая возможность) @Hints() class Person <> Вариант 2: использование повторяемых аннотаций (новая возможность) @Hint(«hint1») @Hint(«hint2») class Person <> Используя вариант два, компилятор java неявно устанавливает аннотацию @Hint. Это важно для чтения информации об аннотации посредством отражения. Hint hint = Person.class.getAnnotation(Hint.class); System.out.println(hint); // null Hints hints1 = Person.class.getAnnotation(Hints.class); System.out.println(hints1.value().length); // 2 Hint[] hints2 = Person.class.getAnnotationsByType(Hint.class); System.out.println(hints2.length); // 2 Хотя мы не объявляли аннотацию @Hints для класса Person, ее можно прочитать с помощью метода getAnnotation(Hints.class). Однако более удобным методом является getAnnotationsByType, который предоставляет доступ ко всем аннотированным с помощью аннотации @Hint. К тому ж использование аннотаций в Java 8 расширяется до двух целей: @Target() @interface MyAnnotation <>
Это все
Мое руководство по программированию в Java 8 завершилось. Если вы хотите узнать больше о всех новых классих и возможностях API JDK 8, просто прочтите мою следующую статью. Это поможет вам разобраться во всех новых классах и скрытых возможностях JDK 8, таким как Arrays.parallelSort, StampedLock и CompletableFuture.