这似乎是一个愚蠢的问题,但我无法理解这种令人毛骨悚然的行为。我完全知道java Date
类不会在其中存储任何TimeZone信息。它只存储自1970年1月1日00:00:00 GMT以来的毫秒数
事实是,我使用的是驻留在具有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会考虑系统时区。如果您已在数据库中的 UTC 中存储了DateTime
,但您的系统时间或至少您的Java应用时区位于其他时区(例如,IST - 印度标准时间),那么hibernate认为{{存储在数据库中的1}}也在IST中,这就是导致整个问题的原因。
为此建议 Basil ,在不同服务器上使用相同的时区。应该首选UTC。修复我申请的是我添加了以下代码:
DateTime
在TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
中,我的应用时区为 UTC ,现在hibernate正在按预期获取和存储日期。
答案 0 :(得分:4)
您的问题可能会使用一些重写。
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&正如你所看到的那样,日历类是非常糟糕和令人困惑的。避免他们。使用开源第三方Joda-Time或Java 8中的新java.time.* classes(受Joda-Time启发)。
以下代码在美国西海岸时区的Mac上使用Joda-Time 2.3和Java 8。
让我们确定1389975349000L≈16:15UTC≈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:45UTC≈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
我不是百分百肯定,但是......
→您的网络应用服务器计算机的时钟设置似乎不正确,设置为UTC时间而非印度时间。
网络应用服务器显然是在印度时区的16:15时间,但显然在那一刻印度的真实时间是21:45。换句话说,时间与时区不匹配。
将UTC时间与非UTC时区混合=错误。
如果您设置印度时区,请设置印度时间以匹配。
请注意,我们在两组数字中都有“16:15”。
java.util.Date类的设计非常糟糕,它本身没有时区信息 BUT 在其toString
的实现中应用Java虚拟机的默认时区。这是避免此课程的众多原因之一,但可能是您的问题的关键。
避免使用java.util.Date,java.util.Calendar和java.text.SimpleDateFormat类。仅在需要时与其他类交换值时使用。
使用Joda-Time或新的java.time。*类(JSR 310)。
指定时区,永远不要依赖默认时区。
匹配每台服务器上的时区。通过谷歌搜索“当前时间在utc”验证。
尽可能将主机服务器的操作系统时区设置为UTC
或GMT
。在某些操作系统上没有这样的选择,因此选择“大西洋/雷克雅未克”作为解决方法,因为冰岛全年保持在UTC / GMT,没有任何夏令时废话。
不通过调用TimeZone.setDefault
来设置JVM的默认时区(在最糟糕的情况下,作为最后的手段除外)。设置默认值是粗鲁的,因为它会立即影响该JVM中运行的所有应用程序中所有线程中的所有代码。并且它是不可靠的,因为任何其他代码都可以在运行时在您的应用程序上更改它。而是在所有日期时间代码中指定时区。永远不要隐含地依赖JVM的当前默认值。 Joda-Time和java.time都有采用时区参数的方法。因此,虽然将所有服务器计算机的“主机操作系统”时区设置为UTC是一种很好的做法,但永远不会依赖于Java代码中的那些。
答案 1 :(得分:1)
解决时区转移问题还有另一种选择;在这种情况下,您不必将整个JVM放在UTC时区(无论如何都不是一个好主意;例如,您可能希望在多个Java应用程序之间共享JVM,而这样的hacky解决方案可能会导致问题。未来的)。
因此,有一个小型开源项目DbAssist
,它为这个问题提供了一个干净而优雅的解决方案。在内部,它会将您实体中的java.util.Date
字段映射到自定义UtcDateType
。自定义类型强制JDBC(以及后来的Hibernate)将数据库中的日期视为UTC。对于不同版本的Hibernate,此修复程序有不同版本(其API在版本之间更改了几次),因此您必须根据表here选择正确的版本。
在同一页面上,您还可以找到有关如何安装和应用修复程序的详细说明。通常,如果您使用的是Hibernate 5.2.2,只需将以下行添加到您的POM文件中:
<dependency>
<groupId>com.montrosesoftware</groupId>
<artifactId>DbAssist-5.2.2</artifactId>
<version>1.0-RELEASE</version>
</dependency>
然后,修复程序的应用程序在JPA Annotations和HBM文件设置之间有所不同,因此我将仅提供一个可能的设置示例;对于JPA Annotations案例(没有Spring Boot),只需将此行添加到persistence.xml
标记之间的<persistence-unit>
文件中:
<class>com.montrosesoftware.dbassist.types</class>
现在,当读取/保存到数据库时,实体中的日期将被视为UTC。如果你想进一步了解Java / Hibernate中日期和时区问题的本质,你可以阅读这个article解释它的更多细节。