1

I have a datestring in the following format: "dd/MM/yyyy HH:mm:ss". I want to create a Date object from it and I'm using the SimpleDateFormat class. Our application accepts many different date formats that are stored in a String array. What we do is iterate through that array, use applyPattern to our SimpleDateFormat object and try to parse the given datestring. If it throws an exception we try the next date format in our array etc.

However I found out that the parse method of the SimpleDateFormat class doesn't necessarily attempt to parse the whole string. If it successfully creates a Date object from parts of the string then it returns it. The problem here is that our given datestring contains both date and time data, however in our first parse attempt the SimpleDateFormat uses a simpler pattern: "dd/MM/yyyy". And since during the parsing process it finds a matching date in the given datestring it stops there and creates a Date object that has no time information.

Is there a way to force SimpleDateFormat to parse the whole string it is given?

String dateString = "01/01/2015 05:30:00";
Date date = null;
for (String format : Constants.DATE_FORMATS) {//String Array that contains many date format strings.
    try {
        simpleDateFormat.applyPattern(format);//First format applied is "dd/MM/yyyy".
        date = simpleDateFormat.parse(dateString);
        //No exception thrown it accepts the "dd/MM/yyyy" part of the dateString even though the string itself contains even more data.
    }
    catch (Exception e) {}
}
//Returns a date object with the time set to 00:00:00
PentaKon
  • 4,139
  • 5
  • 43
  • 80
  • 3
    Can you show some code so that we can understand better what your problem is? – Tunaki Sep 25 '15 at 08:02
  • And show example data as well. – Basil Bourque Sep 25 '15 at 08:07
  • @Tunaki Isn't it clear already? OP likes to prevent patterns like *"dd/MM/yyyy"* to parse Strings with the pattern *"dd/MM/yyyy HH:mm:ss"* (e.g. "25/09/2015 10:00:00"), since he would loose the time value. – Tom Sep 25 '15 at 08:09
  • @Tom Exactly. I want to perfectly match the format with the string given otherwise throw exception. – PentaKon Sep 25 '15 at 08:15
  • Can you change the order in which the patterns are tested, and use this one last? Or can you extend `SimpleDateFormat` and test for the string length before you parse it? – dotvav Sep 25 '15 at 08:17
  • @dotvav The pattern order idea was the first we had but it is hacky in my opinion because we accept as many 'official' patterns as possible. Which pattern do you put first? The longest one i guess but that's a weird way to do it. As for the string length, many patterns have the same length i.e.: `dd/MM/yyyy` and `dd-MM-yyyy` – PentaKon Sep 25 '15 at 08:19
  • If you sort them by reversed natural order of `String` then `"dd/MM/yyyy"` will be tested after `"dd/MM/yyyy HH:mm:ss"`. And, more generally, every pattern `ABCDE` that starts with an existing other shorter version `ABC` will be tested before. – dotvav Sep 25 '15 at 08:42
  • FYI, the terribly troublesome old 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 Jan 20 '19 at 01:49
  • This is true but so are legacy systems :( – PentaKon Jan 21 '19 at 15:20

4 Answers4

3

You can check how much of the input was parsed by supplying a ParsePosition. The parse method updates the position to tell you how much it parsed.

ParsePosition pos = new ParsePosition(0);
date = simpleDateFormat.parse(dateString, pos);

if (pos.getIndex() < dateString.length()) {
    // did not parse all of dateString
}
Joni
  • 108,737
  • 14
  • 143
  • 193
1

For those who want to get a java.util.Date object back and avoid using a 3rd party library I used a compacted version of the answer:

for (String format : Constants.DATE_FORMATS) {
        try {
            return Date.from(LocalDateTime.parse(dateString, DateTimeFormatter.ofPattern(format)).atZone(ZoneId.systemDefault()).toInstant());
        } catch (DateTimeParseException e) {}
    }

With this code block it will use the correct format, using java 8's own java.time library which matches the formats correctly. Don't forget to add the try-catch block! The IDE will not ask for it since DateTimeParseException is a runtime exception but we want handle it in order to go to the next loop iteration.

PentaKon
  • 4,139
  • 5
  • 43
  • 80
  • It’s still better if you can avoid the `Date` class completely. If one does need a `Date` for a legacy API, I recommend this answer. – Ole V.V. Dec 25 '20 at 19:47
0

I don't think there is a way to force the SimpleDateFormat to parse the whole String, but you can compare the length of the format with the length of the input

String[] formats = new String[] {"dd/MM/yyyy", "dd/MM/yyyy HH:mm:ss"};
String input = "25/09/2015 10:25:31";
Date result = null;

for (String format: formats) {
    if (input != null && format.length() == input.length()) {
        try {
            SimpleDateFormat sdFormat = new SimpleDateFormat(format);
            result = sdFormat.parse(input);
            break;
        }
        catch (ParseException e) {
        }
    }
}

EDIT Another way is to use JodaTime to parse the String.

String[] formats = new String[] { "dd/MM/yyyy", "dd/MM/yyyy HH:mm:ss" };
String input = "25/09/2015 10:25:31";
LocalDateTime result = null;

for (String format : formats) {
    try {
        DateTimeFormatter sdFormat = DateTimeFormat.forPattern(format);
        result = sdFormat.parseLocalDateTime(input);
        break;
    }
    catch (IllegalArgumentException e) {
    }
}
System.out.println(result);

Date resultDate = result.toDate();
System.out.println(resultDate);

This gives you

2015-09-25T10:25:31.000
Fri Sep 25 10:25:31 CEST 2015
Pinguin895
  • 999
  • 2
  • 11
  • 28
  • That's the solution we came up with as well but we wanted to avoid post parse checks. If there's no way to force a full string parse then we'll go with that. – PentaKon Sep 25 '15 at 08:23
  • @Konstantine okay, then the only other solution comming into my mind is using JodaTime (Like described in my edit). But that would result in using an external library – Pinguin895 Sep 25 '15 at 08:36
  • Isn't JodaTime now a core library for Java 8? I think it is the `Time` class and all its affiliations. – PentaKon Sep 25 '15 at 08:39
  • No, Java 8 just uses the same Classnames, but it is not the same as JodaTime – Pinguin895 Sep 25 '15 at 08:41
  • For the non-Joda version, how would you handle more complex patterns like those including 'Z', which matches more than one character? – Jeff French Mar 06 '16 at 20:03
0

You may use something like that instead of SimpleDateFormat:

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

class StrictDateFormat extends SimpleDateFormat {
    private String pattern;

    public void applyPattern(String pattern) {
        super.applyPattern(pattern);
        this.pattern = pattern;
    }

    @Override
    public Date parse(String source) throws ParseException {
        if (source.length() != pattern.length()) {
            throw new ParseException("Wrong size", pattern.length());
        }
        return super.parse(source);
    }
}

This will throw an exception if the string you parse is not the same length as the pattern. That should prevent "01/01/2015 05:30:00" from being parsed by "dd/MM/yyyy". This one will be always strict, and not only for "dd/MM/yyyy".

dotvav
  • 2,808
  • 15
  • 31