it-swarm.com.ru

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

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

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

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

290
micha

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

Чтобы создать объект LocalDateTime из строки, вы можете использовать метод static 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)

464
micha

Вы также можете использовать LocalDate.parse() или 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
130
Sufiyan Ghori

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

Преобразовать LocalDateTime в строку часового пояса ISO8601

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();
31
Marcio Jasinski

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

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

Ключ к пониманию разбора даты:

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

  1. Есть несколько систем для измерения времени. Например, исторические японские календари были получены из временных периодов правления соответствующего императора или династии. Тогда есть, например, Метка времени UNIX К счастью, весь (деловой) мир сумел использовать то же самое.
  2. Исторически системы переключались с/на по различным причинам . Например. от юлианского календаря до григорианского календаря в 1582 году. Таким образом, "западные" даты до этого должны трактоваться иначе.
  3. И, конечно, изменения не произошли сразу. Поскольку календарь происходил из штаб-квартиры какой-то религии и других частей Европы, которые верили в другие диеты, например, Германия не переключалась до 1700 года.

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

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

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

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

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

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

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

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

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

  • В ранних Java выпусках был только Java.util.Date, в котором был немного наивный подход, предполагая, что есть только год, месяц, день и время. Этого быстро не хватило.
  • Кроме того, потребности в базах данных были другими, поэтому довольно рано был введен Java.sql.Date со своими собственными ограничениями.
  • Поскольку ни один из них не охватывал различные календари и часовые пояса, был представлен API Calendar.
  • Это все еще не покрывало сложность часовых поясов. И тем не менее, сочетание вышеперечисленных API-интерфейсов было действительно трудной задачей. Так как Java разработчики начали работать над глобальными веб-приложениями, библиотеки, нацеленные на большинство случаев использования, такие как JodaTime, стали быстро популярными. JodaTime был стандартом де-факто около десяти лет.
  • Но JDK не интегрировался с JodaTime, поэтому работать с ним было немного громоздко. Итак, после очень долгого обсуждения того, как подойти к этому вопросу, JSR-310 было создано в основном на основе JodaTime .

Как бороться с этим в Java Java.time

Определите, какой тип для анализа метки времени в

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

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

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

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

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

    Список часовых поясов составлен с помощью "TZ database" , при поддержке ICAAN.

    Согласно javadoc ZoneId, идентификаторы зоны также могут быть как-то определены как 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 может анализировать 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, который определяет, является ли синтаксический анализатор LENIENT, SMART или STRICT для форматов и неоднозначной информации.

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 .

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

2
Ondra Žižka