【问题标题】: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 的简写。
  • 啊不知道,谢谢!我更新了这个问题,提供了有关如何构建和发送日期的更多信息。
  • 我认为这是反序列化,不是序列化。标题应该调整?

标签: json spring-mvc jackson


【解决方案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()
              ));
          }
      }
      

      【讨论】:

        猜你喜欢
        • 2017-04-25
        • 2019-08-04
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-04-21
        • 1970-01-01
        • 1970-01-01
        • 2015-04-07
        相关资源
        最近更新 更多