Java 8 Дата – LocalDate, LocalDateTime, Instant

API для работы с датой и временем – одна из крупнейших особенностей выпуска Java 8. У Java с самого начала не было последовательного подхода к работе с датой и временем, и API для работы с датой и временем в Java 8 – долгожданное дополнение к основным API Java.

Зачем нам нужен новый API для работы с датой и временем в Java?

Прежде чем мы начнем рассматривать API для работы с датой и временем в Java 8, давайте посмотрим, зачем нам нужно новое API. У существующих классов для работы с датой и временем в Java есть несколько проблем, некоторые из них следующие:

  • Классы для работы с датой и временем в Java не определены последовательно: у нас есть класс Date как в пакете java.util, так и в пакете java.sql. Кроме того, классы для форматирования и разбора определены в пакете java.text.
  • java.util.Date содержит как дату, так и время, в то время как java.sql.Date содержит только значение даты. Наличие этого в пакете java.sql не имеет смысла. Кроме того, оба класса имеют одно и то же имя, что само по себе является очень плохим дизайном.
  • Не существует четко определенных классов для работы с временем, метками времени, форматированием и разбором. У нас есть абстрактный класс java.text.DateFormat для разбора и форматирования. Обычно используется класс SimpleDateFormat для разбора и форматирования.
  • Все классы Date изменяемы, поэтому они не являются потокобезопасными. Это одна из крупнейших проблем с классами Date и Calendar в Java.
  • Класс Date не предоставляет интернационализацию, нет поддержки часового пояса. Поэтому были введены классы java.util.Calendar и java.util.TimeZone, но у них также есть все перечисленные выше проблемы.

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

Принципы проектирования даты и времени в Java 8

API даты и времени в Java 8 – это реализация JSR-310. Он разработан для преодоления всех недостатков устаревших реализаций даты и времени. Некоторые из принципов проектирования нового API даты и времени:

  1. Неизменяемость: Все классы в новом API даты и времени являются неизменяемыми и подходят для многопоточных сред.

  2. Разделение забот: Новое API четко разделяет человекочитаемую дату и время и машинное время (Unix-метка времени). Оно определяет отдельные классы для Даты, Времени, ДатаВремени, Метки времени, Часового пояса и т.д.

  3. Ясность: Методы четко определены и выполняют одно и то же действие во всех классах. Например, для получения текущего экземпляра у нас есть метод now(). Во всех этих классах определены методы format() и parse() вместо того, чтобы иметь отдельный класс для них.

    Все классы используют Шаблон Фабрика и Шаблон Стратегия для лучшей обработки. После использования методов в одном из классов работа с другими классами не будет сложной.

  4. Операции утилиты: Все новые классы API даты и времени поставляются с методами для выполнения общих задач, таких как плюс, минус, форматирование, разбор, получение отдельной части в дате/времени и т. д.

  5. Расширяемость: Новый API даты и времени работает на календарной системе ISO-8601, но мы также можем использовать его с другими не ISO-календарями.

Пакеты API даты и времени

API даты и времени Java 8 состоит из следующих пакетов.

  1. java.time: Это базовый пакет нового API даты и времени Java. Все основные базовые классы являются частью этого пакета, такие как LocalDate, LocalTime, LocalDateTime, Instant, Period, Duration и т. д. Все эти классы являются неизменяемыми и потокобезопасными. В большинстве случаев эти классы будут достаточны для обработки общих требований.
  2. java.time.chrono: Этот пакет определяет общие API для календарных систем, не соответствующих ISO. Мы можем расширить класс AbstractChronology для создания собственной календарной системы.
  3. java.time.format: Этот пакет содержит классы, используемые для форматирования и разбора объектов даты и времени. В большинстве случаев мы не будем использовать их напрямую, потому что основные классы в пакете java.time предоставляют методы форматирования и разбора.
  4. java.time.temporal: Этот пакет содержит временные объекты, и мы можем использовать его для определения конкретных дат или временных точек, связанных с объектами даты/времени. Например, мы можем использовать их, чтобы определить первый или последний день месяца. Вы можете легко определить эти методы, потому что они всегда имеют формат “withXXX”.
  5. Пакет java.time.zone: Этот пакет содержит классы для поддержки различных часовых поясов и их правил.

Примеры классов Java 8 Date Time API

Мы рассмотрели большую часть важных частей Java Date Time API. Пора теперь рассмотреть наиболее важные классы Date Time API с примерами.

1. LocalDate

LocalDate – это неизменяемый класс, представляющий дату с форматом по умолчанию yyyy-MM-dd. Мы можем использовать метод now() для получения текущей даты. Мы также можем предоставить аргументы для года, месяца и дня для создания экземпляра LocalDate.

Этот класс предоставляет перегруженный метод для now(), в котором мы можем передать ZoneId для получения дат в определенном часовом поясе. Этот класс предоставляет ту же функциональность, что и java.sql.Date.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.Month;
import java.time.ZoneId;

/**
 * Примеры LocalDate
 * @author pankaj
 *
 */
public class LocalDateExample {

	public static void main(String[] args) {
		
		//Текущая дата
		LocalDate today = LocalDate.now();
		System.out.println("Current Date="+today);
		
		//Создание LocalDate с указанием входных аргументов
		LocalDate firstDay_2014 = LocalDate.of(2014, Month.JANUARY, 1);
		System.out.println("Specific Date="+firstDay_2014);
		
		
		//Попытка создания даты с недопустимыми входными данными
		//LocalDate feb29_2014 = LocalDate.of(2014, Month.FEBRUARY, 29);
		//Исключение в потоке "main" java.time.DateTimeException: 
		//Недопустимая дата '29 февраля', так как '2014' не является високосным годом
		
		//Текущая дата в "Asia/Kolkata", вы можете получить ее из документации по ZoneId
		LocalDate todayKolkata = LocalDate.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException: Неизвестный идентификатор часового пояса: IST
		//LocalDate todayIST = LocalDate.now(ZoneId.of("IST"));
		
		//Получение даты от базовой даты, то есть 01/01/1970
		LocalDate dateFromBase = LocalDate.ofEpochDay(365);
		System.out.println("365th day from base date= "+dateFromBase);
		
		LocalDate hundredDay2014 = LocalDate.ofYearDay(2014, 100);
		System.out.println("100th day of 2014="+hundredDay2014);
	}

}

Пояснения к методам LocalDate предоставлены в комментариях. При запуске этой программы мы получаем следующий вывод.

Current Date=2014-04-28
Specific Date=2014-01-01
Current Date in IST=2014-04-29
365th day from base date= 1971-01-01
100th day of 2014=2014-04-10

2. LocalTime

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

package com.journaldev.java8.time;

import java.time.LocalTime;
import java.time.ZoneId;

/**
 * Примеры LocalTime
 * @author pankaj
 *
 */
public class LocalTimeExample {

	public static void main(String[] args) {
		
		//Текущее время
		LocalTime time = LocalTime.now();
		System.out.println("Current Time="+time);
		
		//Создание LocalTime, предоставив входные аргументы
		LocalTime specificTime = LocalTime.of(12,20,25,40);
		System.out.println("Specific Time of Day="+specificTime);
		
		
		//Попытка создать время, предоставив недопустимые входные данные
		//LocalTime invalidTime = LocalTime.of(25,20);
		//Exception in thread "main" java.time.DateTimeException: 
		//Invalid value for HourOfDay (valid values 0 - 23): 25
		
		//Текущая дата в "Asia/Kolkata", вы можете получить ее из документации ZoneId
		LocalTime timeKolkata = LocalTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Time in IST="+timeKolkata);

		//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
		//LocalTime todayIST = LocalTime.now(ZoneId.of("IST"));
		
		//Получение даты из базовой даты, то есть 01/01/1970
		LocalTime specificSecondTime = LocalTime.ofSecondOfDay(10000);
		System.out.println("10000th second time= "+specificSecondTime);

	}

}

Вывод:

Current Time=15:51:45.240
Specific Time of Day=12:20:25.000000040
Current Time in IST=04:21:45.276
10000th second time= 02:46:40

3. LocalDateTime

LocalDateTime – это неизменяемый объект даты и времени, который представляет собой дату и время в формате по умолчанию yyyy-MM-dd-HH-mm-ss.zzz. Он предоставляет фабричный метод, который принимает в качестве аргументов ввода LocalDate и LocalTime для создания экземпляра LocalDateTime.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.Month;
import java.time.ZoneId;
import java.time.ZoneOffset;

public class LocalDateTimeExample {

	public static void main(String[] args) {
		
		//Текущая дата
		LocalDateTime today = LocalDateTime.now();
		System.out.println("Current DateTime="+today);
		
		//Текущая дата с использованием LocalDate и LocalTime
		today = LocalDateTime.of(LocalDate.now(), LocalTime.now());
		System.out.println("Current DateTime="+today);
		
		//Создание LocalDateTime путем предоставления входных аргументов
		LocalDateTime specificDate = LocalDateTime.of(2014, Month.JANUARY, 1, 10, 10, 30);
		System.out.println("Specific Date="+specificDate);
		
		
		//Попытка создания даты путем предоставления недопустимых входных данных
		//LocalDateTime feb29_2014 = LocalDateTime.of(2014, Month.FEBRUARY, 28, 25,1,1);
		//Исключение в потоке "main" java.time.DateTimeException: 
		//Недопустимое значение для HourOfDay (допустимые значения 0 - 23): 25

		
		//Текущая дата в "Asia/Kolkata", вы можете получить ее из документации по ZoneId
		LocalDateTime todayKolkata = LocalDateTime.now(ZoneId.of("Asia/Kolkata"));
		System.out.println("Current Date in IST="+todayKolkata);

		//java.time.zone.ZoneRulesException: Unknown time-zone ID: IST
		//LocalDateTime todayIST = LocalDateTime.now(ZoneId.of("IST"));
		
		//Получение даты от базовой даты, т.е. 01/01/1970
		LocalDateTime dateFromBase = LocalDateTime.ofEpochSecond(10000, 0, ZoneOffset.UTC);
		System.out.println("10000th second time from 01/01/1970= "+dateFromBase);

	}

}

Во всех трех примерах мы видели, что если мы предоставляем недопустимые аргументы для создания Даты/Времени, то возникает исключение java.time.DateTimeException, которое является RuntimeException, поэтому нам не нужно явно его перехватывать.

Мы также видели, что можем получить данные о дате/времени, передавая ZoneId; вы можете получить список поддерживаемых значений ZoneId из его JavaDoc. Когда мы запускаем вышеуказанный класс, мы получаем следующий вывод.

Current DateTime=2014-04-28T16:00:49.455
Current DateTime=2014-04-28T16:00:49.493
Specific Date=2014-01-01T10:10:30
Current Date in IST=2014-04-29T04:30:49.493
10000th second time from 01/01/1970= 1970-01-01T02:46:40

4. Мгновенность

Класс Instant используется для работы с форматом времени, читаемым машиной. Instant хранит дату и время в формате Unix-времени.

package com.journaldev.java8.time;

import java.time.Duration;
import java.time.Instant;

public class InstantExample {

	public static void main(String[] args) {
		// Текущее Unix-время
		Instant timestamp = Instant.now();
		System.out.println("Current Timestamp = "+timestamp);
		
		// Пример Instant из Unix-времени
		Instant specificTime = Instant.ofEpochMilli(timestamp.toEpochMilli());
		System.out.println("Specific Time = "+specificTime);
		
		// Пример длительности
		Duration thirtyDay = Duration.ofDays(30);
		System.out.println(thirtyDay);
	}

}

Вывод:

Current Timestamp = 2014-04-28T23:20:08.489Z
Specific Time = 2014-04-28T23:20:08.489Z
PT720H

Утилиты Java 8 Date API

Большинство основных классов для работы с датой и временем предоставляют различные утилиты, такие как плюс/минус дни, недели, месяцы и т. д. Существуют также другие утилиты для настройки даты с использованием TemporalAdjuster и для расчета периода между двумя датами.

package com.journaldev.java8.time;

import java.time.LocalDate;
import java.time.LocalTime;
import java.time.Period;
import java.time.temporal.TemporalAdjusters;

public class DateAPIUtilities {

	public static void main(String[] args) {
		
		LocalDate today = LocalDate.now();
		
		// Получить год, проверить, является ли он високосным
		System.out.println("Year "+today.getYear()+" is Leap Year? "+today.isLeapYear());
		
		// Сравнить две LocalDate на предшествие и последовательность
		System.out.println("Today is before 01/01/2015? "+today.isBefore(LocalDate.of(2015,1,1)));
		
		// Создать LocalDateTime из LocalDate
		System.out.println("Current Time="+today.atTime(LocalTime.now()));
		
		// Операции плюс и минус
		System.out.println("10 days after today will be "+today.plusDays(10));
		System.out.println("3 weeks after today will be "+today.plusWeeks(3));
		System.out.println("20 months after today will be "+today.plusMonths(20));

		System.out.println("10 days before today will be "+today.minusDays(10));
		System.out.println("3 weeks before today will be "+today.minusWeeks(3));
		System.out.println("20 months before today will be "+today.minusMonths(20));
		
		// Темпоральные адаптеры для корректировки дат
		System.out.println("First date of this month= "+today.with(TemporalAdjusters.firstDayOfMonth()));
		LocalDate lastDayOfYear = today.with(TemporalAdjusters.lastDayOfYear());
		System.out.println("Last date of this year= "+lastDayOfYear);
		
		Period period = today.until(lastDayOfYear);
		System.out.println("Period Format= "+period);
		System.out.println("Months remaining in the year= "+period.getMonths());		
	}
}

Вывод:

Year 2014 is Leap Year? false
Today is before 01/01/2015? true
Current Time=2014-04-28T16:23:53.154
10 days after today will be 2014-05-08
3 weeks after today will be 2014-05-19
20 months after today will be 2015-12-28
10 days before today will be 2014-04-18
3 weeks before today will be 2014-04-07
20 months before today will be 2012-08-28
First date of this month= 2014-04-01
Last date of this year= 2014-12-31
Period Format= P8M3D
Months remaining in the year= 8

Java 8 Разбор и Форматирование Даты

Очень распространено форматировать дату в различные форматы, а затем разбирать строку, чтобы получить объекты Date Time.

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

public class DateParseFormatExample {

	public static void main(String[] args) {
		
		// Примеры форматирования
		LocalDate date = LocalDate.now();
		// формат по умолчанию
		System.out.println("Default format of LocalDate="+date);
		// определенный формат
		System.out.println(date.format(DateTimeFormatter.ofPattern("d::MMM::uuuu")));
		System.out.println(date.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		
		LocalDateTime dateTime = LocalDateTime.now();
		// формат по умолчанию
		System.out.println("Default format of LocalDateTime="+dateTime);
		// определенный формат
		System.out.println(dateTime.format(DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss")));
		System.out.println(dateTime.format(DateTimeFormatter.BASIC_ISO_DATE));
		
		Instant timestamp = Instant.now();
		// формат по умолчанию
		System.out.println("Default format of Instant="+timestamp);
		
		// Примеры разбора
		LocalDateTime dt = LocalDateTime.parse("27::Apr::2014 21::39::48",
				DateTimeFormatter.ofPattern("d::MMM::uuuu HH::mm::ss"));
		System.out.println("Default format after parsing = "+dt);
	}

}

Вывод:

Default format of LocalDate=2014-04-28
28::Apr::2014
20140428
Default format of LocalDateTime=2014-04-28T16:25:49.341
28::Apr::2014 16::25::49
20140428
Default format of Instant=2014-04-28T23:25:49.342Z
Default format after parsing = 2014-04-27T21:39:48

Поддержка устаревших классов дат и времени в Java Date API

Устаревшие классы дат и времени используются практически во всех приложениях, поэтому обеспечение обратной совместимости является обязательным. Именно поэтому существует несколько утилитарных методов, с помощью которых мы можем преобразовывать устаревшие классы в новые и наоборот.

package com.journaldev.java8.time;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.TimeZone;

public class DateAPILegacySupport {

	public static void main(String[] args) {
		
		//Преобразование Date в Instant
		Instant timestamp = new Date().toInstant();
		//Теперь мы можем преобразовать Instant в LocalDateTime или другие подобные классы
		LocalDateTime date = LocalDateTime.ofInstant(timestamp, 
						ZoneId.of(ZoneId.SHORT_IDS.get("PST")));
		System.out.println("Date = "+date);
		
		//Преобразование Calendar в Instant
		Instant time = Calendar.getInstance().toInstant();
		System.out.println(time);
		//Преобразование TimeZone в ZoneId
		ZoneId defaultZone = TimeZone.getDefault().toZoneId();
		System.out.println(defaultZone);
		
		//Создание ZonedDateTime из конкретного Calendar
		ZonedDateTime gregorianCalendarDateTime = new GregorianCalendar().toZonedDateTime();
		System.out.println(gregorianCalendarDateTime);
		
		//Преобразование Date API в устаревшие классы
		Date dt = Date.from(Instant.now());
		System.out.println(dt);
		
		TimeZone tz = TimeZone.getTimeZone(defaultZone);
		System.out.println(tz);
		
		GregorianCalendar gc = GregorianCalendar.from(gregorianCalendarDateTime);
		System.out.println(gc);
		
	}

}

Результат:

Date = 2014-04-28T16:28:54.340
2014-04-28T23:28:54.395Z
America/Los_Angeles
2014-04-28T16:28:54.404-07:00[America/Los_Angeles]
Mon Apr 28 16:28:54 PDT 2014
sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]]
java.util.GregorianCalendar[time=1398727734404,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="America/Los_Angeles",offset=-28800000,dstSavings=3600000,useDaylight=true,transitions=185,lastRule=java.util.SimpleTimeZone[id=America/Los_Angeles,offset=-28800000,dstSavings=3600000,useDaylight=true,startYear=0,startMode=3,startMonth=2,startDay=8,startDayOfWeek=1,startTime=7200000,startTimeMode=0,endMode=3,endMonth=10,endDay=1,endDayOfWeek=1,endTime=7200000,endTimeMode=0]],firstDayOfWeek=2,minimalDaysInFirstWeek=4,ERA=1,YEAR=2014,MONTH=3,WEEK_OF_YEAR=18,WEEK_OF_MONTH=5,DAY_OF_MONTH=28,DAY_OF_YEAR=118,DAY_OF_WEEK=2,DAY_OF_WEEK_IN_MONTH=4,AM_PM=1,HOUR=4,HOUR_OF_DAY=16,MINUTE=28,SECOND=54,MILLISECOND=404,ZONE_OFFSET=-28800000,DST_OFFSET=3600000]

Как видно, методы toString() устаревших классов TimeZone и GregorianCalendar слишком многословны и неудобны для пользователя.

Заключение

I like this new Date Time API a lot. Some of the most used classes will be LocalDate and LocalDateTime. It’s very easy to work with the new classes. And, having similar methods that does a particular job makes it easy to find. It will take some time from moving legacy classes to new Date Time classes, but I believe it will be worth the time and effort.

Source:
https://www.digitalocean.com/community/tutorials/java-8-date-localdate-localdatetime-instant