You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何使用Jackson与java.time解析多种ISO类日期时间格式?

解决Spring Boot + Jackson兼容多格式ISO时间的方案

我在Spring Boot 2.x项目里刚好解决过完全一样的问题——处理外部方传来的各种“ISO-ish”时间格式,用Jackson和java.time.OffsetDateTime来兼容所有变种。下面是实战可行的解决方案:

核心思路

Jackson默认的OffsetDateTime序列化/反序列化只严格遵循标准ISO-8601格式,无法兼容你提到的时区偏移变种(比如+00、+0000)和不同精度的毫秒位。我们需要自定义日期格式化器,并全局配置到Jackson的ObjectMapper中,让它能自动识别所有目标格式。

步骤1:自定义兼容所有格式的DateTimeFormatter

用DateTimeFormatterBuilder组合所有需要支持的格式,实现“可选匹配”:

private static final DateTimeFormatter CUSTOM_OFFSET_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
    // 匹配基础的本地日期时间部分(yyyy-MM-dd'T'HH:mm:ss)
    .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
    // 可选的毫秒部分:支持0-3位(比如.410、.41、无毫秒)
    .optionalStart()
        .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
    .optionalEnd()
    // 可选的时区偏移格式1:+HH:MM(比如+00:00)
    .optionalStart()
        .appendOffset("+HH:MM", "+00:00")
    .optionalEnd()
    // 可选的时区偏移格式2:+HHMM(比如+0000)
    .optionalStart()
        .appendOffset("+HHMM", "+0000")
    .optionalEnd()
    // 可选的时区偏移格式3:+HH(比如+00)
    .optionalStart()
        .appendOffset("+HH", "+00")
    .optionalEnd()
    // 可选的UTC标识Z
    .optionalStart()
        .appendLiteral('Z')
    .optionalEnd()
    .toFormatter();

这个格式化器会依次尝试匹配所有你列出的时间格式,完全覆盖需求。

步骤2:全局配置Jackson ObjectMapper

通过Spring Boot的配置类,把自定义格式化器注册到Jackson的JavaTimeModule中,替换默认的OffsetDateTime序列化/反序列化逻辑:

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.JsonDeserializer;
import com.fasterxml.jackson.databind.ser.std.JsonSerializer;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.io.IOException;
import java.time.OffsetDateTime;
import java.time.format.DateTimeParseException;

@Configuration
public class JacksonDateTimeConfig {

    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        JavaTimeModule timeModule = new JavaTimeModule();

        // 注册自定义序列化器:统一输出标准ISO格式(比如2018-01-01T15:56:31.410+00:00)
        timeModule.addSerializer(OffsetDateTime.class, (JsonSerializer<OffsetDateTime>) (value, gen, serializers) -> {
            gen.writeString(value.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME));
        });

        // 注册自定义反序列化器:用我们的格式化器解析所有变种格式
        timeModule.addDeserializer(OffsetDateTime.class, (JsonDeserializer<OffsetDateTime>) (p, ctxt) -> {
            String dateStr = p.getText().trim();
            try {
                return OffsetDateTime.parse(dateStr, CUSTOM_OFFSET_DATE_TIME_FORMATTER);
            } catch (DateTimeParseException e) {
                throw new IOException("无法解析时间格式:" + dateStr, e);
            }
        });

        mapper.registerModule(timeModule);
        // 禁用时间戳格式,强制输出字符串格式
        mapper.disable(com.fasterxml.jackson.databind.SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        return mapper;
    }

    // 上面定义的自定义格式化器
    private static final DateTimeFormatter CUSTOM_OFFSET_DATE_TIME_FORMATTER = new DateTimeFormatterBuilder()
        .append(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
        .optionalStart()
            .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
        .optionalEnd()
        .optionalStart()
            .appendOffset("+HH:MM", "+00:00")
        .optionalEnd()
        .optionalStart()
            .appendOffset("+HHMM", "+0000")
        .optionalEnd()
        .optionalStart()
            .appendOffset("+HH", "+00")
        .optionalEnd()
        .optionalStart()
            .appendLiteral('Z')
        .optionalEnd()
        .toFormatter();
}

步骤3:验证测试

创建一个测试用的请求类和接口,验证所有格式是否能正确解析:

// 请求类
public class TimeTestRequest {
    private OffsetDateTime time1; // 2018-01-01T15:56:31.410Z
    private OffsetDateTime time2; // 2018-01-01T15:56:31.41Z
    private OffsetDateTime time3; // 2018-01-01T15:56:31Z
    private OffsetDateTime time4; // 2018-01-01T15:56:31+00:00
    private OffsetDateTime time5; // 2018-01-01T15:56:31+0000
    private OffsetDateTime time6; // 2018-01-01T15:56:31+00

    // Getters & Setters
}

// 测试接口
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TimeTestController {
    @PostMapping("/test-time-formats")
    public ResponseEntity<TimeTestRequest> testTimeFormats(@RequestBody TimeTestRequest request) {
        // 返回解析后的结果,会自动序列化为标准ISO格式
        return ResponseEntity.ok(request);
    }
}

用Postman或curl发送包含所有格式的JSON请求,接口应该能正确解析所有字段,并返回统一的标准格式。

额外提示

  • 如果只是个别字段需要兼容多格式,也可以用@JsonFormat注解(格式字符串用方括号包裹可选格式),但全局配置更适合大量使用OffsetDateTime的场景:
    @JsonFormat(pattern = "[yyyy-MM-dd'T'HH:mm:ss][yyyy-MM-dd'T'HH:mm:ss.SSS][yyyy-MM-dd'T'HH:mm:ss.SS][yyyy-MM-dd'T'HH:mm:ss.S][yyyy-MM-dd'T'HH:mm:ssZ][yyyy-MM-dd'T'HH:mm:ss+HH:mm][yyyy-MM-dd'T'HH:mm:ss+HHmm][yyyy-MM-dd'T'HH:mm:ss+HH]")
    private OffsetDateTime timestamp;
    
  • 确保你的Spring Boot项目已经引入了jackson-datatype-jsr310依赖(Spring Boot的starter-web默认包含)。

内容的提问来源于stack exchange,提问作者Bastian Voigt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 12:13:57