使用Jackson(Joda-Time模块)反序列化DateTime时的默认时区

18

这个问题是关于使用jackson-datatype-joda模块Joda-Time DateTime进行反序列化的。是否有一个默认时区,将日期字符串反序列化为它?如果有,那是什么?是UTC吗?

我需要问一下,因为Jackson文档对于Joda-Time DateTime并不具体。我在这篇文章中发现(http://wiki.fasterxml.com/JacksonFAQDateHandling),Jackson会默认将java.util.Datejava.util.Calendar反序列化时假定GMT为默认时区。然而,在该文档中并没有提到Joda-Time数据类型。另外,我需要将字符串反序列化为使用UTC时区的DateTime对象,而非GMT:尽管这两个时区非常相似,但存在一些小差异,因此GMT对我来说不可行。

谢谢。


1
UTC和GMT在大多数情况下几乎相同。那么,是什么特别之处让您在应用程序中区分它们呢? - Basil Bourque
4个回答

29
DateTimeDeserializer 的源代码显示它使用由 ObjectMapper 在反序列化期间提供的 DeserializationContext 中的时区。如果您查看ObjectMapper API,您将看到有一个设置时区的方法:
public ObjectMapper setTimeZone(TimeZone tz)

因此,您可以使用这种方法来配置您的ObjectMapper并设置正确的时区。

至于默认值,Javadoc似乎说了一件事,但代码显示了另一件事。

ObjectMapper.setTimeZone(TimeZone tz)的Javadoc:

/**
  * Method for overriding default TimeZone to use for formatting.
  * Default value used is {@link TimeZone#getDefault()}.
  */

然而,代码在以下位置显式设置了时区:

protected final static BaseSettings DEFAULT_BASE = new BaseSettings(
    ...
    // TimeZone.getDefault()
    TimeZone.getTimeZone("GMT"),
    ...

所以,显然,它实际上使用的是 GMT 而不是默认的 JVM 默认时区。

我认为最好的选择可能是不要依赖它,而是通过 ObjectMapper.setTimeZone(TimeZone tz) 自行设置时区。


GMT的默认设置正是我所需要的。看起来Java不使用“UTC”作为可能的时区。谢谢。 - ecbrodie
这是Jackson实现中的错误吗?我们应该创建新的工单吗? - Ondrej Bozek

17

UTC与GMT的区别

对于商业应用程序而言,UTCGMT之间没有实际区别。唯一的区别是子秒分辨率和每几年添加一个闰秒。对于科学、天文、卫星跟踪等应用程序,差异可能很大,但这种情况很少见。

Jackson默认使用UTC/GMT

我不了解Jackson,但从您提供的文档看,它似乎会序列化:(a)自1970年1月1日起的毫秒数(UTC),或者(b)字符串格式,默认为ISO 8601格式:"1970-01-01T00:00:00.000+0000"。因此,回答您关于时区的问题,听起来Jackson默认始终使用UTC(无时区偏移),这是正确的方式。如果您关心该时间使用的时区,请将该事实(所使用的时区)记录在单独的字段中。

Jackson ↔ java.util.Date/Calendar ↔ Joda-Time

这些序列化值(毫秒和ISO 8601字符串)都可以与Joda-TimeDateTime实例的构造函数一起使用。

String dateTimeString = "2013-11-22T18:37:55.645+0000";
org.joda.time.DateTime myDateTime = org.joda.time.format.ISODateTimeFormat.dateTime().withZoneUTC().parseDateTime( dateTimeString );

并且...

Long millisSinceEpoch = 1385495462L;
org.joda.time.DateTime myDateTime = new org.joda.time.DateTime( millisSinceEpoch );

如果您无法直接访问这些序列化值以提供给Joda-Time DateTime构造函数,则让Jackson实例化java.util.Date / Calendar对象。将这些java.util.Date / Calendar对象提供给Joda-Time以实例化DateTime对象以进行进一步的操作。Joda-Time用户经常这样做。

org.joda.time.DateTime myDateTime = new org.joda.time.DateTime( someJavaUtilDateFromJackson );
你可以通过调用 toDateTime() 方法并传递所需的时区,轻松将UTC时间转换为其他时区的时间。
org.joda.time.DateTimeZone kolkataTimeZone = org.joda.time.DateTimeZone.forID( "Asia/Kolkata" );
org.joda.time.DateTime dateTimeInKolkata = myDateTime.toDateTime( kolkataTimeZone ); 

Joda-Time的 toDate 方法可以轻松将时间转换为java.util.Date类型。因此,在Joda-Time中完成大部分工作,然后将其转换回java.util.Date类型以便与Jackson通信。当返回给Jackson时,为了确保准确性,建议将DateTime再次转回UTC时间。

myDateTime.toDateTime( org.joda.time.DateTimeZone.UTC )

在StackOverflow.com上可以找到许多上述Joda-Time操作的示例。

只需动手

我猜你担心得有点过了,而且编码不够。试着进行一些小实验,将值传入和传出Jackson和Joda-Time。你很快就会掌握它。我建议你让Jackson按默认方式做任何它想做的事情,然后再在Joda-Time中进行操作。Joda-Time是为日期时间复杂问题而构建的,而Jackson可能不是。 Joda-Time具有构造函数和方法,以根据需要调整时区。

更光明的未来

在Java 8中,JSR 310:日期和时间API将Joda-Time类内置到Java平台中。预计将看到像Jackson这样的框架更新为直接使用这些新类,同时弃用丑陋的java.util.Date / Calendar类。

看起来jackson-datatype-joda项目正试图为Joda-Time带来这种便利。但是对我来说似乎并不必要。你可以按照上面所讨论的方法在java.util.Date/Calendar和Joda-Time之间进行转换。

附言:该项目文档的“Wiki”链接失败了。因此,我无法查看它们的文档。


8
我也曾为日期格式苦恼,最终找到了解决方案。我想使用格式"2016-02-08T12:49:22.876Z",因为它符合 ISO 8601 标准,并被 JavaScript 的Date对象所使用。同时,我也希望始终使用协调世界时(UTC)时区。
我发现可以通过以下代码实现:
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));

final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setDateFormat(dateFormat);

System.out.println(objectMapper.writeValueAsString(new Date()));

请注意格式字符串中的 X 字符。它从 Java 7 开始受支持,并指定 ISO 8601 中的时区。如在 SimpleDateFormat 中所述,如果时区偏移量为 0(UTC),则它会生成 Z (而不是 +00:00)。

1
从一个简单的代码中,我发现当Jackson从字符串反序列化为Date对象时,如果没有指定时区,则会给出UTC,但是当直接实例化时,则会给出默认时区,即机器的时区。
DateTime date = new DateTime(2013, 1, 2, 0, 0) //gives local timezone

以下内容给出的是协调世界时。
ObjectMapper mapper = new ObjectMapper();
DateTime dt = new DateTime(2013, 1, 2, 0, 0);
String serialized = mapper.writeValueAsString(dt);
DateTime dt1 = mapper.readValue(serialized, DateTime.class); //gives UTC

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接