У меня есть база данных 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.
Сообщите нам, какой собственный тип данных MySQL используется для вашей информации о дате MySQL. Это TIMESTAMP
, DATE
или DATETIME
? Это важно, потому что значения TIMESTAMP
имеют некоторое полезное поведение в отношении часовых поясов. Два других типа данных этого не делают: Пожалуйста, отредактируйте свой вопрос.
Моя проблема в том, что дата не была сохранена с часовым поясом CEST. Например, он хранился вместе с America/New_York.
Из того, что я знаю о MySQL, это невозможно.
Calendar calendar = new GregorianCalendar()
Нет, не надо. API календаря — это катастрофа. Используйте java.time
, единственный API времени в java, который действительно работает и не полностью сломан / очень плохо спроектирован. Если вы не можете (java 7 крайне устарела и небезопасна, вы должны обновиться!), есть бэкпорт jsr310. Добавьте эту зависимость и используйте ее.
Позвольте мне сначала попытаться объяснить, как понимать время, потому что иначе любой ответ на этот вопрос не может быть правильно понят:
Есть 3 совершенно разных понятия, и все они часто упрощаются до значения «время», но упрощать их не следует — эти 3 разных понятия на самом деле не связаны, и если вы когда-либо путаете одно с другим, всегда возникают проблемы. Вы не можете конвертировать между этими тремя концепциями, если вы не сделаете это намеренно!
Теперь по вашему вопросу:
У меня есть база данных 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.
Обычно я бы советовал загружать через getObject(col, LocalDateTime.class)
, а затем использовать D / T API, чтобы взять его оттуда (преобразовать это в ZonedDateTime, используя сохраненный часовой пояс, а затем мир — ваша устрица). Если вы не можете, выясните формулу, чтобы отменить преобразование, которое происходит, когда вы читаете время пробуждения-будильника из mysql в время солнечных вспышек (=Мгновение/Дата) в java. Верните его обратно на время будильника (=LocalDateTime), возьмите его оттуда.
Я разместил решение с формулой для отмены преобразования. Большое спасибо!
Благодаря @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 Ура!
Ваша ошибка мысли заключается в том, что вы не считаете, что
Date
внутренне хранит только целое число, которое представляет «разницу, измеренную в миллисекундах, между текущим временем и полуночью 1 января 1970 года по всемирному координированному времени». Это целое число неверно, как только вы получите объект Date из базы данных, и изменение часового пояса в объектах Calender или Date не повлияет на него.