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").
DateValidator validator = new DateValidatorUsingDateFormat("MM/dd/yyyy"); assertTrue(validator. isValid("02/28/2019")); assertFalse(validator. isValid("02/30/2019")); This was the most common solution before Java 8.
Yes, it is: DateTimeFormat is thread-safe and immutable, and the formatters it returns are as well. Implementation Requirements: This class is immutable and thread-safe.
Run it through SimpleDateFormat with yyyyMMdd ? input. matches("\\d{8}") tests if input consists of 8 digits.
Formatter for printing and parsing date-time objects. This class provides the main application entry point for printing and parsing and provides common implementations of DateTimeFormatter : Using predefined constants, such as ISO_LOCAL_DATE. Using pattern letters, such as uuuu-MMM-dd.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With