我知道也有类似的问题,但我想弄清楚到底是什么问题。
基本上,我试图在我的Oracle数据库中保存一个日期。我希望它存储在UTC时间。数据库中的列是TIMESTAMP,我不保存时区信息。
看看JodaTime Hibernate支持类PersistentDateTime,nullSafeGet()方法似乎只是简单地调用DateTime.toDate(),它返回一个java.util.Date。这是保存在数据库中的日期。
我不明白的是。。。为什么getDate()不保留现有DateTime的时区?我给它发送一个UTC的DateTime(比如17:00),在调用getDate()时,它会切换到我的时区,CET(18:00)。我希望它能节省17:00,而不是18:00。
我意识到这(可能)是从我运行Java的环境中获得TimeZone,一种可能性是用命令行参数来更改它。但我不想那样做,而且我无论如何都不允许在生产机器上。
我目前的想法是实现一个扩展PersistentDateTime并重写nullSafeGet()方法的类,这样它就可以维护传递给它的DateTime的TimeZone。。。也许JodaTime api中有一个方法可以优雅地做到这一点?
不管怎样,我只是好奇是否有人对这种行为感到惊讶,或者只有我一个人。我的想法是好的,还是有人有更好的想法。更改服务器(应用程序或数据库)或其他配置不是一个选项,我需要用代码来解决这个问题。
编辑
为了子孙后代,为了回复那些乐于助人的评论,我想澄清一下我的问题到底是什么。
我认为问题是我的应用服务器和数据库服务器有不同的时区。事实证明这不是问题所在。问题是,应用程序服务器或JVM的时区与我在应用程序中使用的时区不同。让我解释一下。
我的web应用程序管理事件。事件有开始和结束日期。这些是Oracle数据库中的TIMESTAMP列(不带时区)。此外,在应用程序中,您必须定义您正在使用的时区。这与应用程序服务器(以及随后的JVM)或数据库服务器正在使用的时区无关。
但应用服务器当然有一个时区。就我而言,它是中央标准时间CST(GMT-6)。当应用程序中使用的时区与JVM级别使用的时区不一致时,就会出现问题。在我的情况下,应用程序中定义的时区是东部标准时间(GMT-5)。
那么发生了什么?现在的情况是,我从web管理面板创建一个事件,例如:
- 开始日期:2014年1月15日09:00
- 结束日期:2014年1月20日23:00
这些日期是使用Joda DateTime创建的,并被分配给适当的时区(使用DateTime.withZoneRetainFields()),即我在应用程序中使用的时区EST。所以我的活动看起来是这样的:
- 事件名称:事件1
- 开始日期:美国东部时间2014年1月15日09:00
- 结束日期:美国东部时间2014年1月20日23:00
到目前为止还不错。然而,当我保存它时,日期保存如下:
- 开始日期:2014年1月15日08:00
- 结束日期:2014年1月20日22:00
发生了什么?由于应用程序服务器/JVM在CST中运行,它从两个日期中减去了一个小时。我以为这是个bug,但正如评论人士所说,这是一个功能。JodaTime Hibernate驱动程序只需调用DateTime.toDate(),它会创建一个java.util.Date,它本身没有时区,但会根据JVM使用的时区自动更改小时。
我该如何避免这种情况?好吧,我可以用-Duser.timezone参数启动JVM,并确保它始终与应用程序中使用的时区相同。这是一种选择。
我可以做的另一件事不是避免这个问题,而是处理它。换句话说,在Hibernate/数据库级别,每当我加载事件时,请确保开始和结束日期字段都分配了应用程序定义的时区,从而将小时数转换回应用程序中使用的时区。这样,即使它们被保存为08:00和22:00,它们在应用程序中也会显示为09:00和23:00。这显然是一个更好、更明确的解决方案。但我只是不确定这是否值得所有的工作。我已经看到了在网络上使用Hibernate的各种方法——使用属性访问类型或自定义Hibernate用户类型。不确定它们是否正常工作,这似乎是一项艰巨的工作,而且非常具有侵入性。我必须从使用DateTime改为使用Calendar(我想,不确定)和/或在我的POJO中进行特殊映射。
事实上,我们在整个应用程序中都有java.util.Dates,在许多情况下,只需执行一个新的Date()来实例化它们,而不考虑时区。这些最终都应该更改为DateTime,并使用应用程序定义的时区显式实例化。
但这是一项艰巨的工作。目前,我选择简单地使用-Duser.timezone参数,看看它的运行情况。同样,这不是最好的解决方案,但我认为它符合我们目前的需求。
非常感谢所有的评论者让我大开眼界。
j.u.Date很狡猾
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
对象。
更新:您可能根本没有问题或bug。只是误解了功能。
作业
捆绑的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
问题基本上是在JDBC驱动程序中发现的,正如@RobertBowen所说,JDBC驱动程序试图将日期转换为当前JVM时区。有几种方法可以找到解决此问题的方法,如上文所述(使用UTC)。然而,我留给您两个可能的即时解决方案(尽管不是最佳实践)。
1.-启动JVM时设置值:
-Duser.timezone参数
2.-当您的应用程序启动时,设置所需的时区:TimeZone.setDefault(TimeZone.getTimeZone("Mexico/General"));