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