Для момента времени с часовым поясом, таким как 2020-02-01T22: 00-04: 00 [America / New_York], не используйте java.sql.Date
. По двум причинам:
java.sql.Date
- плохо спроектированный класс, настоящий взлом действительно, если он уже плохо спроектирован java.util.Date
. К счастью, оба Date
класса также давно устарели. java.sql.Date
был разработан для даты без времени суток.
Вместо:
- В вашей базе данных SQL используйте
timestamp with time zone
и сохраняйте время в UT C. Таким образом, время, хранимое в вашей базе данных, должно быть 2020-02-02T02: 00Z (Z для UT C). - В Java извлечь ваше время в
OffsetDateTime
(начиная с JDB C 4.2 мы можем сделать это, полностью обходя java.sql.Date
и java.sql.Timestamp
). Затем при необходимости конвертируйте в ZonedDateTime
в вашем часовом поясе. Используйте правильный идентификатор часового пояса в формате регион / город (не только то, что вы считаете смещением UT C).
Для демонстрации:
ZoneId zone = ZoneId.of("Asia/Tbilisi");
OffsetDateTime dateTimeFromDatabase
= OffsetDateTime.of(2020, 2, 2, 2, 0, 0, 0, ZoneOffset.UTC);
ZonedDateTime dateTimeInYourTimeZone
= dateTimeFromDatabase.atZoneSameInstant(zone);
System.out.println(dateTimeInYourTimeZone);
Вывод:
2020-02-02T06: 00 + 04: 00 [Азия / Тбилиси]
Редактировать 1: Вы сказали:
Я понимаю, что это плохо использовать устаревшие java. sql .Date, но у меня нет выбора. "java. sql. Дата была разработана для даты без времени суток." - но я думал, что в любом случае могу узнать время суток, вызвав (java. sql .Date) значение) .getTime () (потому что он возвращает метку времени)
Из документации:
Чтобы соответствовать определению SQL DATE, значения миллисекунд, обернутые экземпляром java.sql.Date
, должны быть «нормализованы» путем установки часов, минут, секунд и миллисекунд на ноль в конкретном часовой пояс, с которым связан экземпляр.
Так что мне кажется, что вы нарушаете контракт. Каковы последствия, я не знаю. Вероятно, они зависят от вашего драйвера JDB C. То есть поведение может измениться со следующей версией этого драйвера JDB C.
Редактировать 2: Я внимательно посмотрел на ваши данные. Я согласен с вами, что они не правы; но проблема не в коде, который вы представили, а в объекте java.sql.Date
, который, кажется, вы каким-то образом получили.
Для моего исследования я сделал:
// time in database: 01-02-2020 22:00
// (in America/New_York -> it's UTC-4 and I need to add extra 4 hours)
ZonedDateTime dateTimeInDatebase = ZonedDateTime
.of(2020, 2, 1, 22, 0, 0, 0, ZoneId.of("America/New_York"));
System.out.println("In database: " + dateTimeInDatebase);
long correctEpochMillis = dateTimeInDatebase.toInstant().toEpochMilli();
System.out.println("Correct millis: " + correctEpochMillis);
// toString() result = 01-02-2020T16:00Z
OffsetDateTime observedDateTime
= OffsetDateTime.of(2020, 2, 1, 16, 0, 0, 0, ZoneOffset.UTC);
long observedEpochMilli = observedDateTime.toInstant().toEpochMilli();
System.out.println("Observed millis: " + observedEpochMilli);
Duration error = Duration.between(dateTimeInDatebase, observedDateTime);
System.out.println("Error: " + error);
Вывод это:
In database: 2020-02-01T22:00-05:00[America/New_York]
Correct millis: 1580612400000
Observed millis: 1580572800000
Error: PT-11H
Наблюдения:
- Смещение UT C в Нью-Йорке в феврале не -04: 00, а -05: 00 (-04: 00 - правильное смещение в летнее время / летнее время).
- Значение в миллисекундах, которое вы получили из вашего
java.sql.Date
, не обозначает момент времени, который должен. В вашем коде нет ничего, что изменило бы момент времени. Таким образом, вы получаете не только неправильный тип, но и неверное значение. - Считайте ошибку, напечатанную в последней строке вывода, как период времени минус 11 часов. Значение миллисекунды в вашем
java.sql.Date
на 11 часов раньше.
Вы сами объяснили некоторые расхождения с разницей во временных зонах, и я считаю, что это правда. Мы еще не убедились, что это целая история. Поэтому я также не могу сказать вам, что решение. Кроме подачи заявки поставщику вашего неверного типа и значения, чтобы вы вместо этого получили правильные данные. Конечно, можно добавить 11 часов, но нужно ли вам тогда добавлять только 10 часов в летнее время года - я не тот человек, которого нужно спрашивать.
Редактировать 3:
Мне только что пришла в голову идея зафиксировать вдвое значение метки времени. Как и в первый раз - добавить смещение локальной зоны (исправить влияние драйвера jdbc), а во второй - обработать смещение дат, хранящихся в базе данных.
Мы можем сделать это, если захотим:
Instant observedResult = Instant.parse("2020-02-01T16:00:00Z");
Object receivedValue = new java.sql.Date(observedResult.toEpochMilli());
long receivedEpochMillis = ((java.sql.Date) receivedValue).getTime();
ZonedDateTime adjustedDateTime = Instant.ofEpochMilli(receivedEpochMillis)
.atZone(ZoneId.systemDefault())
.withZoneSameLocal(ZoneId.of("America/New_York"));
System.out.println(adjustedDateTime);
Вывод при запуске в часовом поясе Азия / Тбилиси (так вот что вернул ZoneId.systemDefault()
; это смещение +04: 00 в течение всего года):
2020-02- 01T20: 00-05: 00 [America / New_York]
Это приближает нас к тому, что, как вы говорите, было в базе данных, но еще слишком рано. Я извиняюсь.
Ссылки