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

341

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

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

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

Миха
источник
11
К вашему сведению, большинство людей большую часть времени хотели бы, ZonedDateTimeа не LocalDateTime. Имя нелогично; Localозначает любую местность в целом , а не по времени конкретной зоны. Таким образом, LocalDateTimeобъект не привязан к временной шкале. Чтобы иметь смысл, чтобы получить определенный момент на временной шкале, вы должны применить часовой пояс.
Базилик Бурк
Смотрите мой ответ для объяснения LocalDateTimeпротив ZonedDateTimeпротив OffsetDateTimeпротив Instantпротив LocalDateпротив LocalTime, как сохранить спокойствие о том, почему это так сложно и как сделать это правильно с первого выстрела.
Ондра Жижка
1
Если бы это было непрактично долго, LocalDateTimeвероятно, назвали бы ZonelessOffsetlessDateTime.
Ондра Жижка

Ответы:

534

Разбор даты и времени

Для создания 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)

Миха
источник
77
Просто отметим, что DateTimeFormatter является неизменным и потокобезопасным, и поэтому рекомендуемый подход заключается в хранении его в статической константе, где это возможно.
JodaStephen
@micha, что если у меня будет "2016-12-31T07: 59: 00.000Z" в формате даты?
Дауд Ахмед
14
@DawoodAbbasi попробуйDateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX")
Рэй Хулха
1
@Loenix, может быть, это потому, что вы пытаетесь вызвать format()класс LocalDateTime, а не экземпляр? По крайней мере, это то, что я сделал: я путать DateTimeс dateTimeв приведенном выше примере.
застеклен
2
Не забудьте заглавные буквы на MM
Весос де Кесо
159

Вы также можете использовать 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
Суфиян Гори
источник
11
Этот ответ затронул важную тему: по возможности используйте предопределенные средства форматирования, например, НЕ создавайте базу форматирования для «гггг-ММ-дд», вместо этого используйте DateTimeFormatter.ISO_LOCAL_DATE. Это сделает ваш код намного чище. Кроме того, постарайтесь максимально использовать формат ISO8061, это принесет дивиденды в долгосрочной перспективе.
Кристофер Ян
Я хочу проанализировать дату для проверки, например, 2018-08-09 12:00:08но когда я анализирую, я вижу Tдобавление, которое мне не нужно. Есть ли способ сделать это?
Рагхувеер
@Raghuveer T - это просто разделитель ISO-8061 между датой и временем. Если вместо этого у вас есть пробел в вашем формате, вы можете просто использовать шаблон yyyy-MM-dd hh:mm:ssдля разбора и форматирования. Символ T всегда будет отображаться в формате по умолчанию (ISO-8061), но вы можете использовать свои собственные шаблоны.
Егор Ханс
39

Оба ответа выше очень хорошо объясняют вопрос о строковых шаблонах. Однако на тот случай, если вы работаете с 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();
Марсио Ясински
источник
20

Разбор строки с датой и временем в определенный момент времени (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было введено, со своими собственными ограничениями.
  • Поскольку ни один из них не охватывал разные календари и часовые пояса, CalendarAPI был представлен.
  • Это все еще не покрывало сложность часовых поясов. И все же, сочетание вышеперечисленных API-интерфейсов было действительно трудной задачей. Так как Java-разработчики начали работать над глобальными веб-приложениями, библиотеки, нацеленные на большинство вариантов использования, такие как JodaTime, стали быстро популярными. JodaTime был стандартом де-факто около десяти лет.
  • Но JDK не интегрировался с JodaTime, поэтому работать с ним было немного громоздко. Итак, после очень долгого обсуждения того, как подойти к этому вопросу, JSR-310 был создан в основном на основе JodaTime .

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

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

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

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

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

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

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

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

    Согласно ZoneId'javadoc', идентификаторы зоны также могут быть как-то указаны как Zи смещение. Я не уверен, как это отображается в реальных зонах. Если временная метка, которая имеет только 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 часов вечера (если я правильно посчитал :))

Ондра Жижка
источник
3

ПОЛУЧИТЕ ТЕКУЩЕЕ ВРЕМЯ UTC В ТРЕБУЕМОМ ФОРМАТЕ

// Current UTC time
        OffsetDateTime utc = OffsetDateTime.now(ZoneOffset.UTC);

        // GET LocalDateTime 
        LocalDateTime localDateTime = utc.toLocalDateTime();
        System.out.println("*************" + localDateTime);

        // formated UTC time
        DateTimeFormatter dTF = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF.format(localDateTime));

        //GET UTC time for current date
        Date now= new Date();
        LocalDateTime utcDateTimeForCurrentDateTime = Instant.ofEpochMilli(now.getTime()).atZone(ZoneId.of("UTC")).toLocalDateTime();
        DateTimeFormatter dTF2 = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
        System.out.println(" formats as " + dTF2.format(utcDateTimeForCurrentDateTime));
Santhosh Hirekerur
источник
0

Я нашел замечательным охватить несколько вариантов формата даты и времени, например:

final DateTimeFormatterBuilder dtfb = new DateTimeFormatterBuilder();
dtfb.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSSS"))
.appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SS"))
    .appendOptional(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.S"))
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
flowgrad
источник
1
`` `public final static DateTimeFormatter TIMESTAMP_XX = new DateTimeFormatterBuilder (). appendPattern (" [[uuuu] [- MM] [- dd]] [[HH] [: мм] [: ss] [. SSS]] "). parseDefaulting (ChronoField.YEAR, 2020) .parseDefaulting (ChronoField.MONTH_OF_YEAR, 1) .parseDefaulting (ChronoField.DAY_OF_MONTH, 1) .parseDefaulting (ChronoField.HOUR_OF_DAY, 0) .parseDefaulting (ChronoField.MINUTE_OF_HOUR, 0) .parseDefaulting (ChronoField.SECOND_OF_MINUTE , 0) .parseDefaulting (ChronoField.NANO_OF_SECOND, 0) .toFormatter (); `` `
Алан Стюарт