Java SE 8 TemporalAccessor.from выдает проблемы при использовании с объектом java.time.Instant
java.time
имеет класс Instant, который инкапсулирует позицию (или "момент") на временной шкале. Хотя я понимаю, что это значение в секундах / наносекундах, поэтому оно не связано напрямую с часовыми поясами или временными смещениями, его toString
возвращает дату и время в формате даты и времени UTC, например, 2014-05-13T20:05:08.556Z. Также anInstant.atZone(zone)
а также anInstant.atOffset(offset)
оба производят значение, которое согласуется с лечением Instant
как подразумеваемый часовой пояс UTC / нулевое смещение.
Я ожидал бы поэтому:
ZoneOffset.from(anInstant)
произвести "ноль"ZoneOffset
OffsetDateTime.from(anInstant)
создать дату / время с нулевым смещениемZoneId.from(anInstant)
(вероятно), чтобы произвести UTCZoneId
ZonedDateTime.from(anInstant)
(вероятно), чтобы произвестиZonedDateTime
с UTCZoneId
Документация для ZonedDateTime.from
, как я читал, кажется, подтверждает это.
по факту ZoneOffset.from(anInstant)
не удается с DateTimeException
и я полагаю по этой причине OffsetDateTime.from(anInstant)
также терпит неудачу, как и два других.
Это ожидаемое поведение?
3 ответа
Короткий ответ:
Дизайнеры JSR-310 не хотят, чтобы люди выполняли преобразования между машинным и человеческим временем с помощью статического метода from()- методы таких типов, как ZoneId
, ZoneOffset
, OffsetDateTime
, ZonedDateTime
и т. д. Это явно указано, если вы внимательно изучаете Javadoc. Вместо этого используйте:
OffsetDateTime#toInstant():Instant
ZonedDateTime#toInstant():Instant
Instant#atOffset(ZoneOffset):OffsetDateTime
Instant#atZone(ZoneId):ZonedDateTime
Проблема статических методов from () состоит в том, что в противном случае люди могут делать преобразования между Instant
и, например, LocalDateTime
не думая о часовом поясе.
Длинный ответ:
Стоит ли рассматривать Instant
в качестве счетчика или полевого кортежа, ответ команды JSR-310 был строгим разделением между так называемым машинным временем и человеческим временем. На самом деле они намерены иметь строгое разделение - см. Их рекомендации. Итак, наконец, они хотят Instant
быть интерпретированным только как счетчик машинного времени. Таким образом, они намеренно имеют дизайн, где вы не можете спросить Instant
для таких полей, как год, час и т. д.
Но на самом деле, команда JSR-310 совсем не последовательна. Они реализовали метод Instant.toString()
как представление полевого кортежа, включающее год,..., час,... и символ смещения Z (для UTC-часового пояса) (сноска: за пределами JSR-310 это довольно распространено, чтобы иметь представление на основе поля на такой машине раз - см., например, в Википедии или на других сайтах о TAI и UTC). Однажды ведущий специализации С. Колебурн сказал в комментарии к проблеме "Threeten-GitHub":
"Если бы мы были действительно жесткой строкой, то toString of a Instant было бы просто количеством секунд с 1970-01-01Z. Мы решили не делать этого и вывели более дружественную toString, чтобы помочь разработчикам, но она не меняется основной факт, что мгновение - это всего лишь количество секунд, и его нельзя преобразовать в год / месяц / день без какого-либо часового пояса ".
Людям может нравиться это дизайнерское решение или нет (как я), но в результате вы не можете спросить Instant
за год,..., час,... и зачет. Смотрите также документацию по поддерживаемым полям:
NANO_OF_SECOND
MICRO_OF_SECOND
MILLI_OF_SECOND
INSTANT_SECONDS
Здесь интересно, чего не хватает, прежде всего отсутствует поле, связанное с зоной. В качестве причины, мы часто слышим утверждение, что объекты, такие как Instant
или же java.util.Date
нет часового пояса. На мой взгляд, это слишком упрощенный взгляд. Хотя это правда, что эти объекты не имеют внутреннего состояния часового пояса (и также нет необходимости иметь такое внутреннее значение), эти объекты ДОЛЖНЫ быть связаны с часовым поясом UTC, поскольку это является основой для каждого вычисления смещения часового пояса и преобразования в локальные типы., Таким образом, правильный ответ будет: Instant
счетчик машины, считающий секунды и наносекунды с эпохи UNIX в часовом поясе UTC (согласно спецификации). Последняя часть - отношение к зоне UTC - не очень хорошо определена командой JSR-310, но они не могут этого отрицать. Дизайнеры хотят отменить аспект часового пояса от Instant
потому что это похоже на человеческое время. Однако они не могут полностью отменить его, потому что это является фундаментальной частью любого внутреннего вычисления смещения. Итак, ваше наблюдение относительно
"Также Instant.atZone(zone)
и Instant.atOffset(offset)
оба дают значение, которое согласуется с трактовкой Мгновенного состояния как подразумеваемого часового пояса UTC / смещения "ноль". "
правильно.
Хотя это может быть очень интуитивным, что ZoneOffset.from(anInstant)
может производить ZoneOffset.UTC
, он выдает исключение, потому что его метод from () ищет несуществующее OFFSET_SECONDS-поле. Разработчики JSR-310 решили сделать это в спецификации по той же причине, а именно, чтобы заставить людей думать, что Instant
официально не имеет ничего общего с часовым поясом UTC, то есть "не имеет часового пояса" (но внутренне они должны принять этот базовый факт во всех внутренних расчетах!).
По той же причине, OffsetDateTime.from(anInstant)
а также ZoneId.from(anInstant)
потерпеть неудачу тоже.
Около ZonedDateTime.from(anInstant)
мы читаем:
"Преобразование сначала получит ZoneId от временного объекта, возвращаясь к ZoneOffset, если необходимо. Затем оно попытается получить Instant, возвращаясь к LocalDateTime, если необходимо. Результатом будет либо комбинация ZoneId или ZoneOffset с Instant или LocalDateTime. "
Таким образом, это преобразование не удастся снова по тем же причинам, потому что ни ZoneId
ни ZoneOffset
можно получить из Instant
, Сообщение об исключении читается как:
"Невозможно получить ZoneId от TemporalAccessor: 1970-01-01T00:00:00Z типа java.time.Instant"
Наконец, мы видим, что все статические методы from () страдают от невозможности выполнить преобразование между человеческим временем и машинным временем, даже если это выглядит интуитивно понятным. В некоторых случаях преобразование между скажем LocalDate
а также Instant
сомнительно Это поведение указано, но я предсказываю, что ваш вопрос не последний вопрос такого рода, и многие пользователи будут продолжать путаться.
Реальная проблема дизайна, на мой взгляд, заключается в том, что:
а) Не должно быть резкого разделения между человеческим временем и машинным временем. Временные объекты, такие как Instant
лучше вести себя как оба. Аналогия в квантовой механике: вы можете рассматривать электрон как частицу и как волну.
б) Все статические из () - методы слишком публичны. По моему мнению, он слишком легко доступен и его лучше удалить из публичного API или использовать более конкретные аргументы, чем TemporalAccessor
, Слабость этих методов заключается в том, что люди могут забыть думать о связанных часовых поясах в таких преобразованиях, потому что они начинают запрос с локального типа. Рассмотрим для примера: LocalDate.from(anInstant)
(в каком часовом поясе???). Однако, если вы прямо спросите Instant
для его даты, как instant.getDate()
лично я считаю дату в UTC-часовом поясе действительным ответом, потому что здесь запрос начинается с точки зрения UTC.
c) В заключение: я абсолютно разделяю с командой JSR-310 хорошую идею избегать преобразований между локальными типами и глобальными типами, такими как Instant
без указания часового пояса. Я просто отличаюсь, когда речь заходит об API-дизайне, чтобы пользователи не делали такое преобразование без часового пояса. Моим предпочтительным способом было бы ограничить методы from()- вместо того, чтобы говорить, что глобальные типы не должны иметь никакого отношения к форматам человеческого времени, таким как календарная дата или настенное время или UTC-timezone-offset.
В любом случае, этот (непоследовательный) дизайн разделения между машинным и человеческим временем теперь заложен благодаря сохранению обратной совместимости, и каждый, кто хочет использовать новый java.time-API, должен с этим мириться.
Извините за длинный ответ, но довольно сложно объяснить выбранный дизайн JSR-310.
Instant
класс не имеет часового пояса. Он печатается так, как будто он находится в зоне UTC, потому что он должен быть напечатан в некоторой зоне (вы не хотели бы, чтобы он печатался в тиках, не так ли?), Но это не часовой пояс - как показано в следующем примере:
Instant instant=Instant.now();
System.out.println(instant);//prints 2014-05-14T06:18:48.649Z
System.out.println(instant.atZone(ZoneId.of("UTC")));//prints 2014-05-14T06:18:48.649Z[UTC]
Подписи ZoneOffset.from
а также OffsetDateTime.from
принять любой временный объект, но они терпят неудачу для некоторых типов. java.time
не похоже на интерфейс для временных значений, которые имеют часовой пояс или смещение. Такой интерфейс мог бы объявить getOffset
а также getZone
, Поскольку у нас нет такого интерфейса, эти методы объявляются отдельно в нескольких местах.
Если бы у вас был ZonedDateTime
Вы могли бы назвать это getOffset
, Если у вас был OffsetDateTime
Вы также можете назвать это смещение - но это два разных getOffset
методы, так как два класса не получают этот метод из общего интерфейса. Это означает, что если у вас есть Temporal
объект, который может быть либо вы должны проверить, если это instanceof
оба из них, чтобы получить смещение.
OffsetDateTime.from
упростит процесс, сделав это для вас - но так как он также не может полагаться на общий интерфейс, он должен принимать любые Temporal
возьмите объект и сгенерируйте исключение для тех, у кого нет смещения.
Просто пример по конверсии, я верю, что некоторые люди получат исключение ниже
(java.time.DateTimeException: невозможно получить LocalDateTime из TemporalAccessor: 2014-10-24T18:22:09.800Z типа java.time.Instant)
если они попытаются -
LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant());
Чтобы решить проблему, пройдите в зону -
LocalDateTime localDateTime = LocalDateTime.from(new Date().toInstant().atZone(ZoneId.of("UTC")));
Ключом к получению LocalDateTime из Instant является предоставление системного часового пояса:
LocalDateTime.ofInstant(myDate.toInstant(), ZoneId.systemDefault())
В качестве примечания, остерегайтесь многозонных облачных серверов, поскольку часовой пояс обязательно изменится.