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

SpringBoot中XMLGregorianCalendar时间格式转换适配失效问题

XMLGregorianCalendar适配器未生效的时间格式转换问题

我需要处理两种时间格式的转换场景:

  • 将外部API返回的06-09-2022 17:05:28:113319格式字符串,转换为XMLGregorianCalendar实例
  • 将XMLGregorianCalendar转换为2022-09-06T17:07:21.319+02:00格式,用于调用另一API

由于项目大量使用WSDL定义的数据结构,我基于Java SpringBoot实现了DateAdapter,但该适配器并未生效,代码如下:

public class DateAdapter extends XmlAdapter<String, XMLGregorianCalendar> {
    private static final String DATE_STRING = "dd-MM-yyyy HH:mm:ss:SSSSSSSX";

    @Override
    public String marshal(XMLGregorianCalendar v) throws Exception {
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_STRING);
        return dateFormat.format(v.toGregorianCalendar().getTime());
    }

    @Override
    public XMLGregorianCalendar unmarshal(String v) throws Exception {
        GregorianCalendar gregorianCalendar = new GregorianCalendar();
        SimpleDateFormat dateFormat = new SimpleDateFormat(DATE_STRING);

        gregorianCalendar.setTime(dateFormat.parse(v));
        return DatatypeFactory.newInstance().newXMLGregorianCalendar(gregorianCalendar);
    }
}

问题排查与修复方案

1. 未绑定适配器到目标字段/类

JAXB不会自动识别自定义适配器,必须显式声明:

  • 单个字段生效:在对应的字段或其getter方法上添加注解:
@XmlJavaTypeAdapter(DateAdapter.class)
private XMLGregorianCalendar targetDateField;
  • 全局生效:在项目的package-info.java文件中配置全局适配器:
@XmlJavaTypeAdapters({
    @XmlJavaTypeAdapter(type = XMLGregorianCalendar.class, value = DateAdapter.class)
})
package com.your.project.package;

import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapters;
import javax.xml.datatype.XMLGregorianCalendar;

2. 时间格式字符串错误

原代码中的格式字符串存在两处问题:

  • SSSSSS:SimpleDateFormat的S最多支持3位毫秒,6位微秒会被错误解析
  • X:输入字符串无时区信息,使用X会导致解析失败

修正输入解析逻辑

针对06-09-2022 17:05:28:113319格式,手动截取前3位毫秒处理:

@Override
public XMLGregorianCalendar unmarshal(String v) throws Exception {
    // 拆分日期和微秒部分
    String[] timeParts = v.split(":");
    String dateTimePart = String.join(":", Arrays.copyOf(timeParts, timeParts.length - 1));
    String microsecPart = timeParts[timeParts.length - 1];
    
    // 解析基础日期时间,加上前3位毫秒
    SimpleDateFormat sdf = new SimpleDateFormat("dd-MM-yyyy HH:mm:ss");
    Date baseDate = sdf.parse(dateTimePart);
    long millis = Long.parseLong(microsecPart.substring(0, 3));
    baseDate.setTime(baseDate.getTime() + millis);
    
    GregorianCalendar gregCal = new GregorianCalendar();
    gregCal.setTime(baseDate);
    // 指定时区(根据实际业务设置,比如+02:00对应Europe/Paris)
    gregCal.setTimeZone(TimeZone.getTimeZone("Europe/Paris"));
    
    return DatatypeFactory.newInstance().newXMLGregorianCalendar(gregCal);
}

简化输出格式化逻辑

XMLGregorianCalendar本身支持直接输出符合要求的格式,无需使用SimpleDateFormat:

@Override
public String marshal(XMLGregorianCalendar v) throws Exception {
    // toXMLFormat()直接返回如2022-09-06T17:07:21.319+02:00的格式
    return v.toXMLFormat();
}

3. 线程安全问题

SimpleDateFormat是非线程安全的,高并发场景下会出现异常,建议替换为Java 8+的DateTimeFormatter:

public class DateAdapter extends XmlAdapter<String, XMLGregorianCalendar> {
    private static final DateTimeFormatter INPUT_FORMATTER = DateTimeFormatter.ofPattern("dd-MM-yyyy HH:mm:ss:SSS");
    private static final ZoneId TIME_ZONE = ZoneId.of("Europe/Paris");

    @Override
    public String marshal(XMLGregorianCalendar v) throws Exception {
        return v.toXMLFormat();
    }

    @Override
    public XMLGregorianCalendar unmarshal(String v) throws Exception {
        // 截取到3位毫秒
        String truncatedTime = v.substring(0, v.length() - 3);
        LocalDateTime localDateTime = LocalDateTime.parse(truncatedTime, INPUT_FORMATTER);
        ZonedDateTime zonedDateTime = ZonedDateTime.of(localDateTime, TIME_ZONE);
        
        GregorianCalendar gregCal = GregorianCalendar.from(zonedDateTime);
        return DatatypeFactory.newInstance().newXMLGregorianCalendar(gregCal);
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 16:50:36