Use optional fields and parseBest
You just want to check, I understand this, but later on you will most likely want to extract the data in a suitable way. Fortunately, and as you already wrote, Java 8 has such a method, parseBest .
parseBest works with optional fields. Therefore, first determine the format you want to yyyy[-MM[-dd]] : yyyy[-MM[-dd]] , with brackets ( [ and ] ) enclosing optional fields.
parseBest also requires that you provide several TemporalQuery<R> . This is actually just a functional wrapper around the <R> R queryFrom(TemporalAccessor) template method. Thus, we can define TemporalQuery<R> simply as Year::from . Good: this is exactly what we want. The fact is that parseBest not quite correctly named: it will parse everything in order and stop after the first corresponding TemporalQuery that matches. So in your case, we must move from the most accurate to the less accurate. Here are the various types you want to handle: LocalDate , YearMonth and Year . So, let's just define TemporalQuery[] as LocalDate::from, YearMonth::from, Year::from . Now, if parseBest does not recognize your input, it will throw an exception.
In general, we parseBest as follows:
parseBest(DateTimeFormatter.ofPattern("yyyy[-MM[-dd]]"), LocalDate::from, YearMonth::from, Year::from);
So let's write it right:
static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy[-MM[-dd]]"); static TemporalAccessor parseDate(String dateAsString) { return FORMATTER.parseBest(dateAsString, LocalDate::from, YearMonth::from, Year::from); }
But ... you just want to check ... Well, in this case the date is calculated, and the expensive work has already been done. So, let's just define validation as follows:
public static boolean isValidDate(String dateAsString) { try { parseDate(dateAsString); return true; } catch (DateTimeParseException e) { return false; } }
I know that it is wrong to use exceptions to handle such cases, but although the current API is very powerful, this very specific case has not been taken into account, so let's just stick to it and use it as is.
Here is the complete code:
import java.time.*; import java.time.format.*; import java.time.temporal.*; class Main { private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy[-MM[-dd]]"); static TemporalAccessor parseDate(String dateAsString) { return FORMATTER.parseBest(dateAsString, LocalDate::from, YearMonth::from, Year::from); } public static boolean isValidDate(String dateAsString) { try { parseDate(dateAsString); return true; } catch (DateTimeParseException e) { return false; } } public static void main(String[] args) { String[] datesAsString = { "2018", "2018-05", "2018-05-22", "abc", "2018-" }; for (String dateAsString: datesAsString) { System.out.printf("%s: %s%n", dateAsString, isValidDate(dateAsString) ? "valid" : "invalid"); } } }
Try it online!
Exit:
2018: valid 2018-05: valid 2018-05-22: valid abc: invalid 2018-: invalid
Do you want more than checking, for example getting the actual value?
Note that you can still use data retrieved from parseBest for future reference, like this:
TemporalAccessor dateAccessor = parseDate(dateAsString); if (dateAccessor instanceof Year) { Year year = (Year)dateAccessor; // Use year } else if (dateAccessor instanceof YearMonth) { YearMonth yearMonth = (YearMonth)dateAccessor; // Use yearMonth } else if (dateAccessor instanceof LocalDate) { LocalDate localDate = (LocalDate)dateAccessor; // Use localDate }