带有ISO参数的DateTimeFormat无法正确解析时区。

9

我有一个控制器,我正在尝试使用mockMVC进行测试

@RequestMapping(value = "/something/{language}", method = RequestMethod.GET, produces = { "application/json", "application/xml" })
    public ResponseEntity<someEntity> getInfo( 
            @PathVariable String language, 
            @DateTimeFormat(iso= DateTimeFormat.ISO.DATE_TIME) @RequestParam(required = false) Date fromDate
    )

我希望能够解析像文档中的日期格式:

DATE_TIME 最常见的ISO日期时间格式是yyyy-MM-dd'T'HH:mm:ss.SSSZ,例如:

但是我一直得到这样的结果:

处理程序执行导致异常:无法将类型为“java.lang.String”的值转换为所需的类型“java.util.Date”;嵌套异常是

org.springframework.core.convert.ConversionFailedException: 
Failed to conv ert from type java.lang.String to type 
@org.springframework.format.annotation.DateTimeFormat 
@org.springframework.web.bind.annotation.RequestParam java.util.Date for value '2015-09-26T01:30:00.000Z'; nested exception is
java.lang.IllegalArgumentException: Unable to parse '2015-09-26T01:30:00.000Z'

据我所见,我没有做错任何事情,这当然是必须的。 有人能解释一下我的问题吗?我认为我不需要发布更多的代码,因为异常确实显示了我传递给API的正确值,对吧?

我认为问题出在最后一个字符 Z 上,因为它代表日期时间解析模式中的时区。 - Sanjeev
通常情况下这个是正常工作的,而且当我以这种格式序列化时也是正常的。现在我只是接受一个字符串并手动将其转换为日期。这可能与它是一个GET请求有关。 - Mathijs Segers
你的日期时间字符串不符合ISO.DATE_TIME格式,它应该是2015-09-26T01:30:00.000<实际时区值>而不是2015-09-26T01:30:00.000Z。例如,2015-09-26T01:30:00.000-04:00 - Sanjeev
1
Z实际上是一个时区(UTC)。ISO规范指出,如果字符串以'Z'结尾,则表示UTC时区。这是一个错误。请参见https://en.wikipedia.org/wiki/ISO_8601#Coordinated_Universal_Time_(UTC)。 - linuxunil
3个回答

9

工作原理

您将日期作为String接收(HTTP请求是基于文本的),并通过pattern指示Spring如何将其转换为日期对象。

//Spring controller 
@GetMapping
public List<Foobar> find(
   @RequestParam(name = "startDate", required = false)
   @DateTimeFormat(pattern = "YOUR_DATE_PATTERN" or iso="ISO Enum member") //how to convert the date string
   Date startDate {
  return service.find(startDate); //work with the java.util.Date object
}

Spring会将这个任务委托给java.text.DateTimeFormat,所以模式必须是格式化程序类可用的有效模式

@DateTimeFormat - 模式 VS ISO

  • 模式:指定您的模式。将直接传递给格式化程序。
  • Iso:一个org.springframework.format.annotation.DateTimeFormat.ISO枚举成员,具有预先构建的日期字符串模式。从Enum文档中:

DATE最常见的ISO日期格式yyyy-MM-dd,例如

DATE_TIME最常见的ISO DateTime格式yyyy-MM-dd'T'HH:mm:ss.SSSZ,例如

NONE表示不应应用任何基于ISO的格式模式。

TIME最常见的ISO时间格式HH:mm:ss.SSSZ,例如

  • 请注意,除了NONE之外,所有枚举成员都使用“Z”作为时区。

日期模式中的“Z”代表什么?

  • 查看日期和时间模式的Javadocs,我们有两个选项来处理时区:

    1. 'Z'字符:RFC 822时区语法(Spring将使用此语法)
zone      =  "UT"  / "GMT"                ; Universal Time
                                        ; North American : UT

        /  "EST" / "EDT"                ;  Eastern:  - 5/ - 4

        /  "CST" / "CDT"                ;  Central:  - 6/ - 5

        /  "MST" / "MDT"                ;  Mountain: - 7/ - 6

        /  "PST" / "PDT"                ;  Pacific:  - 8/ - 7

        /  1ALPHA                       ; Military: Z = UT;
                                        ;  A:-1; (J not used)
                                        ;  M:-12; N:+1; Y:+12

        / ( ("+" / "-") 4DIGIT )        ; Local differential
                                        ;  hours+min. (HHMM)
  • 'X'字符:ISO 8601时区指示符
  • 时间偏移量(15:00−03:30)或者使用'Z'表示UTC/GMT时区

    问题

    • 如果选择org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME枚举成员,则会使用RFC 822语法中的yyyy-MM-dd'T'HH:mm:ss.SSSZ模式来表示时区。

    解决方法

    • 使用相同的模式,但是使用'X'来表示时区(使用ISO 8601语法):

      yyyy-MM-dd'T'HH:mm:ss.SSSX

    关于Z时区

    • ISO 8601规定:

    协调世界时(UTC)

    如果时间处于UTC时区,则在时间之后直接添加一个Z而不加空格。 Z是零UTC偏移的区域标识符。 因此,“09:30 UTC”表示为“09:30Z”或“0930Z”。“14:45:15 UTC”将是“14:45:15Z”或“144515Z”。

    ISO 8601时间表示中的Z后缀有时被称为“Zulu时间”,因为相同的字母用于指定Zulu时区。 但是,定义军事时区列表的ACP 121标准未提及UTC,并且从曾经用作国际民用时间标准的格林威治平均时间(GMT)派生“Zulu时间”。

    相关链接


    使用版本为5.1.3的DateTimeFormat时,当使用yyyy-MM-dd'T'HH:mm:ss.SSSX格式进行日期时间格式化时,会出现java.lang.IllegalArgumentException: Illegal pattern component: X异常。 - s7vr
    请问您能否详细说明一下您的问题? - linuxunil
    当使用doSomething(@RequestParam @DateTimeFormat(pattern="yyyy-MM-dd'T'HH:mm:ss.SSSX")将请求参数映射到控制器方法并输入2011-10-05T14:48:00.000Z时,出现了java.lang.IllegalArgumentException: Illegal pattern component: X的错误提示。 - s7vr
    同时,Java文档中枚举类型的第二个链接已经从Z更改为X。因此不再使用Z来表示时区。 - s7vr

    3
    根据DateTimeFormat.ISO.DATE_TIME的格式,最常见的ISO日期时间格式是yyyy-MM-dd'T'HH:mm:ss.SSSZ,例如2000-10-31 01:30:00.000-05:00。
    其中,Z代表时区值,例如-05:00。
    你的无法解析的字符串值为2015-09-26T01:30:00.000Z,其中Z必须替换为实际的时区值。
    例如,2015-09-26T01:30:00.000-04:00将被ISO.DATE_TIME正确解析。

    虽然“2015-09-26T01:30:00.000-04:00”是有效的且可解析,但“2015-09-26T01:30:00.000Z”也是一个有效的ISO 8601字符串,“Z”代表UTC(“Zulu”)。请参见最佳答案。 - Schlaagi

    1
    尽量不要将参数fromDate作为字符串传递。
    我遇到了同样的问题,当我在API调用中(我使用Postman)从双引号(")中解包参数值时,它起作用了。但你可能需要编码一些字符(例如+)。

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接