Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Weird behavior in java.text.SimpleDateFormat expecting yyyyMMdd given yyyy-MM-dd

I have encountered a very weird behavior while using SimpleDateFormat for parsing a string to a date. Consider the following unit test:

@Test
public void testParse() throws ParseException
{
    DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");

    String dateStr = "2012-12-21";
    Date parsedDate = dateFormat.parse(dateStr);
    Calendar date = Calendar.getInstance();
    date.setTime(parsedDate);

    Assert.assertEquals(2012, date.get(Calendar.YEAR));
    Assert.assertEquals(11, date.get(Calendar.MONTH)); // yeah, Calendar sucks
    Assert.assertEquals(21, date.get(Calendar.DAY_OF_MONTH));
}

As it can be seen there is an intentional mistake in the above code: the SimpleDateFormat is initialized with "yyyyMMdd" but the string to be parsed is in the format "yyyy-MM-dd". I would expect that such thing results in a ParseException, or at least be parsed on a best-effort basis correctly. Instead, for some weird reason the date is parsed as 2011-11-02. Eh?!

This is unacceptable as one single mistake while handling the inputs would result in something totally unexpected / devastating. Switched to JodaTime in the meantime, but it would be nice to understand what went wrong there.

like image 924
Daniel Dinnyes Avatar asked Jul 28 '11 12:07

Daniel Dinnyes


4 Answers

Extract from JavaDoc for setLenient:

public void setLenient(boolean lenient)

Specify whether or not date/time parsing is to be lenient. With lenient parsing, the parser may use heuristics to interpret inputs that do not precisely match this object's format.
With strict parsing, inputs must match this object's format.

If you set it to false, you will get ParseException

like image 90
Sergey Aslanov Avatar answered Nov 09 '22 18:11

Sergey Aslanov


If you use the DateFormat.parse() function, the string must satisfy the input format. if it doesn't do it, the parse function parse wrong. Here a comment about this in the javaDoc:

By default, parsing is lenient: If the input is not in the form used by this object's format method but can still be parsed as a date, then the parse succeeds. Clients may insist on strict adherence to the format by calling setLenient(false).

Then, your problem would be fixed adding the setLenient(false) line. In this case, Java throws the exception.

like image 38
Charliemops Avatar answered Nov 09 '22 19:11

Charliemops


Well, the input would be split into 3 components: year, month, day and you'd get month = -12 and day = -21 (for correction see below). Try to parse 2012/12/21 and you'll get the exception :)

Edit: excerpt from the JavaDoc:

Month: If the number of pattern letters is 3 or more, the month is interpreted as text; otherwise, it is interpreted as a number.

Edit2: Correction

Looking at the source of SimpleDateFormat it seems that 2012-12-21 is actually split into this:

year = "2012"
month = "-1"
day = "2-" 

The source comments state that a - following a number might either denote a negative number (depending on the locale) or be a delimiter. In your case it seems to be taken as a delimiter, thus day = "2-" results in day = 2, hence the second of November.

like image 2
Thomas Avatar answered Nov 09 '22 18:11

Thomas


You need to call setLenient(false). The default is true and Java tries to convert the string even it does not match 100%.

like image 1
Petteri H Avatar answered Nov 09 '22 18:11

Petteri H