blob: 61ea444d965ef7815e46e781273a76681bcbca45 [file] [log] [blame]
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;
}
}