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

Posted

技术标签:

【中文标题】使用 Jackson 进行 DateTime 反序列化的默认时区(Joda-Time 模块)【英文标题】:Default timezone for DateTime deserialization with Jackson (Joda-Time module) 【发布时间】:2013-12-11 21:48:45 【问题描述】:

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

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

谢谢。

【问题讨论】:

UTC 和 GMT 在大多数情况下几乎相同。是什么让您的应用与众不同? 【参考方案1】:

DateTimeDeserializer 的源代码显示它使用来自DeserializationContext 的时区,该时区由ObjectMapper 在反序列化期间提供。如果你查看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”作为可能的时区。谢谢。 所以这是 Jackson 实现中的错误?我们应该创建新票吗?【参考方案2】:

UTC 与 GMT

对于商业应用,UTC 和 GMT 之间没有实际区别。唯一的区别在于亚秒级分辨率和每隔几年添加一个Leap Second。对于科学、天文学、卫星跟踪和此类应用程序而言,差异可能很大,但这种情况很少见。

杰克逊默认为 UTC/GMT

我不知道 杰克逊。但是从查看您链接的文档来看,它们似乎序列化(a)自 1970 年 1 月 1 日以来的毫秒数,UTC,或(b)字符串格式,默认为 ISO 8601 格式:“1970-01 -01T00:00:00.000+0000”。因此,要回答您关于时区的问题,听起来杰克逊默认总是使用 UTC(无时区偏移)进行序列化,这是正确的做法。如果您关心当时使用的时区,您应该在单独的字段中记录该事实(哪个时区)。

杰克逊 ↔ java.util.Date/Calendar ↔ Joda-Time

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

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 时间转换为 Joda-Time 中的其他时区。

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 通信。当回到杰克逊时,我会将我的 DateTimes 切换回 UTC,只是为了更好地衡量。

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

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

去做吧

我怀疑你做的太多担心和编码不够。只需尝试一些小实验,将值传入和传出 Jackson 和 Joda-Time。你会很快掌握它的窍门。我建议你让 Jackson 默认做它想做的任何事情,然后在 Joda-Time 中操作。 Joda-Time 是为日期时间的棘手问题而构建的,而 Jackson 可能不是。 Joda-Time 有构造函数和方法,可以根据需要在时区之间进行调整。

更美好的未来

在 Java 8 中,JSR 310: Date and Time API 将类似 Joda-Time 的类内置到 Java 平台中。希望看到像 Jackson 这样的框架得到更新,可以直接使用这些新类,同时弃用丑陋的 java.util.Date/Calendar 类。

看起来jackson-datatype-joda project 现在正试图为 Joda-Time 带来这种便利。但这对我来说似乎没有必要。如上所述,您可以在 java.util.Date/Calendar 和 Joda-Time 之间进行转换。

附:该项目文档的“Wiki”链接失败。所以我不能看他们的文档。

【讨论】:

【参考方案3】:

我也在日期格式上苦苦挣扎,最后我找到了解决方案。我想使用"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)。

【讨论】:

【参考方案4】:

从一个简单的代码中,我发现当 Jackson 从字符串反序列化为 Date 对象时,如果没有提及时区,它会给出 UTC,但是当直接实例化时,则为 gives default timezone,即机器的时区。

DateTime date = new DateTime(2013, 1, 2, 0, 0) //gives local timezone

以下给出 UTC

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

【讨论】:

以上是关于使用 Jackson 进行 DateTime 反序列化的默认时区(Joda-Time 模块)的主要内容,如果未能解决你的问题,请参考以下文章

在 Spring REST Controller 中对 DateTime 使用 Jackson 反序列化

如何将 graphql-java-extended-scalars DateTime 与 Jackson 一起使用

如何使用 Jackson JSON 处理器序列化 Joda DateTime?

在Spring REST Controller中使用Jackson Deserialization for DateTime

fastjson和jackson使用对比

如何在 Spring MVC 中使用 Jackson 和 Jersey 2 Client 反序列化 Joda DateTime?