Perhaps surprising this formatter works for your task:
private static final DateTimeFormatter formatter
= new DateTimeFormatterBuilder()
.appendPattern("uuuu[-MM[-dd['T'HH[:mm[:ss]]]]]")
.parseDefaulting(ChronoField.MONTH_OF_YEAR, 12)
.parseDefaulting(ChronoField.DAY_OF_MONTH, 31)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 23)
.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 59)
.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 59)
.parseDefaulting(ChronoField.NANO_OF_SECOND, 999_999_999)
.toFormatter(Locale.ROOT);
I have specified 31 as the day of month to pick when no day of month is in the string. For February 2025, which has 28 days, this gives — 2025-02-28T23:59:59.999999999, so the last day of February. It seems that java.time is smart enough to just pick the last day of the month.
Full demonstration:
String[] inputs = { "2025", "2025-01", "2025-02", "2025-01-15", "2025-01-15T09", "2025-01-15T09:15" };
for (String input : inputs) {
LocalDateTime localDateTime = LocalDateTime.parse(input, formatter);
System.out.format(Locale.ENGLISH, "%16s -> %s%n", input, localDateTime);
}
Output:
2025 -> 2025-12-31T23:59:59.999999999
2025-01 -> 2025-01-31T23:59:59.999999999
2025-02 -> 2025-02-28T23:59:59.999999999
2025-01-15 -> 2025-01-15T23:59:59.999999999
2025-01-15T09 -> 2025-01-15T09:59:59.999999999
2025-01-15T09:15 -> 2025-01-15T09:15:59.999999999
You said in a comment:
Yeah, but for simplification of the question lets truncate this Dates to the ChronoUnit.SECONDS and without zones :)
So just leave out .parseDefaulting(ChronoField.NANO_OF_SECOND, 999_999_999)
.
2025 -> 2025-12-31T23:59:59
2025-01 -> 2025-01-31T23:59:59
2025-02 -> 2025-02-28T23:59:59
2025-01-15 -> 2025-01-15T23:59:59
2025-01-15T09 -> 2025-01-15T09:59:59
2025-01-15T09:15 -> 2025-01-15T09:15:59