-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parse composite patterns using ClassicFormat.parseObject backport(#40100) #40503
Conversation
Pinging @elastic/es-core-infra |
Java-time fails parsing composite patterns when first pattern matches only the prefix of the input. It expects pattern in longest to shortest order. Because of this constructing just one DateTimeFormatter with appendOptional is not sufficient. Parsers have to be iterated and if the parsing fails, the next one in order should be used. In order to not degrade performance parsing should not be throw exceptions on failure. Format.parseObject was used as it only returns null when parsing failed and allows to check if full input was read. closes elastic#39916
return (TemporalAccessor) object; | ||
} | ||
} | ||
throw new DateTimeParseException("Failed to parse with all enclosed parsers", input, 0); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we iterate over parsers we would not know exact failure reason for each parsers. Tests that assert about the exact reason (like position where it failed) had to be changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks! Looks mostly fine but I left a few minor comments.
try { | ||
return doParse(input); | ||
} catch (DateTimeParseException e) { | ||
throw new IllegalArgumentException("failed to parse date field [" + input + "] with format [" + format + "]", e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that this method will never throw DateTimeParseException
as mentioned in the Javadoc of DateFormatter#parse
. Can you please update the Javadoc there to reflect this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually the javadoc for the interface could state RuntimeException to be absolutely correct, but it would be very imprecise.
JodaDateFormatter
can throwjava.time.DateTimeException
as well asUnsupportedOperationException
andIllegalArgumentException
JavaDateFormatter
onlyIllegalArgumentException
.NullPointerException
is stated in the underlying doc forFormat.parseObject
but I don't expect this to be thrown as we already check for input not being empty and provide the position of parsing.
I will refactor the javadoc to state that IllegalArgumentException
is expected to be thrown when parsing fails (that applies in both cases). But I wonder if we should ommit the DateTimeException
and UnsupportedOperationException
(thrown only by joda)
This problem also exists in master
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there is no point in digging through the complete implementation of Java time and Joda time in order to come up with a Javadoc that documents all possible exceptions but rather document the ones that a user can "reasonably" expect when a date cannot be parsed which is in our case java.time.DateTimeException
from JodaDateFormatter
and IllegalArgumentException
(from both) so they know how to deal with them.
IMHO it is ok to document both of them on the interface. Although one of them is in fact thrown by only one of the implementations, the exception type itself is available in the JDK and thus generic enough and we should expect users to program against the interface.
ZoneId zoneId = TemporalQueries.zone().queryFrom(accessor); | ||
if (zoneId != null) { | ||
timeZone = zoneId; | ||
} | ||
|
||
return DateFormatters.from(accessor).withZoneSameLocal(timeZone).toInstant().toEpochMilli(); | ||
} | ||
} catch (IllegalArgumentException | DateTimeException e) { | ||
throw new ElasticsearchParseException("failed to parse date field [{}] in format [{}]: [{}]", e, value, format, e.getMessage()); | ||
} catch (IllegalArgumentException | DateTimeException e) {; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: There is an additional unnecessary semicolon at the end of the line.
} catch (IllegalArgumentException | DateTimeException e) { | ||
throw new ElasticsearchParseException("failed to parse date field [{}] in format [{}]: [{}]", e, value, format, e.getMessage()); | ||
} catch (IllegalArgumentException | DateTimeException e) {; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nit: Is there a reason for the additional empty line here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
accident when merging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for iterating. LGTM
29e67c5
to
358d761
Compare
Java-time fails parsing composite patterns when first pattern matches only the prefix of the input. It expects pattern in longest to shortest order. Because of this constructing just one DateTimeFormatter with appendOptional is not sufficient. Parsers have to be iterated and if the parsing fails, the next one in order should be used. In order to not degrade performance parsing should not be throw exceptions on failure. Format.parseObject was used as it only returns null when parsing failed and allows to check if full input was read.
closes #39916
backport #40100