为日期反序列化设置杰克逊时区

Posted

技术标签:

【中文标题】为日期反序列化设置杰克逊时区【英文标题】:Set Jackson Timezone for Date deserialization 【发布时间】:2011-11-25 06:59:08 【问题描述】:

我正在使用 Jackson(通过 Spring MVC Annotations)将字段反序列化为来自 JSON 的 java.util.Date。 POST 看起来像 - "enrollDate":"2011-09-28T00:00:00.000Z",但是当 Spring & Jackson 创建对象时,它将日期设置为 "2011-09-27 20:00:00"

如何在杰克逊设置正确的时区? 或者如果这不是问题,我如何从 JSON 消息中发送 EST?

Javascript/jQuery:

var personDataView =  enrollDate : new Date($("#enrollDate").val()), 
                       //...other members
                      ;


$.postJSON('/some/path/', personDataView, function(data)
    //... handle the response here

);

JSON 消息:

"enrollDate":"2011-09-28T00:00:00.000Z"

弹簧控制器:

@RequestMapping(value="/", method=RequestMethod.POST)
public @ResponseBody String saveProfile(@RequestBody personDataView persondataView, HttpServletRequest request)

        //...dataView has a java.util.Date enrollDate field
        //...other code

【问题讨论】:

你在哪个时区?如果您在 UTC 以西 4 小时,这 2 个印章是等价的。 我在美国东部时间。那么为什么杰克逊假设我发送的传入时间是格林威治标准时间?我应该更改我的 javascript 以以不同的方式发布日期,还是更改杰克逊的设置? 时间戳末尾的 Z 是 +00:00 aka Zulu time aka UTC / GMT 的简写。 啊不知道,谢谢!我更新了这个问题,提供了有关如何构建和发送日期的更多信息。 我认为这是反序列化,不是序列化。标题应该调整? 【参考方案1】:

在 Jackson 2+ 中,您还可以使用 @JsonFormat 注解:

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="America/Phoenix")
private Date date;

如果这样不起作用,请尝试用单引号将 Z 括起来,即 pattern="yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"

【讨论】:

@JsonFormat(shape= JsonFormat.Shape.STRING, pattern="yyyy-MM-dd'T'HH:mm:ss.SSSZ", timezone="US/Arizona")【参考方案2】:

您是否在您的 application.properties 中尝试过这个?

spring.jackson.time-zone= # Time zone used when formatting dates. For instance `America/Los_Angeles`

【讨论】:

感谢这在本地修复了我的项目,在 OpenShift 中我还必须设置环境变量 TZ【参考方案3】:

如果你真的希望 Jackson 返回一个时区不是 UTC 的日期(我自己对此有几个很好的论据,尤其是当一些客户只是没有得到时区部分时),那么我通常会这样做:

ObjectMapper mapper = new ObjectMapper();
DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("CET"));
mapper.getSerializationConfig().setDateFormat(dateFormat);
// ... etc

对了解时区的人没有不良影响-p

【讨论】:

似乎 mapper.getSerializationConfig().setDateFormat(dateFormat) 现在已弃用,新的​​方法是 mapper.setDateFormat(dateFormat)【参考方案4】:

我使用的是 Jackson 1.9.7,我发现执行以下操作并不能解决我的序列化/反序列化时区问题:

DateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSSZ");
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
objectMapper.setDateFormat(dateFormat);

在 JSON 消息中,我得到的不是“2014-02-13T20:09:09.859Z”,而是“2014-02-13T08:09:09.859+0000”,这显然是不正确的。我没有时间单步调试 Jackson 库源代码来找出发生这种情况的原因,但是我发现如果我只是将 Jackson 提供的 ISO8601DateFormat 类指定给 ObjectMapper.setDateFormat 方法,那么日期是正确的。

除了这没有把毫秒放在我想要的格式中,所以我将ISO8601DateFormat 类子分类并覆盖format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition) 方法。

/**
 * Provides a ISO8601 date format implementation that includes milliseconds
 *
 */
public class ISO8601DateFormatWithMillis extends ISO8601DateFormat 

  /**
   * For serialization
   */
  private static final long serialVersionUID = 2672976499021731672L;


  @Override
  public StringBuffer format(Date date, StringBuffer toAppendTo, FieldPosition fieldPosition)
  
      String value = ISO8601Utils.format(date, true);
      toAppendTo.append(value);
      return toAppendTo;
  

【讨论】:

输出与您提供的 SimpleDateFormat 完全匹配。根据SimpleDateFormat Javadoc:hh 输出 12 小时时间,kk 输出 24 时间,Z 输出时区偏移量。我想你想要一个明确的日期格式yyyy-MM-dd'T'kk:mm:ss.SSS'Z' 日期的格式是一个小问题,对我来说主要问题是 ObjectMapper 吐出的实际日期值不正确。仔细看我给出的例子,日期输出实际上与预期的相差 12 小时,也就是说,我得到的不是“2014-02-13T20:09:09.859Z”,而是“2014-02-13T08:09:09.859” +0000" 08:09:09 in 12 小时(缺少 am/pm 指示符)相当于 24 中的 20:09:09小时时间。 你说得对,我对 ISO8601 日期时间格式规范和 SimpleDateFormat 之间的区别感到困惑,它们是不同的。在 ISO8601 中,'hh' 表示 24 小时制,但在 SimpleDateFormat 中,它表示 12 小时制 (w3.org/TR/NOTE-datetime) 啊!所以我的解决方案仍然有效,但在我的场景中是不必要的。谢谢。【参考方案5】:

看起来较旧的答案适用于较旧的 Jackson 版本,但由于 objectMapper 具有方法 setTimeZone(tz),因此完全忽略在 dateFormat 上设置时区。

如何在 Jackson 2.11.0 版中正确设置时区到 ObjectMapper:

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setTimeZone(TimeZone.getTimeZone("Europe/Warsaw"));

完整示例

  @Test
  void test() throws Exception 
    ObjectMapper objectMapper = new ObjectMapper();
    objectMapper.findAndRegisterModules();
    objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    JavaTimeModule module = new JavaTimeModule();
    objectMapper.registerModule(module);
    objectMapper.setTimeZone(TimeZone.getTimeZone("Europe/Warsaw"));

    ZonedDateTime now = ZonedDateTime.now();
    String converted = objectMapper.writeValueAsString(now);

    ZonedDateTime restored = objectMapper.readValue(converted, ZonedDateTime.class);
    System.out.println("serialized: " + now);
    System.out.println("converted: " + converted);
    System.out.println("restored: " + restored);

    Assertions.assertThat(now).isEqualTo(restored);
  
`

【讨论】:

我使用相同的方法,但设置 JVM 时区 mapper.setTimeZone(TimeZone.getDefault()),以避免对特定时区进行硬编码。【参考方案6】:

刚遇到这个问题,终于意识到LocalDateTime没有任何时区信息。如果您收到带有时区信息的日期字符串,则需要将其用作类型:

ZonedDateTime

查看link

【讨论】:

我在尝试为 LocalDateTime 指定时区时不断收到此错误,即使该模式包含时区 (pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSZ) .我把它改回日期,一切都开始工作了。JSON解析错误:无效格式:“2019-04-01T00:00:00+0300”在“+0300”处格式错误;嵌套异常是com.fasterxml.jackson .databind.JsonMappingException:无效格式:“2019-04-01T00:00:00+0300”在“+0300”处格式错误 @AmanicA 如何为 LocalDateTime 对象指定时区?本地时区应始终默认为您的本地时区。我认为你无法改变这一点。你最好像我在帖子中建议的那样使用 ZonedDateTime。【参考方案7】:

您的日期对象可能没问题,因为您使用 GMT 时区发送了以 ISO 格式编码的日期,并且您在打印日期时处于美国东部标准时间。

请注意,日期对象在打印时执行时区转换。您可以检查您的 date 对象是否正确:

Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
cal.setTime(date);
System.out.println (cal);

【讨论】:

【参考方案8】:

我在日历反序列化方面遇到了同样的问题,解决了扩展 CalendarDeserializer。 它强制 UTC 时区 如果有人需要,我粘贴代码:

@JacksonStdImpl
public class UtcCalendarDeserializer extends CalendarDeserializer 

    TimeZone TZ_UTC = TimeZone.getTimeZone("UTC");

    @Override
    public Calendar deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException 
        JsonToken t = jp.getCurrentToken();
        if (t == JsonToken.VALUE_NUMBER_INT) 
            Calendar cal = Calendar.getInstance(TZ_UTC);
            cal.clear();
            cal.setTimeInMillis(jp.getLongValue());

            return cal;
        

        return super.deserialize(jp, ctxt);
    

在 JSON 模型类中,只需将字段注释为:

@JsonDeserialize(using = UtcCalendarDeserializer.class)
private Calendar myCalendar;

【讨论】:

【参考方案9】:

对于现在(2020 年 2 月)遇到此问题的任何人来说,以下 Medium 帖子对于我们克服它至关重要。

https://medium.com/@ttulka/spring-http-message-converters-customizing-770814eb2b55

在我们的例子中,应用程序使用了@EnableWebMvc,如果删除它会中断,因此“没有 Spring Boot 的生活”部分至关重要。这就是最终为我们解决这个问题的方法。它允许我们在序列化期间仍然使用和生成 JSON 和 XML 以及格式化我们的日期时间以满足应用程序的需求。

@Configuration
@ComponentScan("com.company.branch")
@EnableWebMvc
public class WebMvcConfig implements WebMvcConfigurer 

@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) 
    converters.add(0, new MappingJackson2XmlHttpMessageConverter(
                new Jackson2ObjectMapperBuilder()
                        .defaultUseWrapper(false)
                        .createXmlMapper(true)
                        .simpleDateFormat("yyyy-mm-dd'T'HH:mm:ss'Z'")
                        .build()
        ));
    converters.add(1, new MappingJackson2HttpMessageConverter(
                new Jackson2ObjectMapperBuilder()
                        .build()
        ));
    

【讨论】:

以上是关于为日期反序列化设置杰克逊时区的主要内容,如果未能解决你的问题,请参考以下文章

杰克逊:使用时区 id 解析 ZonedDateTime

JSON如何反序列化日期时间并将UTC转换为指定时区?

Spring boot - Jackson 日期序列化和反序列化

反序列化 JSON 日期无时区

反序列化日期保持本地时间

我如何反序列化以杰克逊为单位的时间戳?