JodaTime Hibernate支持使用错误的TimeZone保存DateTime

时间:2014-01-26 00:33:30

标签: java hibernate timezone jodatime

我知道有类似的问题,但我想明确问题是什么。

基本上我正在尝试在Oracle数据库中保存日期。我希望它以UTC时间存储。数据库中的列是TIMESTAMP,我没有保存TimeZone信息。

查看JodaTime Hibernate支持类PersistentDateTime,似乎nullSafeGet()方法只是调用DateTime.toDate(),它返回一个java.util.Date。这是在数据库中保存的日期。

我没有得到的是......为什么getDate()不保留现有DateTime的TimeZone?我以UTC格式发送日期时间(比如说17:00),并在调用getDate()时将其切换到我的TimeZone,CET(18:00)。我希望它能节省17:00,而不是18:00。

我意识到这可能(可能)从我运行Java的环境中获取TimeZone,并且有一种可能性是通过命令行参数来改变它。但我不想这样做,而且我还是不允许在生产机器上使用。

我目前的想法是实现一个扩展PersistentDateTime并覆盖nullSafeGet()方法的类,因此它维护传递给它的DateTime的TimeZone。不确定如何做到这一点......也许JodaTime api中的某个方法可以优雅地实现这一目标吗?

无论如何,如果其他人对这种行为感到惊讶或者仅仅是我,我只是好奇。如果我的想法很好,或者有人有更好的想法。更改服务器(应用程序或数据库)或其他配置不是一种选择,我需要使用代码修复此问题。

修改

为了后人的缘故,并回复有用的评论者,我想澄清我的问题是什么。

我认为问题在于我的应用服务器和我的数据库服务器有不同的时区。事实证明这不是问题。问题是应用服务器或JVM的时区与我在我的应用中使用的时区不同。让我解释一下。

我的网络应用管理活动。活动有开始和结束日期。这些是Oracle数据库中的TIMESTAMP列(没有时区)。此外,在应用程序中,您必须定义您正在使用的时区。这与app服务器(以及随后的JVM)或数据库服务器正在使用的时区无关。

但当然,应用服务器 确实 有时区。就我而言,它是CST,中央标准时间(GMT-6)。当应用程序中使用的时区与JVM级别使用的时区不一致时,会出现问题。在我的例子中,应用程序中定义的时区是东部标准时间(GMT-5)。

那么发生了什么?发生了什么,我从网络管理面板创建了一个事件,例如:

  • 开课日期:2014年1月15日09:00
  • 截止日期:2014年1月20日23:00

这些日期是使用Joda DateTime创建的,并分配了正确的时区(使用DateTime.withZoneRetainFields()),即我在应用程序EST中使用的那个。所以我的活动看起来像这样:

  • 活动名称:活动1
  • 开课日期:15/01/2014 09:00 EST
  • 截止日期:2014年1月20日23:00 EST

到目前为止一切顺利。但是,当我保存时,日期保存如下:

  • 开课日期:15/01/2014 08:00
  • 截止日期:20/01/2014 22:00

发生什么事了?发生了什么事情是因为app server / JVM在CST中运行,它从两个日期减去了一个小时。我认为这是一个错误,但正如评论者所说,它是一个功能。 JodaTime Hibernate驱动程序只需调用DateTime.toDate(),它创建一个java.util.Date,它本身没有时区,但会根据JVM使用的时区自动更改小时。

我该如何避免这种情况?好吧,我可以使用-Duser.timezone参数启动JVM,并确保它始终与应用程序中使用的时区相同。这是一个选择。

我能做的另一件事是不要回避问题,而是使用它。换句话说,在Hibernate /数据库级别,每当我加载一个Event时,请确保为开始日期和结束日期字段分配应用程序定义的时区,从而将小时数转换回应用程序中使用的时区。这样,即使它们保存为08:00和22:00,它们也会在应用程序中显示为09:00和23:00。这显然是更好的解决方案,更明确。但我不确定它是否值得所有的工作。我已经在Web上看到了各种使用Hibernate的方法 - 使用Property Access Type或自定义Hibernate用户类型。不确定它们是否正常工作,看起来像是很多工作而且非常具有侵入性。我必须从使用DateTime更改为Calendar(我认为,不确定)和/或在我的POJO中进行特殊映射。

事实是我们在整个应用程序中都有java.util.Dates,并且在许多情况下只需要执行一个新的Date()来实例化它们,而不考虑时区。这些应最终全部更改为DateTime,并使用应用程序定义的时区显式实例化。

但这需要做很多工作。目前我选择只使用-Duser.timezone参数,看看它是怎么回事。同样,不是最好的解决方案,但我认为它现在满足了我们的需求。

非常感谢所有评论者,睁开眼睛"原样。

2 个答案:

答案 0 :(得分:1)

j.u.Date是Tricky

chrylis的评论很可能是正确的:你被java.util.Date类所欺骗。 Date没有时区,但其toString实现应用了JVM的默认时区。对于没有经验的Java程序员来说,似乎就像Date对象有一个时区,但实际上不是

如果chrylis和我是正确的,那么你没有数据库问题,你没有JDBC问题,你没有Hibernate问题,并且你没有Joda-Time问题。问题在于设计和实现的java.util.Date/Calendar类设计不佳。

另一种可能的解释是类似的...... Joda-Time DateTime实例确实知道其指定的时区。如果您忽略指定时区,则会分配JVM的默认时区。或者,指定时区对象(DateTimeZone类),例如预定义的常量DateTimeZone.UTC对象。

Upshot:您可能没有任何问题或错误。只是误解了功能。

作业...

捆绑的Java类......

java.util.Date date = new java.util.Date();
long dateMillis = date.getTime();

Joda-Time ...(转换java.util.Date实例)

DateTime dateTime = new DateTime( date, DateTimeZone.UTC );     
long dateTimeMillis = dateTime.getMillis();

转储到控制台...

System.out.println( "date: " + date + " = " + dateMillis );
System.out.println( "dateTime: " + dateTime + " = " + dateTimeMillis );

跑步时......

date: Sat Jan 25 17:18:02 PST 2014 = 1390699082010
dateTime: 2014-01-26T01:18:02.010Z = 1390699082010

答案 1 :(得分:0)

问题基本上在JDBC驱动程序中找到,它试图将日期转换为当前JVM时区或从当前JVM时区转换日期,如@RobertBowen所述。有几种方法可以找到此问题的解决方法,如上面已经评论过的(使用UTC)。但是,我给你留下了两个可能的即时解决方案(尽管不是最佳实践)。

1.-启动JVM时设置值:

  

-Duser.timezone参数

2.-当您的应用程序启动时,请设置所需的TimeZone:TimeZone.setDefault(TimeZone.getTimeZone("Mexico/General"));