Вопрос-ответ

How can I parse/format dates with LocalDateTime? (Java 8)

Как я могу анализировать / форматировать даты с помощью LocalDateTime? (Java 8)

В Java 8 добавлен новый java.time API для работы с датами и временем (JSR 310).

У меня есть дата и время в виде строки (например, "2014-04-08 12:30"). Как я могу получить LocalDateTime экземпляр из данной строки?

После того, как я закончил работу с LocalDateTime объектом: как я могу затем преобразовать LocalDateTime экземпляр обратно в строку с тем же форматом, что показан выше?

Переведено автоматически
Ответ 1

Анализ даты и времени

Чтобы создать LocalDateTime объект из строки, вы можете использовать статический LocalDateTime.parse() метод. Он принимает строку и DateTimeFormatter в качестве параметра. DateTimeFormatter используется для указания шаблона даты / времени.

String str = "1986-04-08 12:30";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.parse(str, formatter);

Форматирование даты и времени

Чтобы создать форматированную строку из LocalDateTime объекта, вы можете использовать format() метод.

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
LocalDateTime dateTime = LocalDateTime.of(1986, Month.APRIL, 8, 12, 30);
String formattedDateTime = dateTime.format(formatter); // "1986-04-08 12:30"

Обратите внимание, что в DateTimeFormatter есть некоторые часто используемые форматы даты / времени, предопределенные в качестве констант. Например: использование DateTimeFormatter.ISO_DATE_TIME для форматирования LocalDateTime экземпляра выше приведет к получению строки "1986-04-08T12:30:00".

Методы parse() и format() доступны для всех объектов, связанных с датой / временем (например, LocalDate или ZonedDateTime)

Ответ 2

Вы также можете использовать LocalDate.parse() or LocalDateTime.parse() для String без предоставления шаблона, если String находится в формате ISO 8601.

Например,

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
System.out.println("Date: " + aLD);

String strDatewithTime = "2015-08-04T10:11:30";
LocalDateTime aLDT = LocalDateTime.parse(strDatewithTime);
System.out.println("Date with Time: " + aLDT);

Вывод,

Date: 2015-08-04
Date with Time: 2015-08-04T10:11:30

И используйте DateTimeFormatter только в том случае, если вам приходится иметь дело с другими шаблонами дат.

Например, в следующем примере, dd MMM uuuu представляет день месяца (две цифры), три буквы названия месяца (январь, февраль, март, ...) и четырехзначный год:

DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
String anotherDate = "04 Aug 2015";
LocalDate lds = LocalDate.parse(anotherDate, dTF);
System.out.println(anotherDate + " parses to " + lds);

Вывод

04 Aug 2015 parses to 2015-08-04

также помните, что DateTimeFormatter объект является двунаправленным; он может как анализировать входные данные, так и форматировать выходные данные.

String strDate = "2015-08-04";
LocalDate aLD = LocalDate.parse(strDate);
DateTimeFormatter dTF = DateTimeFormatter.ofPattern("dd MMM uuuu");
System.out.println(aLD + " formats as " + dTF.format(aLD));

Вывод

2015-08-04 formats as 04 Aug 2015

(Смотрите Полный список шаблонов для форматирования и синтаксического анализа DateFormatter.)

  Symbol  Meaning                     Presentation      Examples
------ ------- ------------ -------
G era text AD; Anno Domini; A
u year year 2004; 04
y year-of-era year 2004; 04
D day-of-year number 189
M/L month-of-year number/text 7; 07; Jul; July; J
d day-of-month number 10

Q/q quarter-of-year number/text 3; 03; Q3; 3rd quarter
Y week-based-year year 1996; 96
w week-of-week-based-year number 27
W week-of-month number 4
E day-of-week text Tue; Tuesday; T
e/c localized day-of-week number/text 2; 02; Tue; Tuesday; T
F week-of-month number 3

a am-pm-of-day text PM
h clock-hour-of-am-pm (1-12) number 12
K hour-of-am-pm (0-11) number 0
k clock-hour-of-am-pm (1-24) number 0

H hour-of-day (0-23) number 0
m minute-of-hour number 30
s second-of-minute number 55
S fraction-of-second fraction 978
A milli-of-day number 1234
n nano-of-second number 987654321
N nano-of-day number 1234000000

V time-zone ID zone-id America/Los_Angeles; Z; -08:30
z time-zone name zone-name Pacific Standard Time; PST
O localized zone-offset offset-O GMT+8; GMT+08:00; UTC-08:00;
X zone-offset 'Z' for zero offset-X Z; -08; -0830; -08:30; -083015; -08:30:15;
x zone-offset offset-x +0000; -08; -0830; -08:30; -083015; -08:30:15;
Z zone-offset offset-Z +0000; -0800; -08:00;

p pad next pad modifier 1

' escape for text delimiter
'' single quote literal '
[ optional section start
] optional section end
# reserved for future use
{ reserved for future use
} reserved for future use
Ответ 3

Оба ответа Суфияна Гори и миши очень хорошо объясняют вопрос, касающийся строковых шаблонов. Однако, на всякий случай, если вы работаете с ISO 8601, нет необходимости применять DateTimeFormatter поскольку LocalDateTime уже подготовлен для этого:

Преобразуйте LocalDateTime в строку ISO 8601 часового пояса

LocalDateTime ldt = LocalDateTime.now();
ZonedDateTime zdt = ldt.atZone(ZoneOffset.UTC); // You might use a different zone
String iso8601 = zdt.toString();

Преобразовать строку ISO8601 обратно в LocalDateTime

String iso8601 = "2016-02-14T18:32:04.150Z";
ZonedDateTime zdt = ZonedDateTime.parse(iso8601);
LocalDateTime ldt = zdt.toLocalDateTime();
Ответ 4

Разбор строки с датой и временем в определенный момент времени (Java называет это "Instant") довольно сложен. Java решала это в нескольких итерациях. Последняя версия, java.time и java.time.chrono, покрывает почти все потребности (за исключением замедления времени :) ).

Однако такая сложность вносит много путаницы.

Ключ к пониманию анализа дат - это:

Почему в Java так много способов анализа даты?


  1. Существует несколько систем для измерения времени. Например, исторические японские календари были созданы на основе временных интервалов правления соответствующего императора или династии. Тогда есть, например, временная метка Unix. К счастью, всему (деловому) миру удалось использовать то же самое.

  2. Исторически системы переключались с / на, по разным причинам. Например, с юлианского календаря на григорианский календарь в 1582 году; таким образом, "западные" даты до этого нужно обрабатывать по-разному.

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

... и почему LocalDateTime, ZonedDateTime и др. такой сложный


  1. Существуют часовые пояса. Часовой пояс - это, по сути, "полоса" * [3] поверхности Земли, власти которой следуют тем же правилам, когда и какое смещение по времени. Сюда входят правила летнего времени.


    Часовые пояса меняются со временем для разных областей, в основном в зависимости от того, кто кого завоевывает. И правила одного часового пояса меняются со временем также.



  2. Существуют смещения по времени. Это не то же самое, что часовые пояса, потому что часовым поясом может быть, например, "Прага", но у него смещение по летнему и зимнему времени.


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


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



  3. Несколько часовых поясов могут использовать одинаковое смещение времени для определенных периодов. Например, часовой пояс GMT / UTC совпадает с часовым поясом "Лондон", когда летнее смещение времени не действует.



Чтобы немного усложнить (но это не слишком важно для вашего варианта использования):


  1. Ученые наблюдают за динамикой Земли, которая меняется с течением времени; основываясь на этом, они добавляют секунды в конце отдельных лет. (Таким образом, 24:00:00 2040-12-31 может быть допустимой датой-временем.) Для этого требуется регулярное обновление метаданных, которые системы используют для правильного преобразования дат. Например, в Linux вы получаете регулярные обновления пакетов Java, включая эти новые данные.



  2. Обновления не всегда сохраняют предыдущее поведение как для исторических, так и для будущих временных меток. Таким образом, может случиться так, что анализ двух временных меток с учетом изменения некоторого часового пояса, сравнивая их, может дать разные результаты при запуске в разных версиях программного обеспечения. Это также относится к сравнению между затронутым часовым поясом и другим часовым поясом.


    Если это вызовет ошибку в вашем программном обеспечении, рассмотрите возможность использования какой-либо временной метки, в которой нет таких сложных правил, как временная метка Unix.



  3. Из-за версии 7 для будущих дат мы не можем точно преобразовать даты. Так, например, текущий синтаксический анализ 8524-02-17 12:00:00 может быть отключен через пару секунд после будущего синтаксического анализа.



API-интерфейсы JDK для этого развивались с учетом современных потребностей


  • В ранних версиях Java был только java.util.Date который имел немного наивный подход, предполагающий, что есть только год, месяц, день и время. Этого быстро оказалось недостаточно.

  • Кроме того, потребности баз данных были разными, поэтому довольно рано была введена java.sql.Date со своими собственными ограничениями.

  • Поскольку ни один из них не хорошо описывает разные календари и часовые пояса, был представлен Calendar API.

  • Это все еще не учитывало сложность часовых поясов. И все же, сочетание вышеупомянутых API было действительно сложным в работе. Когда разработчики Java начали работать над глобальными веб-приложениями, библиотеки, ориентированные на большинство вариантов использования, такие как JodaTime, быстро приобрели популярность. JodaTime был стандартом де-факто около десяти лет.

  • Но JDK не интегрировался с JodaTime, поэтому работа с ним была немного громоздкой. Итак, после очень долгого обсуждения того, как подойти к этому вопросу, JSR-310 был создан в основном на основе JodaTime.

Как с этим справиться в Java java.time

Определите, к какому типу следует преобразовывать временную метку

Когда вы используете строку временной метки, вам нужно знать, какую информацию она содержит. Это ключевой момент. Если вы не сделаете это правильно, в итоге вы получите загадочные исключения, такие как "Невозможно создать Instant", "Отсутствует смещение зоны", "неизвестный идентификатор зоны" и т.д.

Содержит ли он дату и время?


  1. Есть ли у него смещение по времени? Смещение по времени - это часть + чч: мм. Иногда +00:00 может быть заменен на Z как "время Зулу", UTC как всемирное координированное время или GMT как среднее время по Гринвичу. Они также устанавливают часовой пояс. Для этих временных меток вы используете OffsetDateTime.



  2. Есть ли у него часовой пояс? Для этих временных меток вы используете ZonedDateTime. Зона указывается либо с помощью



    • имя ("Прага", "Тихоокеанское стандартное время", "PST") или

    • "идентификатор зоны" ("Америка / Лос-Анджелес", "Европа / Лондон"), представленный java.time .ZoneId.


    Список часовых поясов составляется "базой данных TZ", поддерживаемой ICAAN.


    Согласно ZoneIdjavadoc, идентификатор зоны также может быть каким-то образом указан как Z и offset . Я не уверен, как это соотносится с реальными зонами.


    Если временная метка, которая имеет только TZ , попадает в високосный час изменения временного смещения, то это неоднозначно, и интерпретация является предметом ResolverStyle, см. Ниже.



  3. Если у него нет ни того, ни другого, то предполагается отсутствующий контекст или им пренебрегают. И решать должен потребитель. Поэтому его необходимо проанализировать как LocalDateTime и преобразовать в OffsetDateTime, добавив недостающую информацию:



    • Вы можете предположить, что это время UTC. Добавьте смещение UTC на 0 часов.

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

    • Вы можете пренебречь и просто использовать его как есть. Это полезно, например, для сравнения или вычитания два раза (см. Duration), или когда вы не знаете, и это действительно не имеет значения (например, расписание местного автобуса).



Частичная информация о времени


  • В зависимости от того, что содержит временная метка, вы можете взять из нее LocalDate, LocalTime, OffsetTime MonthDay, Year YearMonth,,, или,, из нее.

Если у вас есть полная информация, вы можете получить java.time.Instant. Это также используется внутри для преобразования между OffsetDateTime и ZonedDateTime.

Выясните, как это проанализировать

Существует обширная документация по DateTimeFormatter, которая может как анализировать строку метки времени, так и форматировать в строку.

Предварительно созданные DateTimeFormatters должны охватывать более или менее все стандартные форматы временных меток. Например, ISO_INSTANT can parse 2011-12-03T10:15:30.123457Z .

Если у вас есть какой-то специальный формат, то вы можете создать свой собственный DateTimeFormatter (который также является анализатором).

private static final DateTimeFormatter TIMESTAMP_PARSER = new DateTimeFormatterBuilder()
.parseCaseInsensitive()
.append(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SX"))
.toFormatter();

Я рекомендую взглянуть на исходный код DateTimeFormatter и черпать вдохновение в том, как создать его с помощью DateTimeFormatterBuilder. Пока вы там, также взгляните на ResolverStyle который определяет, является ли анализатор МЯГКИМ, ИНТЕЛЛЕКТУАЛЬНЫМ или СТРОГИМ для форматов и неоднозначной информации.

TemporalAccessor

Итак, частой ошибкой является углубляться в сложность TemporalAccessor. Это связано с тем, как разработчики привыкли работать с SimpleDateFormatter.parse(String). Правильно, DateTimeFormatter.parse("...") дает вам TemporalAccessor.

// No need for this!
TemporalAccessor ta = TIMESTAMP_PARSER.parse("2011-... etc");

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

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z", TIMESTAMP_PARSER);

На самом деле вам это тоже не нужно DateTimeFormatter. Типы, которые вы хотите проанализировать, имеют parse(String) методы.

OffsetDateTime myTimestamp = OffsetDateTime.parse("2011-12-03T10:15:30.123457Z");

Что касается TemporalAccessor, вы можете использовать это, если у вас есть смутное представление о том, какая информация содержится в строке, и вы хотите принять решение во время выполнения.

Я надеюсь, что пролил свет понимания на вашу душу :)

Примечание: Есть бэкпорт из java.time в Java 6 и 7: ThreeTen-Backport. Для Android у него есть ThreeTenABP.

[3] Дело не только в том, что они не являются полосами, но и в некоторых странных крайностях. Например, некоторые соседние острова Тихого океана имеют часовые пояса +14:00 и -11:00. Это означает, что хотя на одном острове 1 мая в 3 часа дня, на другом острове не так далеко, все еще 12 часов 30 апреля (если я правильно посчитал :))

java java-8