SQL DATE与java.sql.Date中的时区

Luk*_*der 37 java sql timezone date jdbc

我对SQL DATE数据类型的行为感到有些困惑java.sql.Date.以下语句为例:

select cast(? as date)           -- in most databases
select cast(? as date) from dual -- in Oracle
Run Code Online (Sandbox Code Playgroud)

让我们用Java准备并执行该语句

PreparedStatement stmt = connection.prepareStatement(sql);
stmt.setDate(1, new java.sql.Date(0)); // GMT 1970-01-01 00:00:00
ResultSet rs = stmt.executeQuery();
rs.next();

// I live in Zurich, which is CET, not GMT. So the following prints -3600000, 
// which is CET 1970-01-01 00:00:00
// ... or   GMT 1969-12-31 23:00:00
System.out.println(rs.getDate(1).getTime());
Run Code Online (Sandbox Code Playgroud)

换句话说,我绑定到语句的GMT时间戳成为我回来的CET时间戳.添加时区的步骤是什么?为什么?

注意:

  • 我已经观察到这对于任何这些数据库都是如此:

    DB2,Derby,H2,HSQLDB,Ingres,MySQL,Oracle,Postgres,SQL Server,Sybase ASE,Sybase SQL Anywhere

  • 我发现这对SQLite来说是假的(它实际上没有真正的DATE数据类型)
  • 当使用java.sql.Timestamp而不是使用时,所有这些都是无关紧要的java.sql.Date
  • 这是一个类似的问题,但不回答这个问题:java.util.Date vs java.sql.Date

Dan*_*ruz 40

JDBC规范不问候时区定义的任何细节.尽管如此,我们大多数人都知道必须处理JDBC时区差异的痛苦; 只需查看所有StackOverflow问题!

最终,日期/时间数据库类型的时区处理归结为数据库服务器,JDBC驱动程序以及介于两者之间的所有内容.你甚至受到JDBC驱动程序错误的支配; PostgreSQL的一个固定在8.3版本的bug哪里

传递Calendar对象的Statement.getTime,.getDate和.getTimestamp方法正在以错误的方向旋转时区.

使用new Date(0)(假设您使用的是Oracle JavaSE)创建新日期时java.sql.Date,将创建日期

使用给定的毫秒时间值.如果给定的毫秒值包含时间信息,则驱动程序将时间组件设置为默认时区(运行应用程序的Java虚拟机的时区)中与零GMT对应的时间.

所以,new Date(0)应该使用GMT.

当您调用时ResultSet.getDate(int),您正在执行JDBC实现.JDBC规范没有规定JDBC实现应该如何处理时区细节; 所以你受到了实施的支配.查看Oracle 11g oracle.sql.DATEJavaDoc,Oracle DB似乎不存储时区信息,因此它会执行自己的转换以获取日期java.sql.Date.我有一个Oracle数据库的经验,但我的JDBC实现使用服务器的和本地JVM的时区设置,从做转换猜测oracle.sql.DATEjava.sql.Date.


您提到多个RDBMS实现正确处理时区,但SQLite除外.让我们看一下当您将日期值发送到JDBC驱动程序以及从JDBC驱动程序获取日期值时H2SQLite如何工作.

H2 JDBC驱动程序PrepStmt.setDate(int, Date)使用ValueDate.get(Date)哪个调用DateTimeUtils.dateValueFromDate(long)进行时区转换.

使用此SQLite JDBC驱动程序,PrepStmt.setDate(int, Date)调用PrepStmt.setObject(int, Object)并且不进行任何时区转换.

H2 JDBC驱动程序JdbcResultSet.getDate(int)返回get(columnIndex).getDate().get(int)返回Value指定列的H2 .由于列类型是DATE,H2使用ValueDate.ValueDate.getDate()调用DateTimeUtils.convertDateValueToDate(long),最终创建java.sql.Date一个时区转换后.

使用这个SQLite JDBC驱动程序,RS.getDate(int)代码更简单; 它只返回java.sql.Date使用long存储在数据库中的日期值.

因此,我们看到H2 JDBC驱动程序在处理带日期的时区转换方面非常聪明,而SQLite JDBC驱动程序则没有(不是说这个决定不聪明,它可能很适合SQLite设计决策).如果你追查你提到的其他RDBMS JDBC驱动程序的源代码,你可能会发现大多数都以与H2相似的方式接近日期和时区.

虽然JDBC规范没有详细说明时区处理,但RDBMS和JDBC实现设计人员考虑时区并正确处理它是很有意义的.特别是如果他们希望他们的产品在全球舞台上销售.这些设计师非常聪明,即使在没有具体规范的情况下,他们中的大多数都能做到这一点并不令我感到惊讶.


我发现了这个Microsoft SQL Server博客,在SQL Server 2008中使用时区数据,它解释了时区如何使事情变得复杂:

时区是一个复杂的区域,每个应用程序都需要解决您如何处理时区数据以使程序更加用户友好.

不幸的是,目前没有时区名称和价值的国际标准权威.每个系统都需要使用他们自己选择的系统,并且在有国际标准之前,尝试让SQL Server提供一个系统是不可行的,并且最终会导致比它解决的问题更多的问题.

  • 不幸的是,当您实际需要处理多个时区时,日期/时间RDBMS类型可能会非常痛苦.特别是当您将遗留应用程序投入混合时...而且它们不处理时区. (3认同)
  • 因此,从本质上讲,您是说我的所有11个测试数据库(SQLite除外)的行为均相同,这仅仅是巧合或“偶然兼容性”,而未指定?我希望不是!?但是,我同意您的理由,您也可能是正确的,但是我觉得这有点令人恐惧... :-) (2认同)

phi*_*lwb 11

是进行转换的jdbc驱动程序.它需要将Date对象转换为db/wire格式可接受的格式,并且当该格式不包含时区时,趋势是在解释日期时默认为本地计算机的时区设置.因此,考虑到您指定的驱动程序列表,最可能的情况是您将日期设置为GMT 1970-1-1 00:00:00,但是将其设置为语句时的解释日期是CET 1970-1-1 1:00:00.由于日期只是日期部分,因此您将1970-1-1(没有时区)发送到服务器并回复给您.当驾驶员获得日期,并将其作为日期访问时,它会看到1970-1-1并再次使用当地时区解释,即CET 1970-1-1 00:00:00或GMT 1969-12 -31 23:00:00 因此,与原始日期相比,您已经"丢失"了一个小时.


Jon*_*oni 5

该类java.sql.Date对应于 SQL DATE,它不存储时间或时区信息。实现这一点的方法是“规范化”日期,就像javadoc所说的那样:

为了符合 SQL DATE 的定义,java.sql.Date 实例包装的毫秒值必须通过在实例关联的特定时区中将小时、分钟、秒和毫秒设置为零来“标准化” .

这意味着,当您在 UTC+1 中工作并要求数据库提供 DATE 时,合规的实现完全符合您的观察:返回一个 java.sql.Date ,其毫秒值对应于 00:00的相关日期:00 UTC+1与数据最初如何到达数据库无关。

如果这不是您想要的,数据库驱动程序可能允许通过选项更改此行为。

另一方面,当您将 a 传递java.sql.Date给数据库时,驱动程序将使用默认时区将日期和时间组件与毫秒值分开。如果您使用0UTC+X,则 X>=0 的日期为 1970-01-01,X<0 的日期为 1969-12-31。


旁注:看到Date(long)构造函数的文档与实现不同,这很奇怪。javadoc 是这样说的:

如果给定的毫秒值包含时间信息,则驱动程序会将时间组件设置为对应于零 GMT 的默认时区(运行应用程序的 Java 虚拟机的时区)中的时间。

然而,在 OpenJDK 中实际实现的是这样的:

public Date(long date) {
    // If the millisecond date value contains time info, mask it out.
    super(date);

}
Run Code Online (Sandbox Code Playgroud)

显然,这种“屏蔽”没有实现。同样因为指定的行为没有很好地指定,例如应该将 1970-01-01 00:00:00 GMT-6 = 1970-01-01 06:00:00 GMT 映射到 1970-01-01 00:00 :00 GMT = 1969-12-31 18:00:00 GMT-6,还是到 1970-01-01 18:00:00 GMT-6?


Rol*_*olf 5

java.util.Date和Oracle/MySQL Date对象都只是一个时间点的表示,而不管位置如何.这意味着它很可能在内部存储为自"epoch"GMT 1970-01-01 00:00:00以来的毫秒/毫秒数.

当您从结果集中读取时,对"rs.getDate()"的调用会告诉结果集获取包含该时间点的内部数据,并将其转换为java Date对象.此日期对象是在本地计算机上创建的,因此Java将选择您当地的时区,即苏黎世的CET.

您看到的差异是表示的差异,而不是时间的差异.