簡體   English   中英

日期時間字段或時區問題的瘋狂 MS SQL Server JDBC 驅動程序?

[英]Crazy MS SQL Server JDBC driver at datetime field or time zone issue?

在調試過程中,我以簡單的例子結束:

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 --date=@1429068600 +%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();

執行結果(我使用 Coreutils date實用程序仔細檢查結果):

=> getTimestamp().getTime(): 1429057800000

$ TZ=UTC date --date=@1429057800 +%F_%T
2015-04-15_00:30:00

=> getDate().getTime(): 1429045200000

$ TZ=UTC date --date=@1429045200 +%F_%T
2015-04-14_21:00:00

日期類型和 JDBC Java 映射的官方文檔沒有說明結果差異......

我的程序在GMT+03:00時區執行,我有 SQL Server 2008 並嘗試使用來自https://msdn.microsoft.com/en-us/sqlserver/aa937724.aspx 的JDBC 驅動程序 4.0 和 4.1

我希望在所有情況下都能獲得 UTC 時間戳(從 1970 年開始),這僅適用於我用來交互式調試查詢的 Linux ODBC tsql實用程序。

跆拳道?

您的第一對查詢計算自紀元(本地)日期本地午夜以來的秒數。 這種差異在任何時區都是相同的,因此當您將數據庫時區設置為 UTC 並將先前確定的偏移量轉換回時間戳時,您將獲得“相同”的日期和時間,因為數字匹配,但它們代表不同的絕對時間,因為它們相對於不同的 TZ。

當您通過 JDBC 執行查詢時,您正在計算數據庫時區中的Timestamp ,GMT+03:00。 java.sql.Timestamp表示絕對時間,但是,表示為在紀元之交時格林威治標准時間午夜的偏移量。 JDBC 驅動程序知道如何進行補償。 因此,您隨后記錄的是1970-01-01 00:00:00 GMT+00:002015-04-15 03:30:00 GMT+03:00

getDate().getTime()版本不太清楚,但似乎當您將時間戳作為Date檢索時,從而截斷時間部分,截斷是相對於數據庫時區執行的。 之后,與另一種情況類似, java.sql.Date.getTime()返回從紀元輪到結果絕對時間的偏移量。 也就是說,它正在計算1970-01-01 00:00:00 GMT+00:002015-04-15 00:00:00 GMT+03:00之間的差異

這都是一致的。

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);

從 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);
}

因此datetime兩個 4 字節單詞表示從 1900 年開始的天數和一天中的毫秒數,並且該數據設置為Calendar

很難檢查Calendar是從哪里來的。 但是代碼顯示可能正在使用本地 Java TZ(查看TimeZone.getDefault() )。

如果我們假設 DB 在 UTC 中保持從 1900 年開始的時間,則您需要應用 TZ 校正以從 Java 端的 UTC 中的 JDBC 驅動程序獲得long getTimestamp().getTime()結果,因為驅動程序假定來自 DB 的本地時間。

重要更新我在我的應用程序開始時嘗試將默認語言環境設置為 UTC:

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

並從getTimestamp().getTime()獲取 UTC 毫秒。 那是一場勝利! 我可以避免使用可怕的 SQL Server 日期算法來獲取 1970 年的秒數。

正如其他答案中提到的,JDBC 驅動程序將調整日期/時間到/從 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
        }
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM