使用 ObjectMapper 为 JodaTime 设置时区偏移的 Jackson DateTime 格式

Posted

技术标签:

【中文标题】使用 ObjectMapper 为 JodaTime 设置时区偏移的 Jackson DateTime 格式【英文标题】:Jackson DateTime formatting for JodaTime with timezone offset using ObjectMapper 【发布时间】:2021-04-19 10:29:42 【问题描述】:

我正在升级 Jackson 库的版本,但遇到了格式化/解析带有时区偏移的 Joda DateTime 的问题。我已经分享了与两个不同版本的 Jackson 库一起使用的示例代码。请让我知道我做错了什么或错过了什么。

jackson-datatype-joda:2.2.4

//jackson-datatype-joda: 2.2.4
@Test
public void jodaDateTimeZoneTest() throws JsonProcessingException 
    ObjectMapper mapper = getObjectMapper();
    DateTime dateTimeInChicago = new DateTime(DateTimeZone.forID("America/Chicago"));
    DateTime dateTimeInBucharest = new DateTime(DateTimeZone.forID("Europe/Bucharest"));
    
    System.out.println("the Input Value for Chicago is "+dateTimeInChicago); //Sample input is 2021-01-14T05:32:11.194-06:00
    System.out.println("the Input Value for Bucharest is "+dateTimeInBucharest); //Sample input is 2021-01-14T13:32:11.232+02:00
    Foo fooChicago = new Foo();
    fooChicago.setDateTime(dateTimeInChicago);
    System.out.println("the output value for Chicago is "+mapper.writeValueAsString(fooChicago)); //sample ouput is "dateTime":"2021-01-14T05:32:11.194-06:00"

    Foo fooBucharest = new Foo();
    fooBucharest.setDateTime(dateTimeInBucharest);

    System.out.println("the Input Value for Bucharest is "+mapper.writeValueAsString(fooBucharest)); //Sample output is "dateTime":"2021-01-14T13:32:11.232+02:00" 

jackson-datatype-joda:2.11.1

//jackson-datatype-joda: 2.11.1
@Test
public void jodaDateTimeZoneTest() throws JsonProcessingException 
    ObjectMapper mapper = getObjectMapper();
    DateTime dateTimeInChicago = new DateTime(DateTimeZone.forID("America/Chicago"));
    DateTime dateTimeInBucharest = new DateTime(DateTimeZone.forID("Europe/Bucharest"));
    
    System.out.println("the Input Value for Chicago is "+dateTimeInChicago); //Sample input is 2021-01-14T05:34:27.966-06:00
    System.out.println("the Input Value for Bucharest is "+dateTimeInBucharest); //Sample input is 2021-01-14T13:34:28.012+02:00
    Foo fooChicago = new Foo();
    fooChicago.setDateTime(dateTimeInChicago);
    System.out.println("the output value for Chicago is "+mapper.writeValueAsString(fooChicago)); //sample ouput is "dateTime":"2021-01-14T11:34:27.966Z"

    Foo fooBucharest = new Foo();
    fooBucharest.setDateTime(dateTimeInBucharest);

    System.out.println("the Input Value for Bucharest is "+mapper.writeValueAsString(fooBucharest)); //Sample output is "dateTime":"2021-01-14T11:34:28.012Z" 

两者的通用代码:

private ObjectMapper getObjectMapper() 
    ObjectMapper mapper = new ObjectMapper();
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
    mapper.setDateFormat(dateFormat);
    mapper.registerModule(new JodaModule());
    return mapper;


public class Foo 
    private DateTime dateTime;

    public DateTime getDateTime() 
        return dateTime;
    

    public void setDateTime(DateTime dateTime) 
        this.dateTime = dateTime;
    

    使用 2.2.4 将对象转换为字符串后的值 杰克逊是 2021-01-14T05:32:11.194-06:00 在 2.11.1 中,使用将对象转换为字符串后的值 杰克逊是 2021-01-14T11:34:27.966Z 在更高版本中,偏移量被添加/减去 实际时间。

预期: 使用更高版本的 Jackson 库时,如何获得与偏移量相同的值(如 -06:00)?

【问题讨论】:

【参考方案1】:

JodaTime DateTimeSerializer 的实现在 2.2 和 2.11 之间发生了很大变化,这是 2.6 发生的最后一次重大变化。

2.6 版引入了一个SerializationFeature.WRITE_DATES_WITH_ZONE_ID 标志来指示被序列化的DateTime 是否应该在其自己的时区和时区 ID 中打印(启用该功能时)或打印为相应的 UTC 时间戳(当该功能已禁用,默认行为)。但是,效果是打印时区偏移量和时区 ID。如果您将 2.11 代码更改为

mapper.enable(SerializationFeature.WRITE_DATES_WITH_ZONE_ID);
mapper.registerModule(new JodaModule());

你会发现输出是

"dateTime":"2021-02-03T08:58:30.632-06:00[America/Chicago]"

使用映射器注册模块时,默认DateTimeSerializer 注册在JodaModule 中。当我尝试注册 DateTimeSerializer 的自定义实例而不是默认实例时,我还没有找到覆盖时区格式的方法(以便只打印偏移量)。

但是,您可以实现并注册您自己的序列化程序,它将完全按照您的需要格式化 DateTime。基于DateTimeSerializer逻辑的简单示例:

public class CustomDateTimeSerializer extends JsonSerializer<DateTime> 

    @Override
    public void serialize(DateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException 
        gen.writeString(FormatConfig.DEFAULT_DATETIME_PRINTER.createFormatter(provider).withOffsetParsed().print(value));
    

然后配置你的 ObjectMapper:

private ObjectMapper getObjectMapper() 
    ObjectMapper mapper = new ObjectMapper();

    // Instead of .setDateFormat() - see note below
    mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
    // Use all the defaults of JodaModule
    mapper.registerModule(new JodaModule());
    // Override with your custom logic
    SimpleModule customJodaModule = new SimpleModule();
    customJodaModule.addSerializer(DateTime.class, new CustomDateTimeSerializer());
    mapper.registerModule(customJodaModule);

    return mapper;

注意:配置

mapper.setDateFormat(dateFormat);

并不会真正影响 JodaTime 日期序列化的格式,但作为副作用,它会禁用 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS 功能。您可以删除 mapper.setDateFormat(dateFormat) 并改为使用

mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

注意 2: UTC-06:00 和 [America/Chicago] 不是同义词 - 芝加哥(以及世界上许多其他地方)在夏季观察夏令时,当它移动到 UTC-05:00 时。您必须考虑哪种格式最适合您的目的。为了实现系统之间的最佳互操作性,最好在 UTC 时区发送时间戳(Jackson Joda 模块默认的行为方式)。

注意 3: 您可能需要为 DateTime 配置自定义反序列化器,并为其他 Joda 数据类型配置自定义反序列化器对,只要预期会出现仅打印时区偏移的相同行为。

【讨论】:

我认为这是正确的答案。 mapper.registerModule(new JodaModule()); mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);,并且必须在我的 Pojo 中的 DateTime 属性上指定 @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd@HH:mm:ss.SSSZ")

以上是关于使用 ObjectMapper 为 JodaTime 设置时区偏移的 Jackson DateTime 格式的主要内容,如果未能解决你的问题,请参考以下文章

使用 ObjectMapper - 如何将 JSON 结果快速转换为 TableView

使用 ObjectMapper 为 JodaTime 设置时区偏移的 Jackson DateTime 格式

swift 使用ObjectMapper将数组转换为Realm的List类型

Swift 3 ObjectMapper - 将 URLSession 结果转换为 Map 对象失败

ObjectMapper 正在转换为错误的日期时间

无法在 Spring Boot 中将 ProblemHandler 设置为 ObjectMapper