3

First of all please be aware I'm stuck with java6 here.

I am trying to get a Date from a String that may come in different formats. For some reason I don't get a ParseException where I'd expect one...

    import java.text.SimpleDateFormat;
    import java.util.Date;

    public class test1{

      public static void main(String argc[]){
          System.out.println(parseAllDateFormats(argc[0]));
    }

      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 Date parseAllDateFormats(String date) {
          if (date == null)
              return null;
          for (int f = 0; f < dateFormats.length; f++) {
              String format = dateFormats[f];
              try {
                  SimpleDateFormat dateFormat = new SimpleDateFormat(format);
    System.out.println("trying " + format);                
                  return dateFormat.parse(date);
              }
              catch (Exception e) {}
          }
          return null;
      }

    }

run: java test1 1980-04-25

I'd expect to get:

trying yyyyMMdd
trying yyyy/MM/dd
trying yyyy-MM-dd
Fri Apr 25 00:00:00 EST 1980

But I only get:

trying yyyyMMdd
Tue Dec 04 00:00:00 EST 1979

Any idea what's wrong?

Ole V.V.
  • 81,772
  • 15
  • 137
  • 161
Chris G.
  • 33
  • 4
  • I recommend you don’t use `SimpleDateFormat` and `Date`. Those classes are poorly designed and long outdated, the former in particular notoriously troublesome. Instead use `LocalDate` and `DateTimeFormatter`, both from [java.time, the modern Java date and time API](https://docs.oracle.com/javase/tutorial/datetime/). – Ole V.V. Jul 27 '19 at 06:09
  • 1
    Your question is not only well worked out, with (almost) minimal code example and specific expected and observed output, which always makes me happy. It’s also a question about a common problem and thus likely to help other readers in the future. It could have been the work of a seasoned Stacker. Thank you. – Ole V.V. Jul 27 '19 at 07:04
  • Possible duplicate of [How to prevent SimpleDateFormat to parse wrong formatted dates?](https://stackoverflow.com/questions/39330230/how-to-prevent-simpledateformat-to-parse-wrong-formatted-dates) – Ole V.V. Apr 27 '22 at 16:31

2 Answers2

7

SimpleDateFormat is lenient by default and won't throw exceptions even though the format of the date given might not match the format its configured to parse. You can set it to be less lenient like so:

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

This way it will check that the characters are actually valid for that date.

Josh Lambert
  • 115
  • 1
  • 5
  • 1
    Quoted from [the JavaDocs](https://docs.oracle.com/javase/7/docs/api/java/text/DateFormat.html#parse\(java.lang.String,%20java.text.ParsePosition\)): "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)." – Benjamin Jul 26 '19 at 19:49
  • 2
    Also note that javadoc of [`parse(String source)`](https://docs.oracle.com/javase/8/docs/api/java/text/DateFormat.html#parse-java.lang.String-) says *"The method may not use the entire text of the given string"*, so you might want to use the [`parse(String source, ParsePosition pos)`](https://docs.oracle.com/javase/8/docs/api/java/text/DateFormat.html#parse-java.lang.String-java.text.ParsePosition-) overload, and verify that the string value is an exact match for the date format, i.e. all the text was used. – Andreas Jul 26 '19 at 20:10
  • 1
    The description in the answer isn’t precise. A lenient `SimpleDateFormat` *does* throw an exception if it cannot make the format match. – Ole V.V. Jul 27 '19 at 06:13
  • 1
    FYI, the terribly troublesome date-time classes such as [`java.util.Date`](https://docs.oracle.com/javase/10/docs/api/java/util/Date.html), [`java.util.Calendar`](https://docs.oracle.com/javase/10/docs/api/java/util/Calendar.html), and `java.text.SimpleDateFormat` are now [legacy](https://en.wikipedia.org/wiki/Legacy_system), supplanted by the [*java.time*](https://docs.oracle.com/javase/10/docs/api/java/time/package-summary.html) classes built into Java 8 and later. See [*Tutorial* by Oracle](https://docs.oracle.com/javase/tutorial/datetime/TOC.html). – Basil Bourque Jul 27 '19 at 18:39
2

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

Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
Ole V.V.
  • 81,772
  • 15
  • 137
  • 161