I am using objectMapper.setTimeZone(TimeZone.getTimeZone("CET")) to alter the output of timezones when using objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false);. This all works fine, but it seems that specifying a timezone also causes Jackson to misinterpret the Z in incoming ISO8601 times. The following testcase describes this issue:
@Test
public void testBehavior() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setTimeZone(TimeZone.getTimeZone("CET"));
String time = "2016-01-01T17:00:00.000Z";
Date dateAccordingToJackson = objectMapper.convertValue(time, Date.class);
Date correctDate = Date.from(Instant.from(DateTimeFormatter.ISO_DATE_TIME.parse(time)));
Assert.assertEquals("Demonstrate Jackson bug(?)", correctDate.toString(),
dateAccordingToJackson.toString());
}
With the following output:
Demonstrate Jackson bug(?) expected:<Fri Jan 01 1[8]:00:00 CET 2016> but was:<Fri Jan 01 1[7]:00:00 CET 2016>
It seems to me that this is incorrect behavior since as far as I know ISO8601 mandates that a trailing Z indicates the time is transmitted in the UTC timezone. The documentation does say it affects all timezone behavior, but I don't think this should be happening.
I've tested with Jackson 2.7.2
Hmmh. This is bit difficult to say for sure, but one constant problem has been that java.util.Date does not have actual timezone information. So in many cases comparison of Dates using toString() gives false fails because timezone used by Date.toString() is not controlled by Jackson, but rather uses system default timezone.
In this case I am not sure as the actual underlying time value appears to be different, which should NOT be the case. So perhaps it is a legitimate bug.
I do believe this is a bug. The difference between dateAccordingToJackson and correctDate is also visible when checking their getTime() values: expected:<1451667600000> but was:<1451664000000>
I believe that objectMapper.setTimeZone should not affect the parsing of the Z in incoming timestamps. From the RFC:
Z A suffix which, when applied to a time, denotes a UTC
offset of 00:00; often spoken "Zulu" from the ICAO
phonetic alphabet representation of the letter "Z".
Behavior is as I expect when I omit the call to setTimeZone, but I'd like to have dates still be output in my timezone (when using objectMapper.configure(SerializationConfig.Feature.WRITE_DATES_AS_TIMESTAMPS, false)).
@wouterhund I agree; timezone from input should determine timezone used for decoding the value, and not the timezone defined for internal usage. So it does sound like a bug. I mentioned other issues mostly as they could be related.
One thing that would be useful here would be reproduction that does not rely on Java 8. I can not add Java 8 references from within databind (we require Java 7 for 2.7), so test as is can not yet be included.
Another question here would be whether off-by-one for CET is due to timezone, or possibly some other issue (daylights savings, perhaps?). Should be relatively easy to use some other timezone that is further away from UTC.
Since we know that 2016-01-01T17:00:00.000Z is 1451667600000 the test-case can be further reduced to:
@RunWith(Parameterized.class)
public class JacksonISO8601DeserializationTest {
@Parameters
public static Collection<String> data() {
return Arrays.asList("UTC", "CET", "America/Los_Angeles", "Australia/Melbourne");
}
@Parameter
public String timeZone;
@Test
public void testBehavior() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.setTimeZone(TimeZone.getTimeZone(timeZone));
String time = "2016-01-01T17:00:00.000Z";
long correctTime = 1451667600000l;
Date dateAccordingToJackson = objectMapper.convertValue(time, Date.class);
Assert.assertEquals("ISO8601 decoding mismatch " + timeZone, correctTime, dateAccordingToJackson.getTime());
}
}
No Java 8 dependency and I've also included timezone parameters. It passes for UTC, but not the other three.
@wouterhund Excellent, thanks!
I noticed this issue while using StdDateFormat, the cause is that DATE_FORMAT_STR_ISO8601_Zuses "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'" as the format to use when the date comes with a Z. Notice that the Z is considered a literal, meaning it will be ignored completely.
When cloning the object for this pattern the CET TimeZone is used, and thus when parsing the date it will be interpreted in the CET TimeZone.
I believe the solution is to use DEFAULT_TIMEZONE (UTC) instead of _timezone when cloning DATE_FORMAT_ISO8601_Z.
Adding suggested test shows that this has been fixed at some point: 2.9 and 2.10 branches pass at least. Will add unit test in 2.10, close this issue.
Most helpful comment
I noticed this issue while using
StdDateFormat, the cause is thatDATE_FORMAT_STR_ISO8601_Zuses"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"as the format to use when the date comes with a Z. Notice that the Z is considered a literal, meaning it will be ignored completely.When cloning the object for this pattern the CET TimeZone is used, and thus when parsing the date it will be interpreted in the CET TimeZone.
I believe the solution is to use
DEFAULT_TIMEZONE(UTC) instead of_timezonewhen cloningDATE_FORMAT_ISO8601_Z.