Accept sub-millisecond fractional seconds in xs:dateTime - #298
Merged
Conversation
vpaturet
force-pushed
the
fix/subsecond-datetime-fraction
branch
from
September 7, 2026 12:46
d7384d6 to
c4835e5
Compare
xs:dateTime places no limit on the number of fractional-second digits, but the parse formatter accepted at most three. A schema-valid timestamp carrying microseconds threw a DateTimeParseException, which JAXB reports to the default ValidationEventHandler and leaves the property unset - so consumers saw a silent null. Widen the fraction to NANO_OF_SECOND with a maximum of nine digits. Both the field and the width have to change: widening the width alone leaves MILLI_OF_SECOND unable to carry more than three digits, which parses without error but silently truncates. The fraction stays optional and minimal-width, so marshalled output is unchanged for every value that can round-trip today.
vpaturet
force-pushed
the
fix/subsecond-datetime-fraction
branch
from
September 7, 2026 12:57
c4835e5 to
42ee22d
Compare
Contributor
|
This fixes the problem with the operating period mentioned in the description: <UicOperatingPeriod version="153" id="IT:ITH4:UicOperatingPeriod:U182_20260416_0">
<FromDate>2026-05-03T23:30:09.398629+02:00</FromDate>
<ToDate>2026-08-31T00:00:00</ToDate>
<ValidDayBits>1111110111111011111101111110101111011111001111100111110011111001111100111110011111001111100111110011111001111100111110011111001</ValidDayBits>
</UicOperatingPeriod> |
vpaturet
marked this pull request as ready for review
September 7, 2026 13:18
Contributor
The xs:dateTime adapter was widened to nanosecond precision, but LocalTimeISO8601XmlAdapter carried the identical defect: its parse formatter accepted at most three fractional digits. Since bindings.xjb routes every xs:time element through this adapter, a schema-valid DepartureTime, ArrivalTime or WaitTime carrying microseconds threw a DateTimeParseException and was left unset by the default ValidationEventHandler - fields far more commonly populated than the xs:dateTime ones that motivated the first fix. A producer emitting microseconds in a date-time emits them in passing times too, so fixing only xs:dateTime left the same data silently disappearing. Widen the fraction to NANO_OF_SECOND with a maximum of nine digits, as in LocalDateTimeISO8601XmlAdapter. Marshalled output is unchanged for every value that can round-trip today: the fraction stays optional and minimal-width. Caching is unaffected, since the cache admits only times whose nano is zero.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



xs:dateTimeplaces no limit on the number of fractional-second digits, but theparse formatter in
LocalDateTimeISO8601XmlAdapteraccepts at most three:A schema-valid timestamp carrying microseconds therefore fails to parse. Because
JAXB routes the resulting
DateTimeParseExceptionthrough the defaultValidationEventHandler, the property is simply left unset — so downstreamconsumers see a silent
nullrather than an error.This was hit in the wild by OpenTripPlanner on an Italian NeTEx dataset
(opentripplanner/OpenTripPlanner#7956), where a
UicOperatingPeriodwasdiscarded because its
FromDatewas dropped:Note that the
+02:00offset is not the problem — the formatter already has anoptional offset section and discards it, which is the long-standing deliberate
behaviour discussed in #4. It is the six-digit fraction that fails.
The change
One line — widen the fraction to nanosecond precision:
Both the field and the width have to change. Widening the width alone — as
proposed in #41 — makes parsing succeed but silently truncates, because
MILLI_OF_SECONDcan only carry three digits:MILLI_OF_SECOND, 0, 9(#41)NANO_OF_SECOND, 0, 9(here)...09.398629+02:00...09.398(truncated)...09.398629...00.012345+01:00...00.012(truncated)...00.012345Compatibility
Marshalled output is unchanged for every value that can round-trip today. The
fraction stays optional and minimal-width, so whole seconds still emit
2026-08-31T00:00:00and milliseconds still emit...09.398. Output differsonly for
LocalDateTimevalues carrying sub-millisecond precision — whichcurrently cannot enter the model through unmarshalling at all, since they throw.
The wider format only preserves precision that is presently discarded.
There is no measurable parse cost;
maxWidthbounds the digit loop rather thanadding work. If anything this is faster on the affected input, since it replaces
exception construction with a successful parse.
Prior discussion
exists that has more than 3 digits"). It received no review and was closed
unmerged.
input that is not schema-valid, which is the basis on which it was
questioned. This case is the exception: the example above validates against the
official NeTEx schema and still fails to parse, as demonstrated by
@stjugi in
Timestamp with more flexibility (e.g. nanoseconds, spaces as separators) #294 (comment).
This PR takes only that piece and leaves the rest of Timestamp with more flexibility (e.g. nanoseconds, spaces as separators) #294 untouched.
Not addressed here
parseDefaulting(OFFSET_SECONDS, OffsetDateTime.now()...)is evaluated once atclass-initialisation, baking the JVM's startup offset into a static field, so a
long-running process that crosses a DST boundary keeps a stale offset. It is
also redundant, since the parse target is a
LocalDateTimeand the offset isdiscarded either way.