Simpledateformat Parse(String Str) Doesn't Throw an Exception When Str = 2011/12/12Aaaaaaaaa

SimpleDateFormat parse(string str) doesn't throw an exception when str = 2011/12/12Simpledateformat Parse(String Str) Doesn't Throw an Exception When Str = 2011/12/12Aaaaaaaaaa?

The JavaDoc on parse(...) states the following:

parsing does not necessarily use all characters up to the end of the string

It seems like you can't make SimpleDateFormat throw an exception, but you can do the following:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/d");
sdf.setLenient(false);
ParsePosition p = new ParsePosition( 0 );
String t1 = "2011/12/12aaa";
System.out.println(sdf.parse(t1,p));

if(p.getIndex() < t1.length()) {
throw new ParseException( t1, p.getIndex() );
}

Basically, you check whether the parse consumed the entire string and if not you have invalid input.

Why does SimpleDateFormat.parse not throw an exception here?

Any idea what's wrong?

This is one of the places where SimpleDateFormat gets really troublesome. When trying to parse 1980-04-25 using the pattern yyyyMMdd it parses 1980 as year as expected, then -0 as month since it was supposed to be two characters, then 4 as day of month even though it was supposed to be two characters too. It ignores the -25 because it has done parsing.

A SimpleDateFormat with standard settings also ignores the fact that there is no month -0. It takes it as 0, so the month before January 1980, which is why you get December 1979.

SimpleDateFormat is notoriously troublesome, and Date is poorly designed too. Both are long outdated. I recommend you don’t use SimpleDateFormat. Ever.

java.time

private static final String[] dateFormats
= "yyyyMMdd,yyyy/MM/dd,yyyy-MM-dd,dd/MM/yyyy,dd-MM-yyyy,dd-MMM-yyyy,yyyy MM dd"
.split(",");

public static LocalDate parseAllDateFormats(String date) {
if (date == null) {
return null;
}
for (String format : dateFormats) {
try {
System.out.println("trying " + format);
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern(format, Locale.ENGLISH);
return LocalDate.parse(date, dateFormatter);
}
catch (DateTimeParseException e) {
// Ignore, try next format
}
}
return null;
}

Let’s try it with your example string:

    System.out.println(parseAllDateFormats("1980-04-25"));

Output is the expected:

trying yyyyMMdd
trying yyyy/MM/dd
trying yyyy-MM-dd
1980-04-25

I am using java.time, the modern Java date and time API. Compared to the old date-time classes it is so much nicer to work with. The modern DateTimeFormatter can resolve the parsed values into a date in three styles: strict, smart and lenient. Smart is the default where it will only accept month number from 1 through 12. Also LocalDate.parse will insist on parsing the entire string, or it will throw an exception.

Compared to your code I also made a couple of minor modifications. For most purposes I would have made an array of DateTimeFormatter (not of String), but it would not have allowed me to print trying yyyyMMdd, so here I didn’t. I put curly braces in the first if statement. I specified locale on the formatter because month abbreviations differ between languages and I want to control which language is used. I catch the specific DateTimeParseException and I added a comment why I ignore it because otherwise ignoring an exception is a bad, bad thing. At the bottom of your method you should also consider throwing an exception if no format worked rather than returning null.

Java 6

be aware I'm stuck with java6 here.

  • The java.time classes are built into Java 8 and later, as well as Android 26 and later.
  • ✅ Most of the java.time functionality is back-ported to Java 6 & Java 7 in the ThreeTen-Backport project.
  • Further adapted for earlier Android (<26) in ThreeTenABP. See How to use ThreeTenABP….

Links

  • Related question: SimpleDateFormat giving wrong date instead of error
  • Related question: SimpleDateFormat parse(string str) doesn't throw an exception when str = 2011/12/12Simpledateformat Parse(String Str) Doesn't Throw an Exception When Str = 2011/12/12Aaaaaaaaaa?
  • Oracle tutorial: Date Time explaining how to use java.time.

SimpleDateFormat doesn't work as expected

DateFormat#parse doesn't necessarily use the entire string:

Parses text from the beginning of the given string to produce a date. The method may not use the entire text of the given string.

(my emphasis)

SimpleDateFormat's docs tell us that yyyy doesn't necessarily mean it will require four digits for a year:

Year:

...

  • For parsing, if the number of pattern letters is more than 2, the year is interpreted literally, regardless of the number of digits. So using the pattern "MM/dd/yyyy", "01/11/12" parses to Jan 11, 12 A.D.

So it's correct (if possibly surprising) that it parses that string in the year 201.

You can use parse(String,ParsePosition) to figure out whether the entire string has been consumed, or validate it with a regular expression before parsing. Here's a version that will check that the whole string has been parsed, and not just the first characters:

public static boolean isThisDateValid(String dateToValidate, String dateFormat) {
if (dateToValidate == null) {
return false;
}

SimpleDateFormat sdf = new SimpleDateFormat(dateFormat);
sdf.setLenient(false);

ParsePosition position = new ParsePosition(0);
Date date = sdf.parse(dateToValidate, position);
return date != null && position.getIndex() == dateToValidate.length();
}

Simple date format parses 2k11 like values which should throw an exception

I started digging through the source code for SimpleDateFormat, and read enough to glean what appears to be happening here. What is happening is that your year component 2k11 is being interpreted as a two digit year (actually one digit, 2), and everything that follows is being ignored as not being part of the date pattern. Consider the following code which produces the same result:

  DateFormat dateFormat = new SimpleDateFormat("dd MMM, yyyy");
dateFormat.setLenient(false);
Date blah = dateFormat.parse("01 Jan, 2 Jon Skeet");
System.out.println(blah);

This prints:

Sun Jan 01 00:00:00 CET 2

As you can see, the intepreted year is 2, and the Jon Skeet blurb was ignored. To understand exactly why SimpleDateFormat is doing this, read the source code.

Raise exception when invalid string is passed to SimpleDateFormat in JAVA

java.time

I recommend that you use java.time, the modern Java date and time API, for your date work. Advantages in your particular case include that the formatter you need is already built in and it does throw the exception that you ask for.

For demonstration I am using this auxiliary method:

public static void tryToParse(String dateString) {
try {
LocalDate date
= LocalDate.parse(dateString, DateTimeFormatter.BASIC_ISO_DATE);
System.out.println(dateString + " -> " + date);
} catch (DateTimeParseException dtpe) {
System.out.println(dateString + ": " + dtpe.getMessage());
}
}

Trying it out:

    // Valid date
tryToParse("20200507");
// Invalid date value
tryToParse("20210229");
// Text after date
tryToParse("20200623Q");
// Example from question
tryToParse("9450524Q");

Output is:

20200507 -> 2020-05-07
20210229: Text '20210229' could not be parsed: Invalid date 'February 29' as '2021' is not a leap year
20200623Q: Text '20200623Q' could not be parsed, unparsed text found at index 8
9450524Q: Text '9450524Q' could not be parsed at index 6

Please also enjoy the precise and helpful exception messages. What happened in the last case was: year 9450 and month 52 were parsed, but since 4Q is not a valid two-digit date, the exception was thrown (before validating whether 52 is a valid month number).

What happened in your code

The SimpleDateFormat class is a notorious troublemaker. You have discovered a central design problem with it, but certainly not the only one. What it did was: It parsed 4-digit year, 9450, and two-digit month, 52. There are 12 months in a year, but a SimpleDateFormat with standard settings doesn’t care. It converts 48 of the months to 4 years and ends up in the 4th month 4 years later. Finally 4 is parsed as day of month. The remainder of the text, the Q, is ignored.

As I see it your example exhibits three flaws of SimpleDateFormat:

  1. With standard settings it is lenient, it accepts an invalid month number.
  2. When asked to parse two digits and finding only one, it settles with that one digit without reporting the error.
  3. In the case of unparseable text after the parsed text it does not report any error either.

Links

  • Oracle tutorial: Date Time explaining how to use java.time.
  • Related questions:

    • SimpleDateFormat giving wrong date instead of error

      • Modern answer
    • SimpleDateFormat parse(string str) doesn't throw an exception when str = 2011/12/12Simpledateformat Parse(String Str) Doesn't Throw an Exception When Str = 2011/12/12Aaaaaaaaaa?

      • Modern answer

SimpleDateFormat possible error in method parse

Make sure you setLenient(false) on the SimpleDateFormat. Otherwise it will parse lots of invalid dates without exception and you can't trust the output too much.

Java SimpleDateFormat accepts invalid date. with format 'MM/dd/yyyy' parses '01/01/2021anything'

This happens because it is implemented this way.

SimpleDateFormat will inspect the format, and start to parse each token present in the format, but does not necessarily parse the whole string, according to the docs:

Parses text from the beginning of the given string to produce a date. The method may not use the entire text of the given string.

You shouldn't be using SimpleDateFormat and Date anyways, because they have more problems than this one.

Use classes from the java.time package instead. In your case, you should probably be using LocalDate. A simple translation of your code to code using java.time would be something like this:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MM/dd/yyyy");
LocalDate d = LocalDate.parse("01/01/2021anything", formatter);
System.out.println(d);

The abovementioned code will throw a DateTimeParseException, because it doesn't like anything.



Related Topics



Leave a reply



Submit