java.sql.Date 和 Time 时区不同

发布时间:2021-03-08 13:14
private GregorianCalendar formatDate(Date dateStatus, Time timeStatus) {
    GregorianCalendar calendar = (GregorianCalendar) GregorianCalendar.getInstance(TimeZone.getTimeZone("UTC"));
    calendar.setTime(new Date(dateStatus.getTime() + timeStatus.getTime()));
    return calendar;
}

以上代码以毫秒为单位返回日历值。但是我在本地和在 jenkins 中运行测试时得到了不同的值,导致测试用例失败。

运行在不同时区的本地和 Jenkins 服务器。

詹金斯错误:

Expected: 1554866100000
     got: 1554903900000

我该如何处理?

回答1
  1. java.sql.Date 是一个非常不幸的 API 设计混乱。该类扩展了 java.util.Date,而该类是谎言<​​/strong>。它根本代表一个日期(check the source code,如果您持怀疑态度,这是可以理解的)。它代表一个时刻,没有任何时区信息,基于 UTC 新年以来的毫秒数。 1970 't 谎言(例如 java.time.Instant因此值得怀疑:它隐含地选择一个时区并将其混合在一起,以便为您提供答案。这就是为什么大多数 Date 的方法,例如 .getYear(),都被标记为已弃用:在 java 核心库中,弃用标记通常实际上并不意味着“这将很快被删除”,它实际上意味着:“这是根本就坏了,你永远不应该调用这个方法”。 (参见:Thread.stop)。

  2. 尽管如此,JDBC API(您用来与数据库通信的)是建立在此之上的; java.sql.Date 以及 java.sql.Timestamp 扩展 java.util.Date 从而继承混乱。这意味着以这种方式处理日期会将数据库中具有完整时区信息的时间戳转换为无时区结构,然后在 Java 端您可以添加新的时区,这是一种糟糕的处理方式。< /p>

  3. 不幸的是,日期/时间极其复杂(见下文),并且数据库有截然不同的存储方式;通常是多个略有不同的日期/时间类型,例如“TIMESTAMP”、“TIME WITH TIME ZONE”等。

  4. 因此,无论上下文如何,都没有独特的建议:正确的答案取决于您的 JDBC 驱动程序版本、数据库引擎、数据库引擎版本和需求。这意味着最好的方法通常是首先了解基本原理,以便您能够适应并找出最适合您特定情况的方法。

  5. java.util.Calendar 甚至更糟。又是一个谎言(它代表时间。不是日历!),API 的设计非常糟糕(它非常不像 Java)。这第二次尝试日期/时间 API 导致另一个日期/时间库 (java.time) 是有原因的:这很糟糕。不要使用它。

那么,让我试着解释一下基础知识:

你喜欢早上 8 点起床。现在是中午,你检查你的手机。上面写着“下一个闹钟将在 20 小时后响起”。您现在在阿姆斯特丹的史基浦机场搭乘协和式飞机,向西飞到纽约。飞行需要3个小时。落地后,查看手机。应该说“下一个闹钟将在 17 小时后响起”(飞行时间已经过去了 3 小时),还是应该说:“下一个闹钟将在 23 小时内响起”(您实际上是在早上 9 点降落在纽约时间,因为它比阿姆斯特丹早 6 小时,所以距当地时间早上 8 点还有 23 小时)。正确答案大概是:23 小时。这需要“本地时间”的概念:以年、月、日、小时、分钟和秒表示的时间点 - 但没有时区,甚至不是“请为我假设一个时区”,而是概念“......无论你现在在哪里”。

在您登机之前,您在阿姆斯特丹的一家理发店预约了您返回的时间。您已于 2022 年 3 月 8 日 12:00 完成。当您查看手机时,上面写着:“365 天、0 小时、0 分钟和 0 秒”,因为您已预约。如果你飞往纽约,那现在应该是“364 天 21 小时 0 分 0 秒”:你目前在纽约的事实与任何情况都没有关系。您会认为自 UTC 时间以来的毫秒数应该足够了,但事实并非如此:想象一下荷兰废除了夏令时(疯狂的想法?No, quite likely actually)。木槌敲击桌子的那一刻,法律通过荷兰将在 2021 年切换到夏令时,然后永远留在那里,就在那一刻?您手机上“预约理发师前的时间”的指示器应立即增加 1 小时(或减少?)。因为那个理发师约会不会自己重新安排在 11:00 或 13:00。

在您的飞行过程中,您在飞机穿越大西洋之前拍了一张郁金香田的照片。这张照片的时间戳是另一个时间概念:如果荷兰决定以某种方式追溯应用时区更改,那么时间戳记为“这张照片拍摄于 2021 年 3 月 8 日,阿姆斯特丹时间 12:05” 应该立即弹出并改为阅读 13:05 或 11:05。这样就不像理发师预约了。

在回答这个问题之前,您需要弄清楚您的情况归结为 3 个不同的时间概念中的哪一个。

只有 java.time 包完全涵盖了这一切。分别:

  • 闹钟时间由 LocalDateLocalTimeLocalDateTime 表示。

  • 预约时间由 ZonedDateTime 表示。

  • 我什么时候制作的图片时间用Instant表示。

java.sql.Date 类型最等同于 Instant,这很奇怪,因为您会认为这更像是 java.sql.Timestamp 的辖区。

语用学,如何完成工作

您的第一站是全面了解您的数据库引擎的数据类型。例如,在 postgres 中:

<头>
概念 java.time 类型 postgres 类型
闹钟 java.time.LocalTime 没有时区的时间
- java.time.LocalDate 日期
- java.time.LocalDateTime 没有时区的时间戳
约会 java.time.ZonedDateTime 带时区的时间戳
我拍的时候 java.time.Instant 没有合适的类型