java - Hibernate 不提供 UTC 时区的日期

标签 java hibernate datetime

这似乎是一个愚蠢的问题,但我无法理解这种令人毛骨悚然的行为。我完全知道 java Date 类没有在其中存储任何 TimeZone 信息。它只存储自 1970 年 1 月 1 日格林威治标准时间 00:00:00 以来的毫秒数

事实是,我使用的是驻留在具有 UTC 时区的服务器上的 MySql,并且我也仅在 UTC 中存储 DateTime。如果我进行选择查询,那么我会得到这个日期 2014-01-17 16:15:49
通过使用 http://www.epochconverter.com/ 我得到了这个:

Epoch timestamp: 1389975349
Timestamp in milliseconds: 1389975349000
Human time (GMT): Fri, 17 Jan 2014 16:15:49 GMT
Human time (your time zone): Friday, January 17, 2014 9:45:49 PM

现在是 Hibernate 的部分。我在以 IST 作为系统时区的机器上运行我的 Java Web 应用程序。我使用 Id 做了一个简单的对象获取,并获取了 createdDate 属性,它是一个 Date 对象。我写了一个简单的代码来理解它的输出,这里是代码:

Date dt = c.getCreatedDate();
System.out.println(dt.getTime());
System.out.println(dt);
DateFormat df = new SimpleDateFormat("dd/MM/yyyy  hh:mm a z");
df.setTimeZone(TimeZone.getTimeZone("IST"));
System.out.println(df.format(dt));
df.setTimeZone(TimeZone.getTimeZone("UTC"));
System.out.println(df.format(dt));

以下是此操作的输出:

1389955549000
2014-01-17 16:15:49.0
17/01/2014  04:15 PM IST
17/01/2014  10:45 AM UTC

如果将此 1389955549000 放在 http://www.epochconverter.com/ 中,则会得到以下输出:

GMT: Fri, 17 Jan 2014 10:45:49 GMT
Your time zone: Friday, January 17, 2014 4:15:49 PM GMT+5.5

这不是预期的输出,对吧。它以毫秒为单位提供时间,距 UTC 时间为 -5:30,因此如果我尝试在 IST 时区中获取时间,那么它实际上为我提供了 UTC 时间

有没有人知道我做错了什么?

-------------------------- 我是如何修复它的 ------------------ ----------

基于来自 - AkoBasil Bourque 的建议

Hibernate 在从数据库中获取日期/时间字段时会考虑系统时区。如果您已将 DateTime 存储在 UTC 数据库中,但您的系统时间或至少您的 java 应用程序时区位于其他时区(例如,IST - 印度标准时间),则 hibernate 认为存储在数据库中的 DateTime 也在 IST 中,这是是什么导致了整个问题。

为此,正如 Basil 建议的那样,在不同的服务器上使用相同的时区。 UTC 应该是首选。我应用的修复是我添加了以下代码:
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));

ServletContextListener 中,它使我的应用程序时区在 UTC 中,现在 hibernate 正在按预期获取和存储日期。

最佳答案

令人困惑的问题

你的问题可以使用一些重写。
If I make a select query – 你应该解释这一点并给出准确的查询语句。

红鲱鱼

由于处理两个独立的服务器(数据库服务器、Web 应用程序服务器),每个服务器都有不同的时区设置,因此您应该更清晰地分离问题。确实,MySQL 服务器似乎只是一个红鲱鱼,分散注意力。

与其谈论无关的 MySQL 服务器,您应该测试并报告实际时间。很容易做到......只需谷歌“UTC当前时间”。

因此,老水手的格言是:使用一个或三个指南针,但永远不要使用两个。

时区

三字母时区代码已经过时,既不标准化也不唯一。例如,“IST”表示“印度标准时间”和“爱尔兰标准时间”。

使用时区名称,如 slightly outdated list 中所示。在您的情况下,+05:30,您可以使用“Asia/Kolkata”,在旧表中也称为“Asia/Calcutta”。这比 UTC/GMT 早 5.5 小时。

乔达时间

正如您所看到的,java.util.Date 和 Calendar 类是出了名的糟糕和困惑。避开它们。使用开源第三方 Joda-Time 或在 Java 8 中使用新的 java.time.* classes(受 Joda-Time 启发)。

下面的代码在美国西海岸时区的 Mac 上使用 Joda-Time 2.3 和 Java 8。

基线

让我们确定 1389975349000L ≈ 16:15 UTC ≈ 21:45 印度。

正如问题所述,这与 EpochConverter.com 一致。

// Specify a time zone rather than depend on defaults.
DateTimeZone timeZoneKolkata = DateTimeZone.forID( "Asia/Kolkata" );

long millis = 1389975349000L;
DateTime dateTimeUtc = new DateTime( millis, DateTimeZone.UTC );
DateTime dateTimeKolkata = dateTimeUtc.toDateTime( timeZoneKolkata );

转储到控制台...

System.out.println( "millis: " + millis );
System.out.println( "dateTimeUtc: " + dateTimeUtc );
System.out.println( "dateTimeKolkata: " + dateTimeKolkata );

运行时…

millis: 1389975349000
dateTimeUtc: 2014-01-17T16:15:49.000Z
dateTimeKolkata: 2014-01-17T21:45:49.000+05:30

神秘号码

问题提到了第二个数字:1389955549000L。

结果证明该数字与第一个数字的日期相同,但时间不同。

让我们确定 1389955549000L ≈ 10:45 UTC ≈ 16:15 印度。

long mysteryMillis = 1389955549000L;
DateTime mysteryUtc = new DateTime( mysteryMillis, DateTimeZone.UTC );
DateTime mysteryKolkata = mysteryUtc.toDateTime( timeZoneKolkata );

转储到控制台...

System.out.println( "mysteryMillis: " + mysteryMillis );
System.out.println( "mysteryUtc: " + mysteryUtc );
System.out.println( "mysteryKolkata: " + mysteryKolkata );

运行时…

mysteryMillis: 1389955549000
mysteryUtc: 2014-01-17T10:45:49.000Z
mysteryKolkata: 2014-01-17T16:15:49.000+05:30

结论

我不是 100% 确定,但是……

→ 您的 Web 应用服务器计算机的时钟设置似乎不正确,设置为 UTC 时间而不是印度时间。

网络应用程序服务器显然是在印度时区的 16:15 时间,但显然当时印度的真实时间是 21:45。换句话说,时间与时区不匹配。

将 UTC 时间与非 UTC 时区混合 = 错误。

如果您设置印度时区,则设置印度时间以匹配。

细节

请注意,我们在两组数字中都有共同的“16:15”。

java.util.Date 类的设计非常糟糕,它本身没有时区信息 但是 在其 toString 的实现中应用了 Java 虚拟机的默认时区。这是避免此类的众多原因之一,但可能是您问题的关键。

得到教训

避免使用 java.util.Date、java.util.Calendar 和 java.text.SimpleDateFormat 类。仅在需要时使用,以根据需要与其他类交换值。

使用 Joda-Time 或新的 java.time.* 类 (JSR 310)。

指定时区,不要依赖默认时区。

将时间与每个服务器上的时区相匹配。通过谷歌搜索“UTC 中的当前时间”进行验证。

尽可能将主机服务器的操作系统时区设置为 UTCGMT。在某些操作系统上没有这样的选择,所以选择“Atlantic/Reykjavik”作为一种解决方法,因为冰岛全年保持 UTC/GMT,没有任何夏令时废话。

不要通过调用 TimeZone.setDefault 来设置 JVM 的默认时区(除非在最坏的情况下作为最后的手段)。设置默认值是粗鲁的,因为它会立即影响在该 JVM 中运行的所有应用程序的所有线程中的所有代码。它是不可靠的,因为任何其他代码都可以在运行时在您的应用程序上更改它。相反, 在所有日期时间代码中指定一个时区 。永远不要隐含地依赖 JVM 的当前默认值。 Joda-Time 和 java.time 都有接受时区参数的方法。因此,虽然将所有服务器计算机的主机操作系统的时区设置为 UTC 是一种很好的做法,但您永远不应该在 Java 代码中依赖它。

关于java - Hibernate 不提供 UTC 时区的日期,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21193516/

相关文章:

hibernate - JPA/Hibernate 异常处理

c# - 从数据库中读取 SQLite DateTime 值并将其分配给 C# 字符串变量

c# - 在 C# 中获取三个月(季度)代码

java - Youtube API V3 Java 无需调用浏览器即可上传视频

java - 如何提高使用 Spring 将 REF CURSOR 检索到 Java 中的性能?

java - 如何在 android 中从 Arraylist 日期按降序对日期进行排序?

java - 如何在hibernate中实现继承?

java - 使用Tomcat的ManagerServlet重新加载servlet资源

java - 与 hibernate 注解的接口(interface)

php - 使用 JavaScript 从字符串创建对象 Date