2

I use SimpleDateFormat to parse strings to Date objects and I wonder why the results are not what I expect.

For example:

DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");

Date date = yyyyMMdd.parse("20100725");
System.out.println(date);

works as expected and outputs

Sun Jul 25 00:00:00 CEST 2010

But

Date date = yyyyMMdd.parse("2010-07-25");
System.out.println(date);

also works and outputs

Mon Dec 07 00:00:00 CET 2009

I expected a ParseException, but it seems that SimpleDateFormat interpretes the month part -07 and the day part -25 as a negative number. First I couldn't figure out how it comes to 7th of december. So I tried another value:

Date date = yyyyMMdd.parse("2010-7-25");
System.out.println(date);

and it outpus

Sun Apr 05 00:00:00 CEST 2009

So it seems that it somehow subtracts 7 month from the year 2010 which whould be 1th of may, and 25 days so the result is 5th of april 2009.

Image that you use the pattern yyyyMMdd in an service implementation and some client accidentially sends the date as yyyy-MM-dd. You will not get an exception. Instead you will get totally different dates. I guess this is not what you expect.

E.g.

String clientData = "2010-05-23";

DateFormat yyyyMMdd = new SimpleDateFormat("yyyyMMdd");
Date parsedDate = yyyyMMdd.parse(clientData);

System.out.println("Client  : " + clientData);
System.out.println("Service : " + yyyyMMdd.format(parsedDate));

Do I miss something?

How do I prevent SimpleDateFormat to parse 'wrong' dates?

Sure I can use a regular expression to check first, but is there a better way?

René Link
  • 48,224
  • 13
  • 108
  • 140
  • 2
    have you tried to use `setLenient(false)`? – Jens Sep 05 '16 at 12:07
  • No, I have not. Thanks. At least it is so simple. but I still wonder why lenient is enabled per default. Since it can lead to misinterpretation and late errors. – René Link Sep 05 '16 at 12:12
  • That question you have to ask the designer of Java ;) – Jens Sep 05 '16 at 12:13
  • I must admit that I never used `setLenient` before, but I guess I will turn it of more often. :) – René Link Sep 05 '16 at 12:19
  • @RenéLink It's easy to forget (which is why it's especially heinous that it's on by default). I just did a check on our codebase and I'm not happy. – Kayaman Sep 05 '16 at 12:32

4 Answers4

8

Use SimpleDateFormat.setLenient(false); to get an exception. Otherwise it will try to parse the input as best as it can, which is usually wrong.

For some reason they decided that leniency should be true by default, but that is hardly a surprise.

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.

Community
  • 1
  • 1
Kayaman
  • 72,141
  • 5
  • 83
  • 121
3

The accepted Answer by Cayman is correct: leniency in parsing by default is the problem.

java.time

You are using troublesome old date-time classes now supplanted by the java.time classes.

No such leniency-by-default problem in java.time. If the input does not strictly match the formatting pattern, a DateTimeParseException is thrown.

The LocalDate class represents a date-only value without time-of-day and without time zone.

ISO 8601 format

For standard ISO 8601 formatted inputs of YYYY-MM-DD, simply call parse directly.

String input = "2010-05-23";
try {
    LocalDate  ld = LocalDate.parse( input ); // Expects standard ISO 8601 input format.
} catch ( DateTimeParseException e ) {
    …
}

“Basic” ISO 8601 format

The ISO 8601 standard allows for “basic” formats that minimize the use of separators. Not that I recommend these variations, but they exist.

Currently java.time predefines only a single one of these “basic” variations, DateTimeFormatter.BASIC_ISO_DATE.

String input = "20100725";
try {
    LocalDate  ld = LocalDate.parse( input , DateTimeFormatter.BASIC_ISO_DATE ); 
} catch ( DateTimeParseException e ) {
    …
}

Custom format

For other formats, specify a formatter.

String input = "2010/07/25";
try {
    DateTimeFormatter f = DateTimeFormatter.ofPattern( "uuuu/MM/dd" );
    LocalDate  ld = LocalDate.parse( input , f ); // Custom format.
} catch ( DateTimeParseException e ) {
    …
}

Localized format

Or let java.time determine the localized format.

String input = … ;
try {
    Locale l = Locale.CANADA_FRENCH ; 
    DateTimeFormatter f = DateTimeFormatter.ofLocalizedDate( FormatStyle.MEDIUM ).withLocale( l );
    LocalDate  ld = LocalDate.parse( input , f ); // Localized format.
} catch ( DateTimeParseException e ) {
    …
}
Community
  • 1
  • 1
Basil Bourque
  • 303,325
  • 100
  • 852
  • 1,154
2
SimpleDateFormat.setLenient(false);

Is what needs to be done, or the input will be tried to be parsed well, and as you know, that doesn't always work. With the function above, the compiler will be strict about the format.

Arnav Borborah
  • 11,357
  • 8
  • 43
  • 88
0

First, if you want to parse String "2010-05-23" your mask should be "yyyy-MM-dd" and not "yyyyMMdd". Second SimpleDateFormat has serious problems as it is not Thread safe. If you use java 8 then use learn and use new package "java.time". If you use any java earlier then version 8 then use some other frameworks for parsing date. One of the most popular is Joda time. Works much better.

Michael Gantman
  • 7,315
  • 2
  • 19
  • 36
  • But if I expect `yyyyMMdd` and someone sends me a wrong formatted date. E.g. in an http request I can't use `yyyy-MM-dd`. See the example at the bottom of my question. – René Link Sep 05 '16 at 12:16
  • 1
    SimpleDateFormat.setLenient(false); will help, I still say move to java.time package or to joda.time package. Also see this article on how to parse any String to a Date: https://www.linkedin.com/pulse/java-8-javatime-package-parsing-any-string-date-michael-gantman?trk=pulse_spock-articles – Michael Gantman Sep 05 '16 at 12:22