Статьи

Более глубокий взгляд на API даты и времени Java 8

В этом посте мы более подробно рассмотрим новый API Date / Time, который мы получаем в Java 8 (
JSR 310 ). Обратите внимание, что этот пост в основном основан на примерах кода, которые показывают новые функциональные возможности API. Я думаю, что примеры говорят сами за себя, поэтому я не тратил много времени на написание текста вокруг них 🙂

Давайте начнем!

Работа с объектами даты и времени

Все классы Java 8 Date / Time API находятся в пакете java.time. Первый класс, на который мы хотим посмотреть, это java.time.LocalDate. LocalDate представляет дату года-месяца-дня без времени. Мы начнем с создания новых экземпляров LocalDate:

01
02
03
04
05
06
07
08
09
10
11
// the current date
LocalDate currentDate = LocalDate.now();
 
// 2014-02-10
LocalDate tenthFeb2014 = LocalDate.of(2014, Month.FEBRUARY, 10);
 
// months values start at 1 (2014-08-01)
LocalDate firstAug2014 = LocalDate.of(201481);
 
// the 65th day of 2010 (2010-03-06)
LocalDate sixtyFifthDayOf2010 = LocalDate.ofYearDay(201065);

LocalTime и LocalDateTime — следующие классы, на которые мы смотрим. Оба работают аналогично LocalDate. LocalTime работает со временем (без дат), в то время как LocalDateTime объединяет дату и время в одном классе:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
LocalTime currentTime = LocalTime.now(); // current time
LocalTime midday = LocalTime.of(120); // 12:00
LocalTime afterMidday = LocalTime.of(133015); // 13:30:15
 
// 12345th second of day (03:25:45)
LocalTime fromSecondsOfDay = LocalTime.ofSecondOfDay(12345);
 
// dates with times, e.g. 2014-02-18 19:08:37.950
LocalDateTime currentDateTime = LocalDateTime.now();
 
// 2014-10-02 12:30
LocalDateTime secondAug2014 = LocalDateTime.of(20141021230);
 
// 2014-12-24 12:00
LocalDateTime christmas2014 = LocalDateTime.of(2014, Month.DECEMBER, 24120);

По умолчанию классы LocalDate / Time будут использовать системные часы в часовом поясе по умолчанию. Мы можем изменить это, предоставив часовой пояс или альтернативную реализацию Clock :

1
2
3
4
5
// current (local) time in Los Angeles
LocalTime currentTimeInLosAngeles = LocalTime.now(ZoneId.of("America/Los_Angeles"));
 
// current time in UTC time zone
LocalTime nowInUtc = LocalTime.now(Clock.systemUTC());

Из объектов LocalDate / Time мы можем получить всевозможную полезную информацию, которая может нам понадобиться. Несколько примеров:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
LocalDate date = LocalDate.of(2014215); // 2014-02-15
 
boolean isBefore = LocalDate.now().isBefore(date); // false
 
// information about the month
Month february = date.getMonth(); // FEBRUARY
int februaryIntValue = february.getValue(); // 2
int minLength = february.minLength(); // 28
int maxLength = february.maxLength(); // 29
Month firstMonthOfQuarter = february.firstMonthOfQuarter(); // JANUARY
 
// information about the year
int year = date.getYear(); // 2014
int dayOfYear = date.getDayOfYear(); // 46
int lengthOfYear = date.lengthOfYear(); // 365
boolean isLeapYear = date.isLeapYear(); // false
 
DayOfWeek dayOfWeek = date.getDayOfWeek();
int dayOfWeekIntValue = dayOfWeek.getValue(); // 6
String dayOfWeekName = dayOfWeek.name(); // SATURDAY
 
int dayOfMonth = date.getDayOfMonth(); // 15
LocalDateTime startOfDay = date.atStartOfDay(); // 2014-02-15 00:00
 
// time information
LocalTime time = LocalTime.of(1530); // 15:30:00
int hour = time.getHour(); // 15
int second = time.getSecond(); // 0
int minute = time.getMinute(); // 30
int secondOfDay = time.toSecondOfDay(); // 55800

Некоторая информация может быть получена без указания конкретной даты. Например, мы можем использовать класс Year, если нам нужна информация о конкретном году:

1
2
3
4
5
6
7
Year currentYear = Year.now();
Year twoThousand = Year.of(2000);
boolean isLeap = currentYear.isLeap(); // false
int length = currentYear.length(); // 365
 
// sixtyFourth day of 2014 (2014-03-05)
LocalDate date = Year.of(2014).atDay(64);

Мы можем использовать методы плюс и минус, чтобы сложить или вычесть определенное количество времени. Обратите внимание, что эти методы всегда возвращают новый экземпляр (классы даты / времени Java 8 являются неизменяемыми).

1
2
3
4
LocalDate tomorrow = LocalDate.now().plusDays(1);
 
// before 5 houres and 30 minutes
LocalDateTime dateTime = LocalDateTime.now().minusHours(5).minusMinutes(30);

TemporalAdjusters — еще один хороший способ манипулирования датами. TemporalAdjuster — это интерфейс с одним методом, который используется для отделения процесса корректировки от фактических объектов даты / времени. Доступ к набору общих TemporalAdjusters можно получить с помощью статических методов класса TemporalAdjusters .

1
2
3
4
5
6
7
LocalDate date = LocalDate.of(2014, Month.FEBRUARY, 25); // 2014-02-25
 
// first day of february 2014 (2014-02-01)
LocalDate firstDayOfMonth = date.with(TemporalAdjusters.firstDayOfMonth());
 
// last day of february 2014 (2014-02-28)
LocalDate lastDayOfMonth = date.with(TemporalAdjusters.lastDayOfMonth());

Статический импорт облегчает чтение:

01
02
03
04
05
06
07
08
09
10
11
12
import static java.time.temporal.TemporalAdjusters.*;
 
...
 
// last day of 2014 (2014-12-31)
LocalDate lastDayOfYear = date.with(lastDayOfYear());
 
// first day of next month (2014-03-01)
LocalDate firstDayOfNextMonth = date.with(firstDayOfNextMonth());
 
// next sunday (2014-03-02)
LocalDate nextSunday = date.with(next(DayOfWeek.SUNDAY));

Часовые пояса

Работа с часовыми поясами — еще одна важная тема, которая упрощается благодаря новому API. Классы LocalDate / Time, которые мы видели до сих пор, не содержат информацию о часовом поясе. Если мы хотим работать с датой / временем в определенном часовом поясе, мы можем использовать ZonedDateTime или OffsetDateTime :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
ZoneId losAngeles = ZoneId.of("America/Los_Angeles");
ZoneId berlin = ZoneId.of("Europe/Berlin");
 
// 2014-02-20 12:00
LocalDateTime dateTime = LocalDateTime.of(20140220120);
 
// 2014-02-20 12:00, Europe/Berlin (+01:00)
ZonedDateTime berlinDateTime = ZonedDateTime.of(dateTime, berlin);
 
// 2014-02-20 03:00, America/Los_Angeles (-08:00)
ZonedDateTime losAngelesDateTime = berlinDateTime.withZoneSameInstant(losAngeles);
 
int offsetInSeconds = losAngelesDateTime.getOffset().getTotalSeconds(); // -28800
 
// a collection of all available zones
Set<String> allZoneIds = ZoneId.getAvailableZoneIds();
 
// using offsets
LocalDateTime date = LocalDateTime.of(2013, Month.JULY, 20330);
ZoneOffset offset = ZoneOffset.of("+05:00");
 
// 2013-07-20 03:30 +05:00
OffsetDateTime plusFive = OffsetDateTime.of(date, offset);
 
// 2013-07-19 20:30 -02:00
OffsetDateTime minusTwo = plusFive.withOffsetSameInstant(ZoneOffset.ofHours(-2));

Timestamps

Такие классы, как LocalDate и ZonedDateTime обеспечивают своевременное представление людей. Однако часто нам нужно работать со временем, если смотреть с точки зрения машины. Для этого мы можем использовать класс Instant, который представляет временные метки. Мгновенное время отсчитывает время, начиная с первой секунды 1 января 1970 года (1970-01-01 00:00:00), также называемое EPOCH . Мгновенные значения могут быть отрицательными, если они произошли до эпохи. Они соответствуют стандарту ISO 8601 для представления даты и времени.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// current time
Instant now = Instant.now();
 
// from unix timestamp, 2010-01-01 12:00:00
Instant fromUnixTimestamp = Instant.ofEpochSecond(1262347200);
 
// same time in millis
Instant fromEpochMilli = Instant.ofEpochMilli(1262347200000l);
 
// parsing from ISO 8601
Instant fromIso8601 = Instant.parse("2010-01-01T12:00:00Z");
 
// toString() returns ISO 8601 format, e.g. 2014-02-15T01:02:03Z
String toIso8601 = now.toString();
 
// as unix timestamp
long toUnixTimestamp = now.getEpochSecond();
 
// in millis
long toEpochMillis = now.toEpochMilli();
 
// plus/minus methods are available too
Instant nowPlusTenSeconds = now.plusSeconds(10);

Периоды и продолжительность

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// periods
 
LocalDate firstDate = LocalDate.of(2010517); // 2010-05-17
LocalDate secondDate = LocalDate.of(201537); // 2015-03-07
Period period = Period.between(firstDate, secondDate);
 
int days = period.getDays(); // 18
int months = period.getMonths(); // 9
int years = period.getYears(); // 4
boolean isNegative = period.isNegative(); // false
 
Period twoMonthsAndFiveDays = Period.ofMonths(2).plusDays(5);
LocalDate sixthOfJanuary = LocalDate.of(201416);
 
// add two months and five days to 2014-01-06, result is 2014-03-11
LocalDate eleventhOfMarch = sixthOfJanuary.plus(twoMonthsAndFiveDays);
 
// durations
 
Instant firstInstant= Instant.ofEpochSecond( 1294881180 ); // 2011-01-13 01:13
Instant secondInstant = Instant.ofEpochSecond(1294708260); // 2011-01-11 01:11
 
Duration between = Duration.between(firstInstant, secondInstant);
 
// negative because firstInstant is after secondInstant (-172920)
long seconds = between.getSeconds();
 
// get absolute result in minutes (2882)
long absoluteResult = between.abs().toMinutes();
 
// two hours in seconds (7200)
long twoHoursInSeconds = Duration.ofHours(2).getSeconds();

Форматирование и анализ

Форматирование и анализ — еще одна важная тема при работе с датами и временем. В Java 8 это можно сделать с помощью методов format () и parse ():

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
// 2014-04-01 10:45
LocalDateTime dateTime = LocalDateTime.of(2014, Month.APRIL, 11045);
 
// format as basic ISO date format (20140220)
String asBasicIsoDate = dateTime.format(DateTimeFormatter.BASIC_ISO_DATE);
 
// format as ISO week date (2014-W08-4)
String asIsoWeekDate = dateTime.format(DateTimeFormatter.ISO_WEEK_DATE);
 
// format ISO date time (2014-02-20T20:04:05.867)
String asIsoDateTime = dateTime.format(DateTimeFormatter.ISO_DATE_TIME);
 
// using a custom pattern (01/04/2014)
String asCustomPattern = dateTime.format(DateTimeFormatter.ofPattern("dd/MM/yyyy"));
 
// french date formatting (1. avril 2014)
String frenchDate = dateTime.format(DateTimeFormatter.ofPattern("d. MMMM yyyy"new Locale("fr")));
 
// using short german date/time formatting (01.04.14 10:45)
DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT)
    .withLocale(new Locale("de"));
String germanDateTime = dateTime.format(formatter);
 
// parsing date strings
LocalDate fromIsoDate = LocalDate.parse("2014-01-20");
LocalDate fromIsoWeekDate = LocalDate.parse("2014-W14-2", DateTimeFormatter.ISO_WEEK_DATE);
LocalDate fromCustomPattern = LocalDate.parse("20.01.2014", DateTimeFormatter.ofPattern("dd.MM.yyyy"));

преобразование

Конечно, у нас не всегда есть объекты нужного нам типа. Поэтому нам нужна опция для преобразования различных объектов, относящихся к дате / времени, между собой. В следующих примерах показаны некоторые из возможных вариантов преобразования:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
// LocalDate/LocalTime <-> LocalDateTime
LocalDate date = LocalDate.now();
LocalTime time = LocalTime.now();
LocalDateTime dateTimeFromDateAndTime = LocalDateTime.of(date, time);
LocalDate dateFromDateTime = LocalDateTime.now().toLocalDate();
LocalTime timeFromDateTime = LocalDateTime.now().toLocalTime();
 
// Instant <-> LocalDateTime
Instant instant = Instant.now();
LocalDateTime dateTimeFromInstant = LocalDateTime.ofInstant(instant, ZoneId.of("America/Los_Angeles"));
Instant instantFromDateTime = LocalDateTime.now().toInstant(ZoneOffset.ofHours(-2));
 
// convert old date/calendar/timezone classes
Instant instantFromDate = new Date().toInstant();
Instant instantFromCalendar = Calendar.getInstance().toInstant();
ZoneId zoneId = TimeZone.getDefault().toZoneId();
ZonedDateTime zonedDateTimeFromGregorianCalendar = new GregorianCalendar().toZonedDateTime();
 
// convert to old classes
Date dateFromInstant = Date.from(Instant.now());
TimeZone timeZone = TimeZone.getTimeZone(ZoneId.of("America/Los_Angeles"));
GregorianCalendar gregorianCalendar = GregorianCalendar.from(ZonedDateTime.now());

Вывод

С Java 8 мы получаем очень богатый API для работы с датой и временем, расположенный в пакете java.time. API может полностью заменить старые классы, такие как java.util.Date или java.util.Calendar, новыми, более гибкими классами. Благодаря главным образом неизменяемым классам новый API помогает в построении многопоточных систем.

  • Источник примеров можно найти на GitHub .