Reputation: 949
Why does this test pass, while the month value is obviously invalid (13)?
@Test
public void test() {
String format = "uuuuMM";
String value = "201713";
DateTimeFormatter.ofPattern(format).withResolverStyle(ResolverStyle.STRICT)
.parse(value);
}
When using a temporal query, the expected DateTimeParseException
is thrown:
DateTimeFormatter.ofPattern(format).withResolverStyle(ResolverStyle.STRICT)
.parse(value, YearMonth::from);
What happens when no TemporalQuery
is specified?
EDIT: the 13 value seems to be a special one, as I learned thanks to the answer of ΦXocę 웃 Пepeúpa ツ (see Undecimber).
But the exception is not thrown even with another value, like 50:
@Test
public void test() {
String format = "uuuuMM";
String value = "201750";
DateTimeFormatter.ofPattern(format).withResolverStyle(ResolverStyle.STRICT)
.parse(value);
}
Upvotes: 8
Views: 1690
Reputation:
I've made some debugging here and found that part of the parsing process is to check the fields against the formatter's chronology.
When you create a DateTimeFormatter
, by default it uses an IsoChronology
, which is used to resolve the date fields. During this resolving phase, the method java.time.chrono.AbstractChronology::resolveDate
is called.
If you look at the source, you'll see the following logic:
if (fieldValues.containsKey(YEAR)) {
if (fieldValues.containsKey(MONTH_OF_YEAR)) {
if (fieldValues.containsKey(DAY_OF_MONTH)) {
return resolveYMD(fieldValues, resolverStyle);
}
....
return null;
As the input has only the year and month fields, fieldValues.containsKey(DAY_OF_MONTH)
returns false
, the method returns null
and no other check is made as you can see in the Parsed
class.
So, when parsing 201750
or 201713
without a TemporalQuery
, no additional check is made because of the logic above, and the parse
method returns a java.time.format.Parsed
object, as you can see by the following code:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuuMM").withResolverStyle(ResolverStyle.STRICT);
TemporalAccessor parsed = fmt.parse("201750");
System.out.println(parsed.getClass());
System.out.println(parsed);
The output is:
class java.time.format.Parsed
{Year=2017, MonthOfYear=50},ISO
Note that the type of the returned object is java.time.format.Parsed
and printing it shows the fields that were parsed (year and month).
When you call parse
with a TemporalQuery
, though, the Parsed
object is passed to the query and its fields are validated (of course it depends on the query, but the API built-in ones always validate).
In the case of YearMonth::from
, it checks if the year and month are valid using the respective ChronoField
's (MONTH_OF_YEAR and YEAR) and the month field accepts only values from 1 to 12.
That's why just calling parse(value)
doesn't throw an exception, but calling with a TemporalQuery
does.
Just to check the logic above when all the date fields (year, month and day) are present:
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("uuuuMMdd").withResolverStyle(ResolverStyle.STRICT);
fmt.parse("20175010");
This throws:
Exception in thread "main" java.time.format.DateTimeParseException: Text '20175010' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 50
As all the date fields are present, fieldValues.containsKey(DAY_OF_MONTH)
returns true
and now it checks if it's a valid date (using the resolveYMD
method).
Upvotes: 5
Reputation: 1559
It is a little odd that an exception is not thrown when parse
is called without a given TemporalQuery
. Some of the documentation for the single argument parse
method:
This parses the entire text producing a temporal object. It is typically more useful to use parse(CharSequence, TemporalQuery). The result of this method is TemporalAccessor which has been resolved, applying basic validation checks to help ensure a valid date-time.
Note that it says it is "typically more useful to use parse(CharSequence, TemporalQuery)". In your examples, parse
is returning a java.time.format.Parsed
object, which is not really used for anything other than creating a different TemporalAccessor
.
Note that if you try to create a YearMonth
from the returned value, an exception is thrown:
YearMonth.from(DateTimeFormatter.ofPattern(format)
.withResolverStyle(ResolverStyle.STRICT).parse(value));
throws
Exception in thread "main" java.time.DateTimeException: Unable to obtain YearMonth from TemporalAccessor: {Year=2017, MonthOfYear=50},ISO of type java.time.format.Parsed
at java.time.YearMonth.from(YearMonth.java:263)
at anl.nfolds.Test.main(Test.java:21)
Caused by: java.time.DateTimeException: Invalid value for MonthOfYear (valid values 1 - 12): 50
at java.time.temporal.TemporalAccessor.get(TemporalAccessor.java:224)
at java.time.YearMonth.from(YearMonth.java:260)
... 1 more
Documentation for Parsed
:
A store of parsed data.
This class is used during parsing to collect the data. Part of the parsing process involves handling optional blocks and multiple copies of the data get created to support the necessary backtracking.
Once parsing is completed, this class can be used as the resultant TemporalAccessor. In most cases, it is only exposed once the fields have been resolved.
Since:1.8
@implSpecThis class is a mutable context intended for use from a single thread. Usage of the class is thread-safe within standard parsing as a new instance of this class is automatically created for each parse and parsing is single-threaded
Upvotes: 1
Reputation: 48258
The month 13 is called : Undecimber
The gregorian calendar that many of us use allows 12 months only but java includes support for calendars which permit thirteen months so it depends on what calendar system you are talking about
For example, the actual maximum value of the MONTH field is 12 in some years, and 13 in other years in the Hebrew calendar system. So the month 13 is valid
Upvotes: 3