12

Considering that flow control via exceptions is considered (by many) to be an anti-pattern, is it possible to validate that a string represents a valid date using the temporal library (java.time.*), without catching an exception?

Consider the following code, which relies upon an internal explosion:

public static boolean isValidDateFormat(String date, DateTimeFormatter formatter) {
    try {
        formatter.parse(date);
        return true;
    } catch (DateTimeParseException e) {
        return false;
    }
}

Can this be achieved without catching a parse explosion? Is there something akin to formatter.isValid(date) (even if that internally explodes - that would be up to the JDK impl which is "behind the curtain").

Bohemian
  • 365,064
  • 84
  • 522
  • 658
  • 1
    Why don't use regex? – Daniel Jipa Jul 24 '15 at 06:13
  • 2
    @Daniel well, because it's not clear, and requires extra coding effort (and testing). My question is more along the lines of "is there some hidden JDK library that I'm not aware of that can, given a `DateTimeFormatter` determine if a given string is valid" – Bohemian Jul 24 '15 at 06:23
  • I see. I do not know about the existence of this valid method. In JDK sources you cannot find anything when using debugger? – Daniel Jipa Jul 24 '15 at 06:27
  • Maybe take a look about jodatime, which implements some more functionnalities around time, time format and time parsing. It uses less exceptions. [Joda time](http://www.joda.org/joda-time/) – Aeldred Jul 24 '15 at 06:35

1 Answers1

3

The format engine of java.time.format always works with internal exceptions to control the flow. This is even true if you try to use a ParsePosition. An exception occurs, and the ParsePosition-object does not even report an error:

   pp = new ParsePosition(0);
   try {
       TemporalAccessor t = 
           DateTimeFormatter.ofPattern("uuuu-MM-dd")
             .withResolverStyle(ResolverStyle.STRICT)
             .parse("2015-02-29", pp);
   } catch (RuntimeException e) {
       e.printStackTrace();
       System.out.println("Error! " + pp);
       // Error! java.text.ParsePosition[index=10,errorIndex=-1]
   }

The javadoc explains:

The operation of this method is slightly different to similar methods using ParsePosition on java.text.Format. That class will return errors using the error index on the ParsePosition. By contrast, this method will throw a DateTimeParseException if an error occurs, with the exception containing the error index. This change in behavior is necessary due to the increased complexity of parsing and resolving dates/times in this API.

The following example tries to avoid an exception by using the method parseUnresolved:

   ParsePosition pp = new ParsePosition(0);
   try {
        TemporalAccessor t = 
            DateTimeFormatter.ofPattern("uuuu-MM-dd")
             .withResolverStyle(ResolverStyle.STRICT)
             .parseUnresolved("2015-02-29", pp);
        System.out.println("Info! " + t + "/" + pp); // note, no error in pp here!
        // Info! {DayOfMonth=29, MonthOfYear=2, Year=2015},null,null/java.text.ParsePosition[index=10,errorIndex=-1]
        boolean leapyear = Year.from(t).isLeap();
        MonthDay md = MonthDay.from(t);
        if (!leapyear && md.getDayOfMonth() == 29 && md.getMonth().getValue() == 2) {
            System.out.println("Error!"); // hand-made validation covering a special case
        }
   } catch (RuntimeException e) {
        e.printStackTrace(); // does not happen for given input
   }

This works without exception but you have to write the validation code yourself which asks for trouble.

I have always considered this approach of throwing exceptions for controlling the program flow as bad coding practice and therefore designed my own library Time4J in such a way that it strives for avoiding internal exceptions as good as possible (not in every case but in most cases without exception).

   ParseLog plog = new ParseLog();
   PlainDate date = ChronoFormatter.ofDatePattern("uuuu-MM-dd", PatternType.CLDR, Locale.ROOT).parse("2015-02-29", plog);
   System.out.println(date); // null
   System.out.println(plog.isError() + "/" + plog.getErrorMessage());
   // true/Validation failed => DAY_OF_MONTH out of range: 29 [parsed={YEAR=2015, MONTH_AS_NUMBER=2, DAY_OF_MONTH=29}]

This code clearly demonstrates the possibility of another design. I consider the choosen design of java.time as potential bottleneck if it comes to batch processing of mass bulk data with a lot of wrong data.

Meno Hochschild
  • 38,305
  • 7
  • 88
  • 115