这似乎是一个愚蠢的问题,但我无法理解这种令人毛骨悚然的行为。我完全知道 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 时间
有没有人知道我做错了什么?
-------------------------- 我是如何修复它的 ------------------ ----------
基于来自 - Ako 和 Basil 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 中的当前时间”进行验证。
尽可能将主机服务器的操作系统时区设置为
UTC
或 GMT
。在某些操作系统上没有这样的选择,所以选择“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/