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

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

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

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

Ответ 1

Дата и время Parsing

Чтобы создать объект 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() или 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 в часовой пояс Строка ISO8601

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

Преобразовать из ISO8601 String обратно в 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, покрывает почти все потребности (кроме 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.

  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

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

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

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

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

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

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

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

    Согласно javadoc ZoneId, идентификатор зоны также может быть как-то указан как Z и смещение. Я не уверен, как это отображается в реальных зонах. Если временная метка, которая имеет только TZ, попадает в високосный час изменения временного смещения, то она неоднозначна, и интерпретация является предметом ResolverStyle, см. Ниже.

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

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

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

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

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

Выяснить, как разобрать это

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

Предварительно созданные DateTimeFormatter должны охватывать все стандартные форматы временных меток. Например, 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 часов вечера (если я правильно посчитал :))

Ответ 5

Преобразование java.time.LocalDate в строку:

LocalDate localDate;
localDate.format(DateTimeFormatter.ofPattern("dd/MM/yyy")); //"dd/MM/yyy" your pattern