ZonedDateTime.toInstant (). ToEpochMilli () потеря данных зоны - PullRequest
1 голос
/ 13 июля 2020

Я использую библиотеку TrueTime, которая возвращает Date в системном часовом поясе. У меня проблемы с преобразованием этого Date в UT C Date при преобразовании в миллисекунды.

Вот что я сделал:

// getting true time in GMT [ex : 2020-07-13T18:00:57.192+03:00 [Europe/Istanbul]]
Date now = getTrueNowTimeInGMT();

// I like to use `LocalDateTime`, so I am converting `Date` to `LocalDateTime`:
LocalDateTime ldtOfSystem = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());

// Converting system default zone to UTC
ZonedDateTime zdtOfSystem = ldtOfSystem.atZone(ZoneId.systemDefault());
ZonedDateTime zdtOfSystemInUTC = zdtOfSystem.withZoneSameInstant(ZoneId.of("UTC"));

// Making sure that it is in the UTC. It is returning correct UTC time [ex: 2020-07-13T15:00:57.192 [UTC]]
System.out.println(zdtOfSystemInUTC); 

// converting zdtOfSystemInUTC to milliseconds - problem is occurring here!
zdtOfSystemInUTC.toInstant().toEpochMilli();

Я теряю данные зоны, и они возвращаются миллисекунды в зависимости от локальной зоны (системной зоны). Я снова конвертирую эти миллисекунды в Date, и результат будет в GMT + 3 [2020-07-13T18: 00: 57.192 + 03: 00]

Я что-то упускаю? Я прочитал в одном посте, что метод toInstant() не заботится о часовом поясе. Как я могу получить миллисекунды из указанного c часового пояса, который я указал в ZonedDateTime?

EDIT Я изменил имена переменных (надеюсь, на лучшее one) спасибо @MenuHochSchild за указание на это.

Разъяснение . Зачем мне нужно время UT C? Я не могу контролировать время пользователя. Он / она может легко изменить дату и время на своем устройстве, и это создает для нас проблемы. Итак, чтобы решить эту проблему, мы нашли библиотеку под названием TrueTime, которая предоставляет объект Date с истинным временем, например:

Tue Jul 14 00:32:46 GMT+03:00 2020

Для синхронизации c с нашим сервер и выполнить некоторые операции, связанные со временем, мне нужно преобразовать это Date, которое является GMT, в UT C и преобразовать его в миллисекунды.

Ответы [ 2 ]

2 голосов
/ 15 июля 2020

Ответ Оле В.В. правильный. Добавлю несколько мыслей и диаграмму.

Никогда не используйте java.util.Date

Ваш код:

Date now = getTrueNowTimeInGMT ();

Посмотрите, обновлена ​​ли ваша библиотека для java .time . Класс java.util.Date был вытеснен годами a go с принятием JSR 310 . Вместо этого следует использовать класс java.time.Instant.

Instant представляет момент в UT C

После получения объекта Date немедленно конвертируйте из устаревшего класса в современный класс. Используйте новые методы преобразования, добавленные к старым классам.

Instant instant = myJavaUtilDate.toInstant() ;

Вы сказали:

Я что-то упустил? Я читал в одном сообщении, что метод toInstant () не заботится о часовом поясе.

java.time.Instant не заботится о часовом поясе, поскольку он всегда представляет момент времени, как видно в UT C, по определению.

  • Захват текущего момента через Instant.now всегда будет виден в перспективе UT C.
  • Добавление / вычитание диапазона -время через Instant::plus и Instant::minus всегда будет в UT C и будет включать общие c 24-часовые дни UT C времени, которые не встречаются с аномалиями политического времени, такими как Летнее время (DST) .

Отсчет от эталона эпохи

Оба этих класса, устаревшие и современные, представляют момент в UT C. Оба внутренне ведут счет времени с первого момента 1970 года в UT C, 1970-01-01T00: 00Z. Instant использует более точное разрешение наносекунд в своем отсчете по сравнению с миллисекундами в Date.

Очевидно, вам нужно подсчет миллисекунд с 1970-01-01T00: 00Z.

long millisSinceEpoch = instant.toEpochMilli() ;

И снова.

Instant instant = Instant.ofEpochMilli( millisSinceEpoch ) ;

Я не рекомендую отслеживать время как счет. Такой подсчет по своей сути неоднозначен для человеческого читателя и может быть ошибочным или неверным. Я настоятельно рекомендую вместо этого использовать стандартные строки ISO 8601 для передачи значения даты и времени.

String output = instant.toString() ;

И снова.

Instant instant = Instant.parse( input ) ;

Table of all date-time types in Java, both modern and legacy

ZonedDateTime

You said:

How can I get the milliseconds from the specific time zone I pointed out in ZonedDateTime?

A ZonedDateTime object represents a moment as seen through the wall-clock time used by people of a particular region. Imagine two people talking on a long-distance phone call, one on the west coast of US, and one in Tunis Tunisia.

Capture the moment when their call started.

Instant callStartedUtc = Instant.now() ;

When the person in US glanced at a clock on their wall as they dialed their phone, what time did they see?

ZonedDateTime zdtLosAngeles = callStartedUtc.atZone( ZoneId.of( "America/Los_Angeles" ) ) ;

When the person in Tunisia glanced at a clock hanging on their wall as they picked up their phone to answer, what time did they see?

ZonedDateTime zdtTunis = callStartedUtc.atZone( ZoneId.of( "Africa/Tunis" ) ) ;

Three objects all representing the very same simultaneous moment. You can extract a Instant object from both ZonedDateTime objects.

Instant instant = zdtLosAngeles.toInstant() ;

These will all be equal. In this example code, eq will be true.

boolean eq = 
    callStartedUtc.equals( zdtLosAngeles.toInstant() ) 
    &&
    callStartedUtc.equals( zdtTunis.toInstant() )
;

All three internally have the very same count from epoch reference.

UTC as The One True Time

Tip: Learn to think in terms of UTC while on the job programming or doing sysadmin work. Think of UTC as the One True Time, with time zones being mere variations.

Do not translate back-and-forth in your head, just stick with UTC. Like learning a foreign language, constant translation in your head will drive you batty.

Exchanging date-time values between programs or systems, logging, debugging, and such should all be done in UTC. I suggest keeping a 2nd clock on your desk set to UTC.

LocalDateTime is not a moment

You said:

// I like to use LocalDateTime, so I am converting Date to LocalDateTime:

LocalDateTime ldtOfSystem = LocalDateTime.ofInstant(now.toInstant(), ZoneId.systemDefault());

You should not "like" LocalDateTime. That class cannot represent a moment. It holds a date and a time-of-day, but lacks the context of a time zone or offse-from-UTC. So it may say "noon on the 23rd of January 2021" but we have no idea if that means noon in Tokyo Japan, noon in Toulouse France, or noon in Toledo Ohio US — all very different moments, several hours apart.

Whenever you are tracking a moment, a specific point on the timeline, you cannot use LocalDateTime. Instead use Instant (for UTC), OffsetDateTime (for an offset-from-UTC, a number hours-minutes-seconds), or ZonedDateTime (for a time zone, in Continent/Region name, such as Europe/Paris).

When in doubt, do not use LocalDateTime. I find most of the time in business apps, we are tracking a specific moment. The big exception is when booking future events, such as appointments, where LocalDateTime is needed.

Search to learn more as this has been covered many many times already on Stack Overflow. For example, Что такое разница между Instant и LocalDateTime? .

Сервер времени

Вы сказали:

Уточнение. Почему мне нужно время UT C?

Полагаю, вы действительно хотели сказать: «Почему мне нужно получать текущий момент от доверенного удаленного сервера времени? ". Если да, я прошу вас отредактировать свой вопрос для ясности.

Это законная потребность. Если локальным часам нельзя доверять и у вас должно быть значение, близкое к текущему моменту, вы действительно должны обращаться к доверенному серверу времени, если он доступен.

Термин UTC на самом деле не является актуально здесь. Сервер времени, скорее всего, вернет значение в единицах UT C. Но нахождение в UT C не имеет ничего общего с локальным или удаленным источником времени.

GMT по сравнению с UT C

Вы сказали:

Мне нужно преобразовать эту дату, которая является GMT, в UT C

Нет, почти наверняка вы этого не сделаете.

Точные определения GMT и различных видов UT C является длинным, сложным, академическим c и спорным практически для всех программистов.

С точки зрения обычных бизнес-приложений, таких как бухгалтерский учет и рабочий процесс, вы можете рассмотреть GMT и UT C быть синонимами. Разница буквально меньше секунды. И часы по умолчанию, используемые Java, действительно игнорируют эту разницу.

Если вы не работаете с ракетной телеметрией, спутниками GPS / Galileo или часами atomi c, вам действительно не нужно заботиться о GMT и UT C.

Более того, в вашем случае использования удаленного сервера времени, GMT по сравнению с UT C еще менее применимо, так как полученное значение теряет значительное время при прохождении по сети (сетям).

2 голосов
/ 14 июля 2020

Кажется, вы кое-что неправильно поняли.

// getting true time in GMT [ex : 2020-07-13T18:00:57.192+03:00 [Europe/Istanbul]]
Date now = getTrueNowTimeInGMT();

A Date не имеет часового пояса или смещения от UT C или GMT. Так что не может быть в GMT. A Date - это момент времени, не больше и не меньше. Кстати, я обычно рекомендую вам вообще не использовать этот класс, так как он плохо спроектирован и давно устарел; но если TrueTime еще не может предоставить вам Instant или другой современный класс, нам придется на данный момент go с Date.

Я теряю данные зоны, и он возвращает миллисекунды на основе локальной зоны (системной зоны). …

Количество миллисекунд с начала эпохи не зависит от часового пояса. Эпоха обычно определяется в UT C, но это один момент времени. Следовательно, миллисекунды никогда не основываются на каком-либо часовом поясе (или вы можете сказать, что они всегда на основе UT C, в зависимости от того, как вы предпочитаете использовать слова, это означает то же самое).

… Я снова конвертирую эти миллисекунды в Date, и результат будет в GMT + 3 [2020-07-13T18: 00: 57.192 + 03: 00]

Я не Не понимаю этой части. Вы указываете те же дату, время и смещение UT C, что и те, которые вы получили от TrueTime, так как же это могло быть неправильно? Java рассматривает UT C и GMT как синонимы (в реальном мире между UT C и GMT всегда меньше секунды, так что это, вероятно, нормально).

Все еще предполагая, что вы не можете получить но на старомодном Date из вашей библиотеки даты и времени мне кажется, что простой способ получить текущее количество миллисекунд с эпохи:

getTrueNowTimeInGMT().getTime();

Из-за того, что я сказал выше это будет надежно независимо от настройки часового пояса устройства.

...