| package com.fasterxml.jackson.databind.deser; |
| |
| import java.text.DateFormat; |
| import java.text.SimpleDateFormat; |
| import java.util.*; |
| |
| import com.fasterxml.jackson.annotation.JsonFormat; |
| import com.fasterxml.jackson.databind.*; |
| import com.fasterxml.jackson.databind.exc.InvalidFormatException; |
| |
| public class TestDateDeserialization |
| extends BaseMapTest |
| { |
| // Test for [JACKSON-435] |
| static class DateAsStringBean |
| { |
| @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="/yyyy/MM/dd/") |
| public Date date; |
| } |
| |
| static class DateAsStringBeanGermany |
| { |
| @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="/yyyy/MM/dd/", locale="fr_FR") |
| public Date date; |
| } |
| |
| static class CalendarAsStringBean |
| { |
| @JsonFormat(shape=JsonFormat.Shape.STRING, pattern=";yyyy/MM/dd;") |
| public Calendar cal; |
| } |
| |
| static class DateInCETBean { |
| @JsonFormat(shape=JsonFormat.Shape.STRING, pattern="yyyy-MM-dd,HH", timezone="CET") |
| public Date date; |
| } |
| |
| /* |
| /********************************************************** |
| /* Unit tests |
| /********************************************************** |
| */ |
| |
| private final ObjectMapper MAPPER = new ObjectMapper(); |
| |
| public void testDateUtil() throws Exception |
| { |
| long now = 123456789L; |
| java.util.Date value = new java.util.Date(now); |
| |
| // First from long |
| assertEquals(value, MAPPER.readValue(""+now, java.util.Date.class)); |
| |
| // then from String |
| String dateStr = dateToString(value); |
| java.util.Date result = MAPPER.readValue("\""+dateStr+"\"", java.util.Date.class); |
| |
| assertEquals("Date: expect "+value+" ("+value.getTime()+"), got "+result+" ("+result.getTime()+")", |
| value.getTime(), result.getTime()); |
| } |
| |
| public void testDateUtilWithStringTimestamp() throws Exception |
| { |
| long now = 1321992375446L; |
| /* As of 1.5.0, should be ok to pass as JSON String, as long |
| * as it is plain timestamp (all numbers, 64-bit) |
| */ |
| String json = quote(String.valueOf(now)); |
| java.util.Date value = MAPPER.readValue(json, java.util.Date.class); |
| assertEquals(now, value.getTime()); |
| |
| // #267: should handle negative timestamps too; like 12 hours before 1.1.1970 |
| long before = - (24 * 3600 * 1000L); |
| json = quote(String.valueOf(before)); |
| value = MAPPER.readValue(json, java.util.Date.class); |
| assertEquals(before, value.getTime()); |
| } |
| |
| public void testDateUtilRFC1123() throws Exception |
| { |
| DateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); |
| // let's use an arbitrary value... |
| String inputStr = "Sat, 17 Jan 2009 06:13:58 +0000"; |
| java.util.Date inputDate = fmt.parse(inputStr); |
| assertEquals(inputDate, MAPPER.readValue("\""+inputStr+"\"", java.util.Date.class)); |
| } |
| |
| public void testDateUtilRFC1123OnNonUSLocales() throws Exception |
| { |
| Locale old = Locale.getDefault(); |
| Locale.setDefault(Locale.GERMAN); |
| DateFormat fmt = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); |
| // let's use an arbitrary value... |
| String inputStr = "Sat, 17 Jan 2009 06:13:58 +0000"; |
| java.util.Date inputDate = fmt.parse(inputStr); |
| assertEquals(inputDate, MAPPER.readValue("\""+inputStr+"\"", java.util.Date.class)); |
| Locale.setDefault(old); |
| } |
| |
| /** |
| * ISO8601 is supported as well |
| */ |
| public void testDateUtilISO8601() throws Exception |
| { |
| /* let's use simple baseline value, arbitrary date in GMT, |
| * using the standard notation |
| */ |
| String inputStr = "1972-12-28T00:00:00.000+0000"; |
| Date inputDate = MAPPER.readValue("\""+inputStr+"\"", java.util.Date.class); |
| Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| c.setTime(inputDate); |
| assertEquals(1972, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.DECEMBER, c.get(Calendar.MONTH)); |
| assertEquals(28, c.get(Calendar.DAY_OF_MONTH)); |
| |
| // And then the same, but using 'Z' as alias for +0000 (very common) |
| inputStr = "1972-12-28T00:00:00.000Z"; |
| inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); |
| c.setTime(inputDate); |
| assertEquals(1972, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.DECEMBER, c.get(Calendar.MONTH)); |
| assertEquals(28, c.get(Calendar.DAY_OF_MONTH)); |
| |
| // Same but using colon in timezone |
| inputStr = "1972-12-28T00:00:00.000+00:00"; |
| inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); |
| c.setTime(inputDate); |
| assertEquals(1972, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.DECEMBER, c.get(Calendar.MONTH)); |
| assertEquals(28, c.get(Calendar.DAY_OF_MONTH)); |
| |
| // Same but only passing hour difference as timezone |
| inputStr = "1972-12-28T00:00:00.000+00"; |
| inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); |
| c.setTime(inputDate); |
| assertEquals(1972, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.DECEMBER, c.get(Calendar.MONTH)); |
| assertEquals(28, c.get(Calendar.DAY_OF_MONTH)); |
| |
| inputStr = "1984-11-30T00:00:00.000Z"; |
| inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); |
| c.setTime(inputDate); |
| assertEquals(1984, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.NOVEMBER, c.get(Calendar.MONTH)); |
| assertEquals(30, c.get(Calendar.DAY_OF_MONTH)); |
| } |
| |
| public void testDateUtilISO8601NoTimezone() throws Exception |
| { |
| // Timezone itself is optional as well... |
| String inputStr = "1984-11-13T00:00:09"; |
| Date inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); |
| Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| c.setTime(inputDate); |
| assertEquals(1984, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.NOVEMBER, c.get(Calendar.MONTH)); |
| assertEquals(13, c.get(Calendar.DAY_OF_MONTH)); |
| assertEquals(0, c.get(Calendar.HOUR_OF_DAY)); |
| assertEquals(0, c.get(Calendar.MINUTE)); |
| assertEquals(9, c.get(Calendar.SECOND)); |
| assertEquals(0, c.get(Calendar.MILLISECOND)); |
| } |
| |
| // [Issue#338] |
| public void testDateUtilISO8601NoMilliseconds() throws Exception |
| { |
| final String INPUT_STR = "2013-10-31T17:27:00"; |
| Date inputDate; |
| Calendar c; |
| |
| inputDate = MAPPER.readValue(quote(INPUT_STR), java.util.Date.class); |
| c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| c.setTime(inputDate); |
| assertEquals(2013, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.OCTOBER, c.get(Calendar.MONTH)); |
| assertEquals(31, c.get(Calendar.DAY_OF_MONTH)); |
| assertEquals(17, c.get(Calendar.HOUR_OF_DAY)); |
| assertEquals(27, c.get(Calendar.MINUTE)); |
| assertEquals(0, c.get(Calendar.SECOND)); |
| assertEquals(0, c.get(Calendar.MILLISECOND)); |
| |
| // 03-Nov-2013, tatu: This wouldn't work, and is the nominal reason |
| // for #338 I thinl |
| /* |
| inputDate = ISO8601Utils.parse(INPUT_STR); |
| c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| c.setTime(inputDate); |
| assertEquals(2013, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.OCTOBER, c.get(Calendar.MONTH)); |
| assertEquals(31, c.get(Calendar.DAY_OF_MONTH)); |
| assertEquals(17, c.get(Calendar.HOUR_OF_DAY)); |
| assertEquals(27, c.get(Calendar.MINUTE)); |
| assertEquals(0, c.get(Calendar.SECOND)); |
| assertEquals(0, c.get(Calendar.MILLISECOND)); |
| */ |
| } |
| |
| public void testDateUtilISO8601JustDate() throws Exception |
| { |
| // Plain date (no time) |
| String inputStr = "1972-12-28"; |
| Date inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); |
| Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| c.setTime(inputDate); |
| assertEquals(1972, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.DECEMBER, c.get(Calendar.MONTH)); |
| assertEquals(28, c.get(Calendar.DAY_OF_MONTH)); |
| } |
| |
| @SuppressWarnings("deprecation") |
| public void testDateSql() throws Exception |
| { |
| java.sql.Date value = new java.sql.Date(0L); |
| value.setYear(99); // 1999 |
| value.setDate(19); |
| value.setMonth(Calendar.APRIL); |
| long now = value.getTime(); |
| |
| // First from long |
| assertEquals(value, MAPPER.readValue(String.valueOf(now), java.sql.Date.class)); |
| |
| // then from default java.sql.Date String serialization: |
| |
| java.sql.Date result = MAPPER.readValue(quote(value.toString()), java.sql.Date.class); |
| Calendar c = gmtCalendar(result.getTime()); |
| assertEquals(1999, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.APRIL, c.get(Calendar.MONTH)); |
| assertEquals(19, c.get(Calendar.DAY_OF_MONTH)); |
| |
| /* [JACKSON-200]: looks like we better add support for regular date |
| * formats as well |
| */ |
| String expStr = "1981-07-13"; |
| result = MAPPER.readValue(quote(expStr), java.sql.Date.class); |
| c.setTimeInMillis(result.getTime()); |
| assertEquals(1981, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.JULY, c.get(Calendar.MONTH)); |
| assertEquals(13, c.get(Calendar.DAY_OF_MONTH)); |
| |
| /* 20-Nov-2009, tatus: I'll be damned if I understand why string serialization |
| * is off-by-one, but day-of-month does seem to be one less. My guess is |
| * that something is funky with timezones (i.e. somewhere local TZ is |
| * being used), but just can't resolve it. Hence, need to comment this: |
| */ |
| // assertEquals(expStr, result.toString()); |
| } |
| |
| public void testCalendar() throws Exception |
| { |
| // not ideal, to use (ever-changing) current date, but... |
| java.util.Calendar value = Calendar.getInstance(); |
| long l = 12345678L; |
| value.setTimeInMillis(l); |
| |
| // First from long |
| Calendar result = MAPPER.readValue(""+l, Calendar.class); |
| assertEquals(l, result.getTimeInMillis()); |
| |
| // Then from serialized String |
| String dateStr = dateToString(new Date(l)); |
| result = MAPPER.readValue(quote(dateStr), Calendar.class); |
| |
| // note: representation may differ (wrt timezone etc), but underlying value must remain the same: |
| assertEquals(l, result.getTimeInMillis()); |
| } |
| |
| public void testCustom() throws Exception |
| { |
| final ObjectMapper mapper = new ObjectMapper(); |
| DateFormat df = new SimpleDateFormat("yyyy-MM-dd'X'HH:mm:ss"); |
| df.setTimeZone(TimeZone.getTimeZone("PST")); |
| mapper.setDateFormat(df); |
| |
| String dateStr = "1972-12-28X15:45:00"; |
| java.util.Date exp = df.parse(dateStr); |
| java.util.Date result = mapper.readValue("\""+dateStr+"\"", java.util.Date.class); |
| assertEquals(exp, result); |
| } |
| |
| /** |
| * Test for [JACKSON-203]: make empty Strings deserialize as nulls by default, |
| * without need to turn on feature (which may be added in future) |
| */ |
| public void testDatesWithEmptyStrings() throws Exception |
| { |
| assertNull(MAPPER.readValue(quote(""), java.util.Date.class)); |
| assertNull(MAPPER.readValue(quote(""), java.util.Calendar.class)); |
| assertNull(MAPPER.readValue(quote(""), java.sql.Date.class)); |
| } |
| |
| // for [JACKSON-334] |
| public void test8601DateTimeNoMilliSecs() throws Exception |
| { |
| // ok, Zebra, no milliseconds |
| for (String inputStr : new String[] { |
| "2010-06-28T23:34:22Z", |
| "2010-06-28T23:34:22+0000", |
| "2010-06-28T23:34:22+00", |
| }) { |
| Date inputDate = MAPPER.readValue(quote(inputStr), java.util.Date.class); |
| Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| c.setTime(inputDate); |
| assertEquals(2010, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.JUNE, c.get(Calendar.MONTH)); |
| assertEquals(28, c.get(Calendar.DAY_OF_MONTH)); |
| assertEquals(23, c.get(Calendar.HOUR_OF_DAY)); |
| assertEquals(34, c.get(Calendar.MINUTE)); |
| assertEquals(22, c.get(Calendar.SECOND)); |
| assertEquals(0, c.get(Calendar.MILLISECOND)); |
| } |
| } |
| |
| public void testTimeZone() throws Exception |
| { |
| TimeZone result = MAPPER.readValue(quote("PST"), TimeZone.class); |
| assertEquals("PST", result.getID()); |
| } |
| |
| public void testCustomDateWithAnnotation() throws Exception |
| { |
| final String INPUT = "{\"date\":\"/2005/05/25/\"}"; |
| DateAsStringBean result = MAPPER.readValue(INPUT, DateAsStringBean.class); |
| assertNotNull(result); |
| assertNotNull(result.date); |
| Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| long l = result.date.getTime(); |
| if (l == 0L) { |
| fail("Should not get null date"); |
| } |
| c.setTimeInMillis(l); |
| assertEquals(2005, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.MAY, c.get(Calendar.MONTH)); |
| assertEquals(25, c.get(Calendar.DAY_OF_MONTH)); |
| |
| // 27-Mar-2014, tatu: Let's verify that changing Locale won't break it; |
| // either via context Locale |
| result = MAPPER.reader(DateAsStringBean.class) |
| .with(Locale.GERMANY) |
| .readValue(INPUT); |
| assertNotNull(result); |
| assertNotNull(result.date); |
| l = result.date.getTime(); |
| if (l == 0L) { |
| fail("Should not get null date"); |
| } |
| c.setTimeInMillis(l); |
| assertEquals(2005, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.MAY, c.get(Calendar.MONTH)); |
| assertEquals(25, c.get(Calendar.DAY_OF_MONTH)); |
| |
| // or, via annotations |
| DateAsStringBeanGermany result2 = MAPPER.reader(DateAsStringBeanGermany.class).readValue(INPUT); |
| assertNotNull(result2); |
| assertNotNull(result2.date); |
| l = result2.date.getTime(); |
| if (l == 0L) { |
| fail("Should not get null date"); |
| } |
| c.setTimeInMillis(l); |
| assertEquals(2005, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.MAY, c.get(Calendar.MONTH)); |
| assertEquals(25, c.get(Calendar.DAY_OF_MONTH)); |
| } |
| |
| public void testCustomCalendarWithAnnotation() throws Exception |
| { |
| CalendarAsStringBean cbean = MAPPER.readValue("{\"cal\":\";2007/07/13;\"}", |
| CalendarAsStringBean.class); |
| assertNotNull(cbean); |
| assertNotNull(cbean.cal); |
| Calendar c = cbean.cal; |
| assertEquals(2007, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.JULY, c.get(Calendar.MONTH)); |
| assertEquals(13, c.get(Calendar.DAY_OF_MONTH)); |
| } |
| |
| public void testCustomCalendarWithTimeZone() throws Exception |
| { |
| // And then with different TimeZone: CET is +01:00 from GMT -- read as CET |
| DateInCETBean cet = MAPPER.readValue("{\"date\":\"2001-01-01,10\"}", |
| DateInCETBean.class); |
| Calendar c = Calendar.getInstance(getUTCTimeZone()); |
| c.setTimeInMillis(cet.date.getTime()); |
| // so, going to UTC/GMT should reduce hour by one |
| assertEquals(2001, c.get(Calendar.YEAR)); |
| assertEquals(Calendar.JANUARY, c.get(Calendar.MONTH)); |
| assertEquals(1, c.get(Calendar.DAY_OF_MONTH)); |
| assertEquals(9, c.get(Calendar.HOUR_OF_DAY)); |
| } |
| |
| /* |
| /********************************************************** |
| /* Tests to verify failing cases |
| /********************************************************** |
| */ |
| |
| public void testInvalidFormat() throws Exception |
| { |
| try { |
| MAPPER.readValue(quote("foobar"), Date.class); |
| fail("Should have failed with an exception"); |
| } catch (InvalidFormatException e) { |
| verifyException(e, "Can not construct instance"); |
| assertEquals("foobar", e.getValue()); |
| assertEquals(Date.class, e.getTargetType()); |
| } catch (Exception e) { |
| fail("Wrong type of exception ("+e.getClass().getName()+"), should get " |
| +InvalidFormatException.class.getName()); |
| } |
| } |
| |
| /* |
| /********************************************************** |
| /* Helper methods |
| /********************************************************** |
| */ |
| |
| private String dateToString(java.util.Date value) |
| { |
| /* Then from String. This is bit tricky, since JDK does not really |
| * suggest a 'standard' format. So let's try using something... |
| */ |
| DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); |
| return df.format(value); |
| } |
| |
| private static Calendar gmtCalendar(long time) |
| { |
| Calendar c = Calendar.getInstance(TimeZone.getTimeZone("GMT")); |
| c.setTimeInMillis(time); |
| return c; |
| } |
| |
| } |