Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

DateTimeFormat with ISO parameter not parsing timezone correctly

I have this controller which I'm trying to test using 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
    )

So I'm expecting to allow date formats like in docs to be parsable: DATE_TIME The most common ISO DateTime Format yyyy-MM-dd'T'HH:mm:ss.SSSZ, e.g.

However I keep on getting things like this:

Handler execution resulted in exception: Failed to convert value of type 'java.lang.String' to required type 'java.util.Date'; nested exception is

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'

As far as I can see I'm not doing anything wrong, which must be of course. Can anyone shine a light what my bad is? I don't think I need to post more code since the exception does show the correct value which I'm passing to the API right?

like image 487
Mathijs Segers Avatar asked May 09 '16 07:05

Mathijs Segers


2 Answers

How it works

You receive the Date as an String (HTTP Request is text based) and instruct Spring on how to convert it to an Date Object by a pattern.

//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 will delegate this task to the java.text.DateTimeFormat, so the pattern should be a valid one for the formatter class to work with.

@DateTimeFormat - Pattern VS Iso

  • Pattern : Specify your pattern. Will be passed directly to the formatter.
  • Iso : An member of org.springframework.format.annotation.DateTimeFormat.ISOEnum, that has a pre-built Date String Pattern. From the Enum docs:

DATE The most common ISO Date Format yyyy-MM-dd, e.g.

DATE_TIME The most common ISO DateTime Format yyyy-MM-dd'T'HH:mm:ss.SSSZ, e.g.

NONE Indicates that no ISO-based format pattern should be applied.

TIME The most common ISO Time Format HH:mm:ss.SSSZ, e.g.

  • Notice that ALL Enum members (except for NONE), uses 'Z' for the timezone.

What 'Z' means in the date pattern?

  • Looking at the Javadocs of the Date and Time Patterns, we have two options to handle timezones:

    1. 'Z' Char : RFC 822 Timezone Syntax (Spring will use this syntax)
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)
  1. 'X' Char : ISO 8601 Timezone designators

Time offsets (15:00−03:30) OR 'Z' for UTC/GMT timezone

The issue

  • If you select org.springframework.format.annotation.DateTimeFormat.ISO.DATE_TIME Enum member, it will use the yyyy-MM-dd'T'HH:mm:ss.SSSZ pattern with the RFC 822 syntax for the timezones.

The fix

  • Use the same pattern but with the 'X' for the timezones (use the ISO 8601 syntax) :

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

About the Z Timezone

  • ISO 8601 states:

Coordinated Universal Time (UTC)

If the time is in UTC, add a Z directly after the time without a space. Z is the zone designator for the zero UTC offset. "09:30 UTC" is therefore represented as "09:30Z" or "0930Z". "14:45:15 UTC" would be "14:45:15Z" or "144515Z".

The Z suffix in the ISO 8601 time representation is sometimes referred to as "Zulu time" because the same letter is used to designate the Zulu time zone. However the ACP 121 standard that defines the list of military time zones makes no mention of UTC and derives the "Zulu time" from the Greenwich Mean Time[27] which was formerly used as the international civil time standard.

Related Links

  • Spring - Javadocs - DateTimeFormat Annotation
  • Spring - Javadocs - DateTimeFormat.ISO Enum
  • Java - Javadocs - Date and Time Patterns
  • RFC 822 - Standard for ARPA Internet Text Messages - Date and Time Specification
  • ISO 8601 - Timezone Designators
like image 84
linuxunil Avatar answered Oct 28 '22 17:10

linuxunil


As per DateTimeFormat.ISO.DATE_TIME

The most common ISO DateTime Format yyyy-MM-dd'T'HH:mm:ss.SSSZ, e.g. 2000-10-31 01:30:00.000-05:00.

Where a Z represents a timezone value for example -05:00.

Your string value which is unparseable is 2015-09-26T01:30:00.000Z where Z must be replaced with actual timezone value.

For example 2015-09-26T01:30:00.000-04:00 will be parsed by ISO.DATE_TIME correctly

like image 26
Sanjeev Avatar answered Oct 28 '22 16:10

Sanjeev