Как я могу анализировать / форматировать даты с помощью 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 так много способов анализа даты?
- Существует несколько систем для измерения времени. Например, исторические японские календари были созданы на основе временных интервалов правления соответствующего императора или династии. Тогда есть, например, временная метка Unix. К счастью, всему (деловому) миру удалось использовать то же самое.
- Исторически системы переключались с / на, по разным причинам. Например, с юлианского календаря на григорианский календарь в 1582 году; таким образом, "западные" даты до этого нужно обрабатывать по-разному.
- И, конечно, изменение произошло не сразу. Потому что календарь пришел из штаб-квартиры какой-то религии, а в других частях Европы верили в других божеств, например, Германия не переходила до 1700 года.
... и почему LocalDateTime
, ZonedDateTime
и др. такой сложный
Существуют часовые пояса. Часовой пояс - это, по сути, "полоса" * [3] поверхности Земли, власти которой следуют тем же правилам, когда и какое смещение по времени. Сюда входят правила летнего времени.
Часовые пояса меняются со временем для разных областей, в основном в зависимости от того, кто кого завоевывает. И правила одного часового пояса меняются со временем также.
Существуют смещения по времени. Это не то же самое, что часовые пояса, потому что часовым поясом может быть, например, "Прага", но у него смещение по летнему и зимнему времени.
Если вы получаете временную метку с часовым поясом, смещение может варьироваться в зависимости от того, в какой части года это происходит. Во время високосного часа временная метка может означать два разных времени, поэтому без дополнительной информации ее невозможно надежно преобразовать.
Примечание: Под меткой времени я подразумеваю "строку, содержащую дату и / или время, необязательно с часовым поясом и / или смещением по времени".
Несколько часовых поясов могут использовать одинаковое смещение времени для определенных периодов. Например, часовой пояс GMT / UTC совпадает с часовым поясом "Лондон", когда летнее смещение времени не действует.
Чтобы немного усложнить (но это не слишком важно для вашего варианта использования):
Ученые наблюдают за динамикой Земли, которая меняется с течением времени; основываясь на этом, они добавляют секунды в конце отдельных лет. (Таким образом, 24:00:00 2040-12-31 может быть допустимой датой-временем.) Для этого требуется регулярное обновление метаданных, которые системы используют для правильного преобразования дат. Например, в Linux вы получаете регулярные обновления пакетов Java, включая эти новые данные.
Обновления не всегда сохраняют предыдущее поведение как для исторических, так и для будущих временных меток. Таким образом, может случиться так, что анализ двух временных меток с учетом изменения некоторого часового пояса, сравнивая их, может дать разные результаты при запуске в разных версиях программного обеспечения. Это также относится к сравнению между затронутым часовым поясом и другим часовым поясом.
Если это вызовет ошибку в вашем программном обеспечении, рассмотрите возможность использования какой-либо временной метки, в которой нет таких сложных правил, как временная метка Unix.
Из-за версии 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", "Отсутствует смещение зоны", "неизвестный идентификатор зоны" и т.д.
- Не удается получить OffsetDateTime из TemporalAccessor
- Не удается получить ZonedDateTime из TemporalAccessor
- Не удается получить LocalDateTime из TemporalAccessor
- Невозможно получить Instant из TemporalAccessor
Содержит ли он дату и время?
Есть ли у него смещение по времени? Смещение по времени - это часть + чч: мм. Иногда +00:00 может быть заменен на Z как "время Зулу",
UTC
как всемирное координированное время или GMT как среднее время по Гринвичу. Они также устанавливают часовой пояс. Для этих временных меток вы используетеOffsetDateTime
.Есть ли у него часовой пояс? Для этих временных меток вы используете
ZonedDateTime
. Зона указывается либо с помощью- имя ("Прага", "Тихоокеанское стандартное время", "PST") или
- "идентификатор зоны" ("Америка / Лос-Анджелес", "Европа / Лондон"), представленный java.time .ZoneId.
Список часовых поясов составляется "базой данных TZ", поддерживаемой ICAAN.
Согласно
ZoneId
javadoc, идентификатор зоны также может быть каким-то образом указан как Z и offset . Я не уверен, как это соотносится с реальными зонами.Если временная метка, которая имеет только TZ , попадает в високосный час изменения временного смещения, то это неоднозначно, и интерпретация является предметом
ResolverStyle
, см. Ниже.Если у него нет ни того, ни другого, то предполагается отсутствующий контекст или им пренебрегают. И решать должен потребитель. Поэтому его необходимо проанализировать как
LocalDateTime
и преобразовать вOffsetDateTime
, добавив недостающую информацию:- Вы можете предположить, что это время UTC. Добавьте смещение UTC на 0 часов.
- Вы можете предположить, что это время того места, где происходит преобразование. Преобразуйте его, добавив часовой пояс системы.
- Вы можете пренебречь и просто использовать его как есть. Это полезно, например, для сравнения или вычитания два раза (см.
Duration
), или когда вы не знаете, и это действительно не имеет значения (например, расписание местного автобуса).
Частичная информация о времени
- В зависимости от того, что содержит временная метка, вы можете взять из нее
LocalDate
,LocalTime
,OffsetTime
MonthDay
,Year
YearMonth
,,, или,, из нее.
Если у вас есть полная информация, вы можете получить java.time.Instant
. Это также используется внутри для преобразования между OffsetDateTime
и ZonedDateTime
.
Выясните, как это проанализировать
Существует обширная документация по DateTimeFormatter
, которая может как анализировать строку метки времени, так и форматировать в строку.
Предварительно созданные DateTimeFormatter
s должны охватывать более или менее все стандартные форматы временных меток. Например, 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 апреля (если я правильно посчитал :))