Чи визначений часовий пояс java.sql.Timestamp?


85

Я повинен зберігати UTC dateTime у БД.
Я перетворив дату і час, вказані в конкретному часовому поясі, на UTC. для цього я дотримувався наведеного нижче коду.
Моя дата введення Час "20121225 10:00:00 Z" часовий пояс "Азія / Калькутта"
Мій сервер / БД (оракул) працює в тому ж часовому поясі (IST) "Азія / Калькутта"

Отримайте об’єкт Date у цьому конкретному часовому поясі

        String date = "20121225 10:00:00 Z";
        String timeZoneId = "Asia/Calcutta";
        TimeZone timeZone = TimeZone.getTimeZone(timeZoneId);

        DateFormat dateFormatLocal = new SimpleDateFormat("yyyyMMdd HH:mm:ss z");
                    //This date object is given time and given timezone
        java.util.Date parsedDate = dateFormatLocal.parse(date + " "  
                         + timeZone.getDisplayName(false, TimeZone.SHORT));

        if (timeZone.inDaylightTime(parsedDate)) {
            // We need to re-parse because we don't know if the date
            // is DST until it is parsed...
            parsedDate = dateFormatLocal.parse(date + " "
                    + timeZone.getDisplayName(true, TimeZone.SHORT));
        }

       //assigning to the java.sql.TimeStamp instace variable
        obj.setTsSchedStartTime(new java.sql.Timestamp(parsedDate.getTime()));

Зберігати в БД

        if (tsSchedStartTime != null) {
            stmt.setTimestamp(11, tsSchedStartTime);
        } else {
            stmt.setNull(11, java.sql.Types.DATE);
        }

ВИХІД

DB (oracle) зберігає те саме, що dateTime: "20121225 10:00:00не в UTC.

Я підтвердив із наведеного нижче sql.

     select to_char(sched_start_time, 'yyyy/mm/dd hh24:mi:ss') from myTable

Мій сервер БД також працює в тому самому часовому поясі "Азія / Калькутта"

Це дає мені наступні види

  1. Date.getTime() не в UTC
  2. Або Timestamp має вплив на часовий пояс під час зберігання в БД. Що я тут роблю не так?

Ще одне питання:

Буде чи timeStamp.toString()друкувати в локальній часовій зоні , як java.util.dateробить? Не UTC?


4
Щоб відповісти на запитання у вашому заголовку, ні, java.sql.Timestamp базується на UTC. Відображення позначки часу залежить від часового поясу.
Gilbert Le Blanc

@Gilbert Le Blanc Дякую !. Не могли б ви відповісти мені "new java.sql.Timestamp (parsedDate.getTime ())" поверне об'єкт часової позначки GMT для мого вводу?
Канагавелу Сугумар

1
@GilbertLeBlanc Хоча java.sql.Timestampперебуває в UTC, зберігаючи в полі TIMESTAMP без інформації про часовий пояс, він використовує еквівалентну дату / час у поточному часовому поясі віртуальної машини
Mark Rotteveel

@Kanagavelu Sugumar: Так. метод getTime класу Date повертає кількість мілісекунд з 1 січня 1970 р., 00:00:00 GMT.
Gilbert Le Blanc

@MarkRotteveel Марк Я не зрозумів вашу думку "зберігати в полі TIMESTAMP без інформації про часовий пояс, він використовує еквівалентну дату / час у поточному часовому поясі віртуальної машини". Не могли б Ви детальніше пояснити свою відповідь. Мені це буде корисніше.
Канагавелу Сугумар

Відповіді:


105

Хоча це прямо не вказано, setTimestamp(int parameterIndex, Timestamp x)водії повинні дотримуватися правил, встановлених setTimestamp(int parameterIndex, Timestamp x, Calendar cal)javadoc :

Встановлює призначений параметр на задане java.sql.Timestampзначення, використовуючи даний Calendarоб'єкт. Драйвер використовує Calendarоб'єкт для побудови TIMESTAMPзначення SQL , яке потім драйвер надсилає до бази даних. За допомогою Calendarоб’єкта драйвер може обчислити позначку часу з урахуванням спеціального часового поясу. Якщо не Calendarвказано жодного об’єкта, драйвер використовує часовий пояс за замовчуванням, тобто віртуальну машину, на якій запущено додаток.

Під час дзвінка за setTimestamp(int parameterIndex, Timestamp x)допомогою драйвера JDBC використовується часовий пояс віртуальної машини для обчислення дати та часу позначки часу в цьому часовому поясі. Ця дата та час є тим, що зберігається в базі даних, і якщо стовпець бази даних не зберігає інформацію про часовий пояс, то будь-яка інформація про зону втрачається (це означає, що програма (програми), що використовують базу даних, можуть використовувати один і той же часовий пояс послідовно або придумайте іншу схему для розрізнення часового поясу (тобто зберігайте в окремій колонці).

Наприклад: Ваш місцевий часовий пояс GMT + 2. Ви зберігаєте "2012-12-25 10:00:00 UTC". Фактичне значення, що зберігається в базі даних, - "2012-12-25 12:00:00". Ви отримуєте його знову: ви повертаєте його знову як "2012-12-25 10:00:00 UTC" (але лише якщо ви отримуєте його за допомогою getTimestamp(..)), але коли інша програма отримує доступ до бази даних у часовому поясі GMT + 0, отримати часову позначку як "2012-12-25 12:00:00 UTC".

Якщо ви хочете зберегти його в іншому часовому поясі, вам потрібно використовувати setTimestamp(int parameterIndex, Timestamp x, Calendar cal)з екземпляром календаря в необхідному часовому поясі. Просто переконайтеся, що ви також використовуєте еквівалентний геттер з тим самим часовим поясом під час отримання значень (якщо TIMESTAMPу вашій базі даних використовується інформація без часового поясу).

Отже, припускаючи, що ви хочете зберегти фактичний часовий пояс GMT, вам потрібно використовувати:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
stmt.setTimestamp(11, tsSchedStartTime, cal);

З JDBC 4.2 сумісний драйвер повинен підтримувати java.time.LocalDateTimejava.time.LocalTime) протягом TIMESTAMPTIME) до get/set/updateObject. У java.time.Local*класи без часових поясів, тому перетворення не повинно застосовуватися (хоча це може відкрити новий набір проблем , якщо ваш код навіть припустити певний часовий пояс).


Я хочу зберігати свій час у GMT. Я вважаю, що мітка часу в GMT. То що мені робити? це setTimestamp (int parameterIndex, Timestamp tzGmtObj, Calendar calGmtObj) ??
Канагавелу Сугумар

якщо "setTimestamp (int parameterIndex, Timestamp x)" використовує часовий пояс віртуальної машини. яка роль x у коді?
Канагавелу Сугумар

1
@KanagaveluSugumar Це фактичне значення мітки часу, яке потрібно встановити. Драйвер використовує об'єкт "Календар" лише для інформації про часовий пояс, а не для його значення!
Mark Rotteveel

1
Чудово, це працює :) Я витрачаю багато часу, щоб довести (Date / Timestamp) .getTime () містить часовий пояс, визначений часовим поясом, у мілісекундах. Тепер я зрозумів, що це не так. Це завжди дає UTC мілісекунди незалежно від часового поясу. Але (Date / Timestamp) .toString () залежить від часового поясу. Дуже дякую.
Канагавелу Сугумар

4
toString()На java.lang.Datejava.lang.Timestamp) завжди буде представити його в вашому часовому поясі.
Mark Rotteveel

35

Я думаю, що правильною відповіддю має бути java.sql. Мітка часу - це складений файл java.util.Date та окреме значення в наносекундах. Інформація про часовий пояс у цьому класі відсутня. Таким чином, як і Date, цей клас просто утримує кількість мілісекунд з 1 січня 1970 р., 00:00:00 GMT + нанос.

У PreparedStatement.setTimestamp (int parameterIndex, Timestamp x, Calendar cal) Календар використовується драйвером для зміни часового поясу за замовчуванням. Але Timestamp все ще тримає мілісекунди в GMT.

API незрозуміло, як саме драйвер JDBC повинен використовувати Календар. Здається, постачальники не соромляться, як їх інтерпретувати, наприклад, востаннє, коли я працював із календарем MySQL 5.5, драйвер просто ігнорував Календар як у PreparedStatement.setTimestamp, так і в ResultSet.getTimestamp.


Зацікавлений коментар щодо MySQL 5.5
Олексій

2
Я вважаю, що це помилково, починаючи з Java 8+. Усередині відмітки часу (оскільки вона поширюється від java.util.Date) є атрибут календаря, який має атрибут zoneinfo, який насправді містить часовий пояс. Ви все ще можете отримати час UTC за допомогою .getTime (), оскільки мітка часу також зберігає його, але він МАЄ інформацію про часовий пояс.
Хорхе. V

1
ви маєте на увазі, що вона містить кількість мілісекунд з 1 січня 1970 року, 00:00:00 UTC, а не GMT. GMT має літній час. Епоха UNIX становить мілісекунди з 1 січня 1970 року, 00:00 UTC.
Кірбі

6

Для Mysql ми маємо обмеження. У драйвері Mysql doc ми маємо:

Нижче наведено деякі відомі проблеми та обмеження для MySQL Connector / J: Коли Connector / J отримує мітки часу для переходу на літній час (DST) за допомогою методу getTimeStamp () у наборі результатів, деякі з повернутих значень можуть бути неправильними. Помилок можна уникнути, використовуючи такі варіанти підключення при підключенні до бази даних:

useTimezone=true
useLegacyDatetimeCode=false
serverTimezone=UTC

Отже, коли ми не використовуємо ці параметри, а дзвонимо setTimestamp or getTimestamp дзвонимо з календарем або без календаря, ми маємо мітку часу в часовому поясі jvm.

Приклад:

Часовий пояс jvm - GMT + 2. У базі даних є мітка часу: 1461100256 = 19/04/16 21: 10: 56,000000000 GMT

Properties props = new Properties();
props.setProperty("user", "root");
props.setProperty("password", "");
props.setProperty("useTimezone", "true");
props.setProperty("useLegacyDatetimeCode", "false");
props.setProperty("serverTimezone", "UTC");
Connection con = DriverManager.getConnection(conString, props);
......
Calendar nowGMT = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
Calendar nowGMTPlus4 = Calendar.getInstance(TimeZone.getTimeZone("GMT+4"));
......
rs.getTimestamp("timestampColumn");//Oracle driver convert date to jvm timezone and Mysql convert date to GMT (specified in the parameter)
rs.getTimestamp("timestampColumn", nowGMT);//convert date to GMT 
rs.getTimestamp("timestampColumn", nowGMTPlus4);//convert date to GMT+4 timezone

Перший метод повертає: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Другий метод повертає: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Третій метод повертає: 1461085856000 = 19/04/2016 - 17:10:56 GMT

Замість Oracle, коли ми використовуємо ті самі дзвінки, у нас є:

Перший метод повертає: 1461093056000 = 19/04/2016 - 19:10:56 GMT

Другий метод повертає: 1461100256000 = 19/04/2016 - 21:10:56 GMT

Третій метод повертає: 1461085856000 = 19/04/2016 - 17:10:56 GMT

NB: Не потрібно вказувати параметри для Oracle.


1
Я думаю, ви могли б для Oracle встановити часовий пояс сеансу на UTC, щоб досягти того самого перетворення, що і MySQL для некалендарного випадку. " ALTER SESSION SET TIME_ZONE = 'UTC'".
eckes

5

Це конкретно від вашого водія. Вам потрібно вказати параметр у вашій програмі Java, щоб вказати часовий пояс, який ви хочете використовувати.

java -Duser.timezone="America/New_York" GetCurrentDateTimeZone

Далі це:

to_char(new_time(sched_start_time, 'CURRENT_TIMEZONE', 'NEW_TIMEZONE'), 'MM/DD/YY HH:MI AM')

Також може бути корисним при правильній обробці перетворення. Взято звідси


ти мав на увазі, що я слідкую за stackoverflow.com/questions/2858182/… або ти хочеш встановити, щоб моя програма JVM працювала в GMT?
Канагавелу Сугумар

@KanagaveluSugumar ну це залежить. Якщо ви хочете змінити позначку часу для КОЖНОГО запиту, встановіть її під час запуску, якщо ви хочете змінити лише ДЕЯКУ (маючи на увазі що-небудь менше КОЖНОГО), то змініть кожен запит таким чином, як зазначено в повідомленні, на яке ви посилаєтесь.
Woot4Moo

5

Відповідь полягає в тому, що java.sql.Timestampце безлад і його слід уникати. Використовуйте java.time.LocalDateTimeзамість цього.

То чому це безлад? З java.sql.TimestampJavaDoc a java.sql.Timestampявляє собою "тонку обгортку, java.util.Dateяка дозволяє API JDBC ідентифікувати це як значення SQL TIMESTAMP". З java.util.DateJavaDoc, " Dateклас призначений для відображення узгодженого універсального часу (UTC)". З специфікації ISO SQL ТАЙМЕСТАР БЕЗ ЧАСОВОГО ЗОНА "- це тип даних, який є датою і часом без часового поясу". TIMESTAMP - це коротка назва TIMESTAMP БЕЗ ЧАСОВОЇ ЗОНИ. Отже, ajava.sql.Timestamp "відображає" UTC, тоді як SQL TIMESTAMP - "без часового поясу".

Оскільки java.sql.Timestampвідображає UTC, його методи застосовують перетворення. Це не спричиняє кінця плутанини. З точки зору SQL немає сенсу перетворювати значення SQL TIMESTAMP в інший часовий пояс, оскільки TIMESTAMP не має часового поясу для перетворення. Що означає конвертувати 42 у Фаренгейт? Це нічого не означає, оскільки 42 не має одиниць температури. Це просто оголене число. Подібним чином ви не можете перетворити ЧАС ВРЕМІ 2020-07-22T10: 38: 00 на Америку / Лос-Анджелес, оскільки 2020-07-22T10: 30: 00 не знаходиться в жодному часовому поясі. Це не в UTC або GMT або щось інше. Це голий час дати.

java.time.LocalDateTimeтакож є голим часом. Він не має часового поясу, як у SQL TIMESTAMP. Жоден з його методів не застосовує жодного перетворення часового поясу, що значно полегшує його передбачення та розуміння. Тому не використовуйте java.sql.Timestamp. Використовуйте java.time.LocalDateTime.

LocalDateTime ldt = rs.getObject(col, LocalDateTime.class);
ps.setObject(param, ldt, JDBCType.TIMESTAMP);

Так дуже добре сказано. Дякую.
Оле В. В.

2

Ви можете скористатися наведеним нижче способом для збереження позначки часу у базі даних, що відповідає вашій бажаній зоні / ідентифікатору зони.

ZonedDateTime zdt = ZonedDateTime.now(ZoneId.of("Asia/Calcutta")) ;
Timestamp timestamp = Timestamp.valueOf(zdt.toLocalDateTime());

Типовою помилкою людей є використання LocaleDateTimeчасової позначки того моменту, який відкидає будь-яку інформацію, вказану для вашої зони, навіть якщо ви спробуєте перетворити її пізніше. Він не розуміє Зони.

Зверніть увагу, Timestampце клас java.sql.Timestamp.


Дякую за рішення. Це спрацювало !!!
Karthik SA
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.