Как я могу принять во внимание часовой пояс java. sql .Date, когда я читаю его из базы данных? - PullRequest
0 голосов
/ 21 апреля 2020

Мое приложение читает java. sql. Дата из базы данных, которая содержит эти даты в часовом поясе Америки / Нью-Йорка (-4 UT C). После выборки данных Hibernate создает объекты java. sql .Date и представляет их в моем местном часовом поясе. Итак, мне нужно конвертировать дату из базы данных в UT C напрямую. Как я могу это сделать?

Мне нужно что-то вроде этого

Instant.ofEpochMilli(((java.sql.Date) value).getTime()).atOffset(offset);

Но offset не делает то, что я хочу. Например:

время в базе данных: 01-02-2020 22:00 (в Америке / New_York -> это UT C -4, и мне нужно добавить дополнительные 4 часа)

время в моем заявлении: 01-02-2020 22:00 +4 (потому что мой часовой пояс UTC + 4). Когда я устанавливаю ZoneOffset.UT C

Instant.ofEpochMilli(((java.sql.Date) value).getTime()).atOffset(ZoneOffset.UTC)

, он удаляет 4 часа и toString () result = 01-02-2020T16: 00Z

Как добавить 4 часа к дате ( java. sql .Date) в базе данных, чтобы это было 02-02-2020 02: 00 UT C?

1 Ответ

0 голосов
/ 22 апреля 2020

Для момента времени с часовым поясом, таким как 2020-02-01T22: 00-04: 00 [America / New_York], не используйте java.sql.Date. По двум причинам:

  1. java.sql.Date - плохо спроектированный класс, настоящий взлом действительно, если он уже плохо спроектирован java.util.Date. К счастью, оба Date класса также давно устарели.
  2. 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

Наблюдения:

  1. Смещение UT C в Нью-Йорке в феврале не -04: 00, а -05: 00 (-04: 00 - правильное смещение в летнее время / летнее время).
  2. Значение в миллисекундах, которое вы получили из вашего java.sql.Date, не обозначает момент времени, который должен. В вашем коде нет ничего, что изменило бы момент времени. Таким образом, вы получаете не только неправильный тип, но и неверное значение.
  3. Считайте ошибку, напечатанную в последней строке вывода, как период времени минус 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]

Это приближает нас к тому, что, как вы говорите, было в базе данных, но еще слишком рано. Я извиняюсь.

Ссылки

...