Why does SimpleDateFormat parse incorrect date?

后端 未结 2 1397
一生所求
一生所求 2020-11-29 09:20

I have date in string format and I want to parse that into util date.

var date =\"03/11/2013\"

I am parsing this as :

new S         


        
相关标签:
2条回答
  • 2020-11-29 09:26

    Jon Skeet’s answer is correct and was a good answer when it was written in 2013.

    However, the classes you use in your question, SimpleDateFormat and Date, are now long outdated, so if someone got a similar issue with them today, IMHO the best answer would be to change to using the modern Java date & time API.

    I am sorry I cannot write Scala code, so you will have to live with Java. I am using

    private static DateTimeFormatter parseFormatter
            = DateTimeFormatter.ofPattern("MM/dd/yyyy");
    

    The format pattern letters are the same as in your question, though the meaning is slightly different. DateTimeFormatter takes the number of pattern letters literally, as we shall see. Now we try:

            System.out.println(LocalDate.parse(date, parseFormatter));
    

    Results:

    • "03/11/2013" is parsed into 2013-03-11 as expected. I used the modern LocalDate class, a class that represents a date without time-of-day, exactly what we need here.
    • Passing "03/88/2013 hjhkjhk" gives a DateTimeParseException with the message Text '03/88/2013 hjhkjhk' could not be parsed, unparsed text found at index 10. Pretty precise, isn’t it? The modern API has methods to parse only part of a string if that is what we want, though.
    • "03/88/201309" gives Text '03/88/201309' could not be parsed at index 6. We asked for a 4 digit year and gave it 6 digits, which leads to the objection. Apparently it detects and reports this error before trying to interpret 88 as a day of month.
    • It does object to a day of month of 88 too, though: "03/88/2013" gives Text '03/88/2013' could not be parsed: Invalid value for DayOfMonth (valid values 1 - 28/31): 88. Again, please enjoy how informative the message is.
    • "03-08-2013" (with hyphens instead of slashes) gives Text '03-08-2013' could not be parsed at index 2, not very surprising. Index 2 is where the first hyphen is.

    Jon Skeet explained that the outdated SimpleDateFormat can be lenient or non-lenient. This is true for DateTimeFormatter too, in fact it has 3 instead of 2 resolver styles, called ‘lenient’, ‘smart’ and ‘strict’. Since many programmers are not aware of this, though, I think they made a good choice of not making ‘lenient’ the default (‘smart’ is).

    What if we wanted to make our formatter lenient?

    private static DateTimeFormatter parseFormatter
            = DateTimeFormatter.ofPattern("MM/dd/yyyy")
                    .withResolverStyle(ResolverStyle.LENIENT);
    

    Now it also parses "03/88/2013", into 2013-05-27. I believe this is what the old class would also have done: counting 88 days from the beginning of March gives May 27. The other error messages are still the same. In other words it still objects to unparsed text, to a 6 digit year and to hyphens.

    Question: Can I use the modern API with my Java version?

    If using at least Java 6, you can.

    • In Java 8 and later the new API comes built-in.
    • In Java 6 and 7 get the ThreeTen Backport, the backport of the new classes (that’s ThreeTen for JSR-310, where the modern API was first defined).
    • On Android, use the Android edition of ThreeTen Backport. It’s called ThreeTenABP, and I think that there’s a wonderful explanation in this question: How to use ThreeTenABP in Android Project.
    0 讨论(0)
  • 2020-11-29 09:52

    You should use DateFormat.setLenient(false):

    SimpleDateFormat df = new SimpleDateFormat("MM/dd/yyyy");
    df.setLenient(false);
    df.parse("03/88/2013"); // Throws an exception
    

    I'm not sure that will catch everything you want - I seem to remember that even with setLenient(false) it's more lenient than you might expect - but it should catch invalid month numbers for example.

    I don't think it will catch trailing text, e.g. "03/01/2013 sjsjsj". You could potentially use the overload of parse which accepts a ParsePosition, then check the current parse index after parsing has completed:

    ParsePosition position = new ParsePosition(0);
    Date date = dateFormat.parse(text, position);
    if (position.getIndex() != text.length()) {
        // Throw an exception or whatever else you want to do
    }
    

    You should also look at the Joda Time API which may well allow for a stricter interpretation - and is a generally cleaner date/time API anyway.

    0 讨论(0)
提交回复
热议问题