为什么JDBC在保存日期时会将时间调整为默认时区?

sup*_*sky 7 java timezone date jdbc ojdbc

这个问题与有关,但这个具体问题侧重于原因。所以不,这不是重复的。

引用答案:

问题是 Java Date 对象不存储时区。该值始终采用 UTC,并在给定的时区(通常是 JVM 的默认时区)中解析和格式化。

Oracle DATE 列也没有时区存储,但应表示用户看到的日期。在 99.99% 的情况下,这意味着 JVM 的默认时区中的日期。

因此,JDBC 驱动程序采用 UTC 格式的时间戳/日期值,将其转换为默认时区,并将其保存到数据库中。

  1. 不按原样调整和保存值 (UTC) 到底有什么问题?
  2. 它试图通过在将值保存到数据库之前调整值来解决什么问题?

这些问题的答案就是为什么。

我看不到设计的好处,我只能看到与之相关的问题。一个恰当的例子是在特定时区完成保存而在另一个时区完成检索。在这个特定主题上抛出的问题数量正好证明了我的观点。

所以最终的问题是,为什么它是这样设计的?原因是什么?

Bas*_*que 10

日期时间处理是一个非常复杂的话题。作为程序员,我们对时间的直观理解对我们不利,使这个主题难以掌握。此外,旧数据库和旧类中糟糕的日期时间处理使工作更加混乱。

时间

首先,避免与最早版本的 Java 捆绑在一起的糟糕的旧日期时间类。切勿使用java.util.Datejava.util.Calendarjava.sql.Timestamp或其他相关类。仅使用java.time类。如果您必须与尚未更新为java.time 的旧代码交互,请调用添加到旧类的新转换方法。

Date被替换了Instant

问题是 Java Date 对象不存储时区。

不对。An Instant(和 a Date)始终采用 UTC。现代类和遗留类都表示自 UTC 时间 1970 年第一时刻 1970-01-01T00:00:00Z 以来的小数秒计数。总是在 UTC,easy-peasy。

Oracle DATE 列的存储也没有时区,

真的。

所述的OracleDATE数据类型表示仅与时区中的日期,但缺乏时区的任何概念或偏移从-UTC。这显然是一种遗留类型,在 SQL 标准定义一些基本的日期时间类型之前创建。在标准中,TIMESTAMP WITHOUT TIME ZONE可能映射接近 Oracle DATE

但应该代表用户看到的日期。在 99.99% 的情况下,这意味着 JVM 的默认时区中的日期。

我不明白作者这句话是什么意思。我认为这是他们笨拙的说法,任何类似于 SQL 标准的类型TIMESTAMP WITHOUT TIME ZONE只是按原样采用任何给定日期或日期与时间,而不尝试在区域或偏移量之间进行调整。因此,如果您在 2018 年 1 月 21 日中午通过,它会存储一个与此字符串等效的值,2018-01-23T12:00而不管是魁北克蒙特利尔的中午还是印度加尔各答的中午(两个不同的时刻,相隔几个小时)。

因此,JDBC 驱动程序采用 UTC 格式的时间戳/日期值,将其转换为默认时区,并将其保存到数据库中。

虽然此处未指定 JDBC 驱动程序,但我怀疑这是它的行为。这种行为将与DATE没有这种区域调整的 Oracle类型的行为相矛盾。OracleDATE类型(在我阅读文档时;我不是 Oracle 用户)是不可知的或不知道区域/偏移量。

在 Java 中,映射到 SQL 标准TIMESTAMP WITHOUT TIME ZONE和 Oracle 的类DATELocalDateTime. 您应该仅在以下三种情况下使用这些无区域类型:

  • 区域或偏移是未知的
    这不好。这是错误的数据。类似于在不知道货币的情况下拥有价格/成本。您应该拒绝此类数据,而不是存储它。
  • 原意是“无处不在”,如,每个时区。
    例如,公司政策规定“我们所有的工厂都将在 12:30 吃午饭”意味着德里的工厂比杜塞尔多夫的工厂提前几个小时休息,而杜塞尔多夫的工厂比底特律的工厂提前几个小时。
  • 打算在未来的特定时刻,但我们害怕政治家重新定义时区
    政府以惊人的频率改变其时区的规则,而且几乎没有警告,甚至根本没有警告。因此,如果您想在某个日期的下午 3 点进行预约,并且您的意思是下午 3 点,而不考虑政府在此期间可能做出的任何疯狂决定,那么请存储LocalDateTime. 要打印报告或显示日历,请动态应用时区 ( ZoneId) 以生成特定时刻 (ZonedDateTimeInstant)。这必须即时完成,而不是存储值。

不按原样调整和保存值 (UTC) 到底有什么问题?

JDBC驱动程序应该不是做给UTC任何调整的这类OracleDate或SQL标准TIMESTAMP WITHOUT TIME ZONE

如果 2018-06-06T21:53Z 的两个用户,一个在魁北克,一个在印度,同时将他们自己狭隘的挂钟时间的当前时刻保存到 SQL-standardTIMESTAMP WITHOUT TIME ZONE或 Oracle类型的列中DATE,那么我们应该看到两个具有值的行:

  • 2018-06-06T17:53(注意日期是“昨天”)
  • 2018-06-07T03:23(注意日期是“明天”)

这些值不同是因为America/Montreal比 UTC 晚四个小时,而Asia/Kolkata比 UTC 早五个半小时,并且没有对时区进行调整。要再次重复自己,存储的值这里仅代表日期和时间的一天,但没有时区或偏移从-UTC,他们的任何上下文不是代表了一会儿。

混淆可能来自这样一个事实,即某些数据库(例如 Postgres)确实将传入值调整为 UTC,以便将值指向不同类型的列,即TIMESTAMP WITH TIME ZONE类型(注意WITHvs WITHOUT)。Postgres 和其他数据库使用任何传递的区域/偏移量信息来调整为 UTC 值,然后丢弃区域/偏移量信息。所以类型名称有点用词不当,您可以将其视为TIMESTAMP WITH RESPECT FOR TIME ZONE.

如果上面在 2018-06-06T21:53Z 看到的相同的两个用户将当前时刻保存到类型为 SQL 的标准列中TIMESTAMP WITH TIME ZONE,那么这两行将显示为:

  • 2018-06-06T21:53Z
  • 2018-06-06T21:53Z

所述Z在端部,发音Zulu和装置UTC。

它试图通过在将值保存到数据库之前调整值来解决什么问题?

通常,日期时间处理的最佳实践是在 UTC 而不是其他区域/偏移量中工作。

作为程序员或系统管理员工作时,忘记你自己狭隘的时区。从您自己的区域到 UTC 或其他区域来回转换会让您筋疲力尽。将 UTC 视为唯一的真实时间;所有其他区域都只是变化。

Instant级表示时间轴上的时刻UTC,分辨率为纳秒(最多小数的9个位数)。

Instant instant = Instant.now() ;  // Capture the current moment in UTC. 
Run Code Online (Sandbox Code Playgroud)

店铺。

String sql = "INSERT INTO tbl ( event ) VALUES ( ? ) ;" ;       // Writing a moment into a column of type `TIMESTAMP WTH TIME ZONE`.  
myPreparedStatement.setObject( 1 , instant ) ;                  // As of JDBC 4.2 and later, we can directly exchange java.time objects with our database.
Run Code Online (Sandbox Code Playgroud)

取回。

Instant instant = myResultSet.getObject( … , Instant.class ) ;
Run Code Online (Sandbox Code Playgroud)

在特定时区呈现那个时刻。

ZoneId z = ZoneId.of( "Asia/Kolkat" ) ;
ZonedDateTime zdt = instant.atZone( z ) ;  // Same moment, same point on the timeline, different wall-clock time.
Run Code Online (Sandbox Code Playgroud)

关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代了麻烦的旧的遗留日期时间类,例如java.util.Date, Calendar, & SimpleDateFormat

现在处于维护模式Joda-Time项目建议迁移到java.time类。

要了解更多信息,请参阅Oracle 教程。并在 Stack Overflow 上搜索许多示例和解释。规范是JSR 310

您可以直接与您的数据库交换java.time对象。使用符合JDBC 4.2或更高版本的JDBC 驱动程序。不需要字符串,不需要类。java.sql.*

从哪里获得 java.time 类?

ThreeTen-额外项目与其他类扩展java.time。该项目是未来可能添加到 java.time 的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多

  • JDBC 驱动程序实际上在写入“TIMESTAMP WITHOUT TIME ZONE”字段时会转换时间戳。这似乎是 JDBC 规范所要求的 - 请参见例如 [PostgreSQL JDBC 驱动程序的 Github 问题的答案](https://github.com/pgjdbc/pgjdbc/issues/1108#issuecomment-559508740) (3认同)