2015-04-16 2 views
0

В процессе отладки я заканчиваться простым примером:Сумасшедший драйвер MS SQL Server JDBC в поле даты или времени часового пояса?

select convert(datetime, '2015-04-15 03:30:00') as ts 
go 

Apr 15 2015 03:30AM 
(1 row affected) 

select convert(int, datediff(second, '1970-01-01 00:00:00', 
        convert(datetime, '2015-04-15 03:30:00'))) as ts 
go 

1429068600 
(1 row affected) 

$ TZ=UTC date [email protected] +%F_%T 
2015-04-15_03:30:00 

Когда я выполнить запрос из JDBC Я получаю 2 разных результатов не равно выше !!!! Код:

String TEST_QUERY = "select convert(datetime, '2015-04-15 03:30:00') as ts"; 
PreparedStatement stmt2 = conn.prepareStatement(TEST_QUERY); 
ResultSet rs = stmt2.executeQuery(); 
rs.next(); 
logger.info("getTimestamp().getTime(): {}", rs.getTimestamp("ts").getTime()); 
logger.info("getDate().getTime(): {}", rs.getDate("ts").getTime()); 
stmt2.close(); 

результат выполнения (I перепроверить результат с Coreutils date утилиты):

=> getTimestamp().getTime(): 1429057800000 

$ TZ=UTC date [email protected] +%F_%T 
2015-04-15_00:30:00 

=> getDate().getTime(): 1429045200000 

$ TZ=UTC date [email protected] +%F_%T 
2015-04-14_21:00:00 

Official docs for date types and JDBC Java mapping ничего не говорят о результате разницы ...

Моя программа выполнена в GMT+03:00 часовом поясе и У меня есть SQL Server 2008 и попробуйте с драйверами JDBC 4.0 и 4.1 от https://msdn.microsoft.com/en-us/sqlserver/aa937724.aspx

Мое ожидание получить временную метку UTC (с 1970 года) во всех случаях, что верно только для утилиты Linux ODBC tsql, которую я использую для интерактивного отладки запросов.

WTF?

ответ

3

Ваша первая пара запросов вычисляет количество секунд с местной полуночи на (местной) дате эпохи. Это различие одинаковое в любом часовом поясе, поэтому, когда вы устанавливаете часовой пояс базы данных в UTC и конвертируете ранее установленное смещение обратно в метку времени, вы получаете «ту же» дату и время в том смысле, что цифры совпадают, но они представляют другое абсолютное время, потому что они относятся к другому TZ.

Когда вы выполняете свой запрос через JDBC, вы вычисляете Timestamp в часовой пояс базы данных, GMT + 03: 00. java.sql.Timestamp представляет собой абсолютное время, однако, выраженное как смещение от полуночи GMT на пороге эпохи. Драйвер JDBC знает, как компенсировать. Таким образом, тогда вы регистрируете разницу во времени между 1970-01-01 00:00:00 GMT+00:00 и 2015-04-15 03:30:00 GMT+03:00.

getDate().getTime() версия немного менее ясна, но, кажется, что, когда вы извлекаете метку времени в качестве Date, тем самым обрывая часть времени, то усечение выполняется относительно временной базы данных зоны. После этого, и аналогично другому случаю, java.sql.Date.getTime() возвращает смещение от поворота эпохи до конечного абсолютного времени. То есть вычисляется разность между 1970-01-01 00:00:00 GMT+00:00 и 2015-04-15 00:00:00 GMT+03:00

Все это согласовано.

+0

Могу ли я попросить разъяснения - SQL Server 'datetime' использует временную метку UTC? Он не может установить временную привязку, скорректированную на локальную зону, поскольку «дневное сохранение» нарушает значения в течение половины года. Является ли SQL Server преобразованием значения типа datetime в промежуточное значение, которое принимает TZ в учетной записи, когда оно появляется в запросе 'select'? Я не понимаю, почему драйвер JDBC читает сервер TZ и пытается компенсировать разницу. – gavenkoa

+0

Внутренний формат, в котором база данных хранит значения Timestamp, не имеет отношения к вопросу. Тем более, что вы не запрашиваете какую-либо таблицу. У вас разные результаты, потому что запросы, которые вы напрямую загружали в базу данных, * не семантически эквивалентны * в сочетании с JDBC + результатом, который вы выполняли на Java. Если вы зададите другой вопрос, вы, вероятно, получите другой ответ. –

+0

Драйверы JDBC должны использовать локальный часовой пояс JVM, запускающего приложение, а не сервер (хотя SQL Server может нарушить это правило, не уверен). –

2

С помощью JD-GUI Я изучаю двоичный код SQL Server JDBC.

rs.getTimestamp() метод приводит к:

package com.microsoft.sqlserver.jdbc; 
final class DDC { 

static final Object convertTemporalToObject(
     JDBCType paramJDBCType, SSType paramSSType, Calendar paramCalendar, int paramInt1, long paramLong, int paramInt2) { 
    TimeZone localTimeZone1 = null != paramCalendar ? 
     paramCalendar.getTimeZone() : TimeZone.getDefault(); 
    TimeZone localTimeZone2 = SSType.DATETIMEOFFSET == paramSSType ? 
     UTC.timeZone : localTimeZone1; 

    Object localObject1 = new GregorianCalendar(localTimeZone2, Locale.US); 

    ... 

    ((GregorianCalendar)localObject1).set(1900, 0, 1 + paramInt1, 0, 0, 0); 
     ((GregorianCalendar)localObject1).set(14, (int)paramLong); 

, который вызывается из Resultset читателя протокола TDS:

package com.microsoft.sqlserver.jdbc; 

final class TDSReader { 

final Object readDateTime(int paramInt, Calendar paramCalendar, JDBCType paramJDBCType, StreamType paramStreamType) 
    throws SQLServerException 
{ 
    ... 
    switch (paramInt) 
    { 
    case 8: 
    i = readInt(); 
    j = readInt(); 

    k = (j * 10 + 1)/3; 
    break; 
    ... 
    return DDC.convertTemporalToObject(paramJDBCType, SSType.DATETIME, paramCalendar, i, k, 0); 
} 

Так два 4 байта слова для datetime обозначают дни 1900 и миллисекунды в день, и что данные установлен на Calendar.

Трудно проверить, откуда именно Calendar.Но код показывает, что возможно использование локальной Java TZ (посмотрите на TimeZone.getDefault()).

Если суммировать эту информацию, можно подумать, что драйверы JDBC чисты. Если вы думаете, что время задержки DB с 1900 в UTC, вам нужно применить коррекцию TZ, чтобы получить результат long getTimestamp().getTime() от драйвера JDBC в UTC на стороне Java, потому что драйвер предполагает, что он получает локальное время от БД.

ВАЖНОЕ ОБНОВЛЕНИЕ Я принимаю эксперимент с установкой по умолчанию локаль UTC в самом начале моего приложения:

TimeZone.setDefault(TimeZone.getTimeZone("GMT+00:00")); 

и получить UTC мс от getTimestamp().getTime(). Это была победа! Я могу избежать чудовищной арифметики даты SQL Server, чтобы получить секунды с 1970 года.

1

Как уже упоминалось в других ответах, драйвер JDBC будет настраивать дату/время на/из значения по умолчанию TimeZone JVM. Это, конечно, только проблема, когда база данных и приложение работают в разных часовых поясах.

Если вам нужна дата/время в другом часовом поясе, вы можете изменить часовой пояс по умолчанию JVM (как предлагается в другом ответе), но это может быть невозможно, если ваш код запущен в приложении сервер, обслуживающий несколько приложений.

Лучшим выбором может быть явное указание требуемого часового пояса, с использованием перегруженного метода с использованием объекта Calendar.

Connection conn = ...; 
String sql = ...; 
Timestamp tsParam = ...; 

// Get Calendar for UTC timezone 
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("UTC")); 

try (PreparedStatement stmt = conn.prepareStatement(sql)) { 

    // Send statement parameter in UTC timezone 
    stmt.setTimestamp(1, tsParam, cal); 

    try (ResultSet rs = stmt.executeQuery()) { 
     while (rs.next()) { 

      // Get result column in UTC timezone 
      Timestamp tsCol = rs.getTimestamp("...", cal); 

      // Use tsCol here 
     } 
    } 
} 
+0

В моем примере я читал данные из набора результатов, case JDBC повреждает данные календаря, и я не контролирую этот процесс. – gavenkoa

+0

Хранение временных данных в формате, зависящем от локали, приводит к плохой арифметической оценке даты или приводит к ошибочному вычислению (причина исторической региональной политики, корректировки дневной экономии). По этой причине SQL Server должен хранить дату в UTC. – gavenkoa

+0

Поставка объекта «Календарь» не будет применять часовой пояс к сохраненному значению в базе данных. Значение в базе данных столбца 'datetime' не имеет часового пояса, поэтому оно неявно находится в часовом поясе сервера базы данных. Объект «Календарь» контролирует, как драйвер JDBC конвертирует между часовым поясом базы данных и часовым поясом метки времени. – Andreas

Смежные вопросы