Рассчитать время по дате, взятой с другим часовым поясом

У меня есть база данных MySQL, в которой хранится значение даты и времени, скажем, 2020-10-11 12:00:00. (формат гггг-мм-дд чч:мм:сс)

Тип этой даты (в mysql) — DATETIME.

Когда я получаю эти данные в своем контроллере, он имеет тип Java 7 «Дата». Но я подозреваю, что он добавляет часовой пояс CEST из-за моей локали. Здесь меня уже сбивает с толку то, что при отображении этой даты, к которой не должен быть привязан часовой пояс, он действительно есть ... и отладчик говорит, что это «2020-10-11 12:00:00 CEST».

Моя проблема в том, что дата не была сохранена с часовым поясом CEST. Например, он хранился вместе с America/New_York. Обновлено: Что я имею в виду под этой строкой, так это то, что дата была сохранена из Нью-Йорка с использованием часового пояса Нью-Йорка. Итак, там действительно было 12:00:00, а здесь, в Мадриде, было 18:00:00. Мне нужно это 18:00:00.

Так вот в Нью-Йорке кто-то в то время делал вставку. Это означает, что время в Европе было другим. Мне нужно рассчитать, какое время было в Европе, когда в Америке было 12 часов ночи. Но мой компьютер продолжает устанавливать эту дату на CEST, когда я ее получаю, поэтому все мои попытки синтаксического анализа терпят неудачу... Это была моя идея:

Date testingDate // This date is initialized fetching the "2020-10-11 12:00:00" from mySql
Calendar calendar = new GregorianCalendar()
calendar.setTime(testingDate)
calendar.setTimeZone(TimeZone.getTimeZone("America/New_York")

SimpleDateFormat localDateFormatter = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
TimeZone localTimeZone = TimeZone.getTimeZone("Europe/Madrid")
localDateFormatter.setTimeZone(localTimeZone)

String localStringDate = localDateFormatter.format(calendar.getTime())
Date newDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(localStringDate)

Моя идея такова: я создаю совершенно новый календарь, добавляю в него время, которое у меня было в Америке, и я также говорю, что в этом календаре должен быть часовой пояс Америки. Поэтому, когда я получаю время с помощью модуля форматирования из Европы, он должен добавить к нему соответствующие часы. Это имеет большой смысл в моей голове, но просто не работает в коде. D: И я действительно не хочу вычислять разницу во времени самостоятельно и добавлять или вычитать часы, потому что, на мой взгляд, это выглядело бы чрезвычайно жестко запрограммированным.

Может ли кто-нибудь дать мне несколько идей о том, что я неправильно интерпретирую, или как мне лучше решить эту проблему?

Важно: я использую Java 7 и Grails 2.3.6.

Ваша ошибка мысли заключается в том, что вы не считаете, что Date внутренне хранит только целое число, которое представляет «разницу, измеренную в миллисекундах, между текущим временем и полуночью 1 января 1970 года по всемирному координированному времени». Это целое число неверно, как только вы получите объект Date из базы данных, и изменение часового пояса в объектах Calender или Date не повлияет на него.

OH GOD SPIDERS 11.12.2020 15:03

Сообщите нам, какой собственный тип данных MySQL используется для вашей информации о дате MySQL. Это TIMESTAMP, DATE или DATETIME? Это важно, потому что значения TIMESTAMP имеют некоторое полезное поведение в отношении часовых поясов. Два других типа данных этого не делают: Пожалуйста, отредактируйте свой вопрос.

O. Jones 11.12.2020 15:23
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
2
2
298
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Моя проблема в том, что дата не была сохранена с часовым поясом CEST. Например, он хранился вместе с America/New_York.

Из того, что я знаю о MySQL, это невозможно.

Calendar calendar = new GregorianCalendar()

Нет, не надо. API календаря — это катастрофа. Используйте java.time, единственный API времени в java, который действительно работает и не полностью сломан / очень плохо спроектирован. Если вы не можете (java 7 крайне устарела и небезопасна, вы должны обновиться!), есть бэкпорт jsr310. Добавьте эту зависимость и используйте ее.

Позвольте мне сначала попытаться объяснить, как понимать время, потому что иначе любой ответ на этот вопрос не может быть правильно понят:

О времени!

Есть 3 совершенно разных понятия, и все они часто упрощаются до значения «время», но упрощать их не следует — эти 3 разных понятия на самом деле не связаны, и если вы когда-либо путаете одно с другим, всегда возникают проблемы. Вы не можете конвертировать между этими тремя концепциями, если вы не сделаете это намеренно!

  • «время солнечных вспышек»: они описывают момент времени как универсальную глобальную концепцию, когда что-то произошло или произойдет. «Эта солнечная вспышка наблюдалась в X» — это время «солнечных вспышек». Лучший способ сохранить это - миллисекунды с эпохи.
  • «время встречи»: они описывают определенный момент времени, каким он был или будет в каком-то локализованном месте, но изложенный в понятной для всего мира форме. «В следующий вторник в 17:00 у нас собрание в масштабе» — один из них. На самом деле это не постоянно, потому что локали могут решить принять новые часовые пояса или, например. переместите «дату переключения» для перехода на летнее время. Например, если у вас назначен прием к стоматологу на «5 ноября, 17:00, 2021 г.» и вы хотите узнать, сколько часов осталось до начала приема, то значение не должно меняться только потому, что вы прилетели в другой часовой пояс и смотрите на этот номер оттуда. Однако это должно измениться, если страна, в которой вы назначили встречу, решила отменить летнее время. В этом разница между этим и «солнечными вспышками». Это все еще может измениться из-за политических решений.
  • «время пробуждения-будильника»: они описывают более изменчивую концепцию: каким-то образом люди относятся ко времени, которое не относится к какому-либо конкретному моменту или даже пытается это сделать. Подумайте: «Мне нравится просыпаться в 8», и, таким образом, количество времени до следующего звонка будильника постоянно меняется, если вы путешествуете по часовым поясам.

Теперь по вашему вопросу:

У меня есть база данных MySQL, в которой хранится значение даты и времени, скажем, 2020-10-11 12:00:00. (формат гггг-мм-дд чч:мм:сс)

Не так быстро. Какой именно тип у этой колонки? Что в вашем заявлении CREATE TABLE? Здесь важно выяснить, что на самом деле хранится на диске? Солнечная вспышка, свидание или будильник? Есть DATE, DATETIME и TIMESTAMP, и с годами mysql значительно изменил способ хранения этих вещей.

Я считаю, что, если вы используете современные подходы к хранилищу (таким образом, новый mysql и никаких настроек для явной эмуляции старого поведения), например. a DATETIME хранит под капотом знак, год, день, час, минуту и ​​секунду, что означает, что это стиль будильника: в этом нет информации о часовом поясе, поэтому фактический момент времени вообще не устанавливается и зависит от кто спрашивает.

В отличие от TIMEZONE, который хранится как секунды эпохи UTC, поэтому это время солнечных вспышек, и он вообще не включает часовой пояс. Вам придется хранить это отдельно. Насколько мне известно, самое полезное из трех представлений времени (время встречи) не существует в mysql. Это очень раздражает; mysql имеет тенденцию быть, так что, возможно, в порядке вещей.

В java существуют все 3 концепции:

  • время солнечных вспышек java.time.Instant. java.util.Date, java.sql.Timestamp, System.currentTimeMillis() также время солнечных вспышек. То, что «Дата» — это временная метка солнечных вспышек, безумно, но есть причина, по которой API был заменен.

  • время приема java.time.ZonedDateTime

  • время пробуждения-будильника java.time.LocalDateTime.

Когда я получаю эти данные в своем контроллере, он имеет тип Java 7 «Дата».

Верно. Итак, время солнечных вспышек.

Вот что важно:

Если тип времени, хранящийся в MySQL, не соответствует типу времени на стороне Java, возникает проблема.

Похоже, у вас есть время пробуждения на диске, и оно оказывается на стороне Java как время солнечных вспышек. Это означает, что кто-то задействовал преобразование часового пояса. Это могло произойти внутри mysql, могло произойти в полете между mysql и драйвером jdbc (mysql помещает его «на провод» преобразованным), или драйвер jdbc сделал это, чтобы соответствовать java.sql.Timestamp.

Лучшее решение - вообще не конвертировать, и единственный реальный способ сделать это - либо изменить определение таблицы mysql, чтобы оно соответствовало java, поэтому сделайте это CREATE TABLE (foo TIMESTAMP), поскольку TIMESTAMP также является временем солнечных вспышек, или, чтобы использовать, в Уровень JDBC, а не:

someResultSet.getTimestamp(col);

поскольку это возвращает время солнечных вспышек, но:

someResultSet.getObject(col, LocalDateTime.class);

Проблема в том, что ваш драйвер JDBC может не поддерживать это. Если это не так, это дрянной драйвер JDBC, но иногда это случается.

Это по-прежнему лучший план — план А. Так что не переходите к дерьмовому варианту плана Б, если нет другого пути.

План B:

Признайте, что преобразование происходит и что это чрезвычайно раздражает и чревато ошибками. Итак, убедитесь, что вы управляете им тщательно и явно: убедитесь, что правильный вызов SET настроен так, чтобы значение mysql того, в каком часовом поясе мы находились, совпадало. Рассмотрите возможность добавления часового пояса в виде столбца в таблицу, если вам действительно нужно время встречи. и так далее.

Вау... это был лучший ответ, который я когда-либо получал в SO @rzwitserloot. Хорошо, несколько вещей: столбец mySQL имеет тип даты и времени. Так что будите тревогу, как вы это называете. Но у меня также есть часовой пояс в другом столбце!. Я знаю, что «гггг-мм-дд чч: мм: сс» был сохранен из часового пояса X. Но разбор его на мой текущий - это шаг, вызывающий у меня проблемы. Что касается вашего плана А, мне не разрешено изменять типы дат в MySQL.

Rauññ 11.12.2020 15:31

Обычно я бы советовал загружать через getObject(col, LocalDateTime.class), а затем использовать D / T API, чтобы взять его оттуда (преобразовать это в ZonedDateTime, используя сохраненный часовой пояс, а затем мир — ваша устрица). Если вы не можете, выясните формулу, чтобы отменить преобразование, которое происходит, когда вы читаете время пробуждения-будильника из mysql в время солнечных вспышек (=Мгновение/Дата) в java. Верните его обратно на время будильника (=LocalDateTime), возьмите его оттуда.

rzwitserloot 11.12.2020 16:08

Я разместил решение с формулой для отмены преобразования. Большое спасибо!

Rauññ 14.12.2020 14:10

Благодаря @rzwitserloot я смог найти решение.

Сначала я получу данные из базы данных. Я избавлюсь от любого часового пояса, добавленного драйвером/mysql, преобразовав его в LocalDateTime. Затем я создам новый ZonedDateTime, используя часовой пояс, который использовался при сохранении данных в базе данных.

Когда у меня есть ZonedDateTime, пришло время преобразовать его, используя мой текущий часовой пояс. Я получу новый объект ZonedDateTime с правильным временем.

Затем я просто добавляю еще несколько строк, чтобы преобразовать его обратно в мой основной класс «Дата»:

Я использовал бэкпорт ThreeTen, как было предложено.

Date dateMySQL //Initialized with the date from mysql
Calendar calendar = new GregorianCalendar()
calendar.setTime(dateMySQL)

org.threeten.bp.LocalDateTime localDateTime = org.threeten.bp.LocalDateTime.of(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH)+1,
            calendar.get(Calendar.DAY_OF_MONTH), calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE),
            calendar.get(Calendar.SECOND))

String timezone //Initialized with the timezone from mysql (Ex: "America/New_York") 
ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, ZoneId.of(timezone))
ZonedDateTime utcDate = zonedDateTime.withZoneSameInstant(ZoneId.of("Europe/Madrid"))
calendar.setTimeInMillis(utcDate.toInstant().toEpochMilli())
Date desiredDate = calendar.time

dateMySQL: "2020-10-11 10:00:00" // CEST из-за моего драйвера

часовой пояс: "Америка/Нью-Йорк"

requiredDate: "2020-10-11 19:00:00" // CEST Ура!

Другие вопросы по теме