繁体   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