Repository navigation
GH-3645: accept both timestamp header formats on read (5.x backport of #3656) - #4583
Merged
Merged
Conversation
…#3656) EnvelopeMapper's two read helpers only ever accepted the mapper's own "yyyy-MM-dd HH:mm:ss:ffffff Z". A round-trippable "o" value -- from EnvelopeSerializer, from a non-Wolverine producer, or from a 6.x host that has flipped its writers -- bound as default/null in silence. The message arrives and the schedule is simply gone, which is exactly the failure mode of GH-1716 and GH-3613. Both helpers now go through one TryParseTimestamp that tries the legacy format first -- it is still what every 5.x mapper writes, so it is the common case on this line's wire -- then falls back to a standard invariant parse. ## Why this belongs on 5.x 6.x is going to flip these writers to "o", the one format a stock parser can read back: DateTime.Parse, DateTimeOffset.TryParse and XmlConvert.ToDateTime all reject the legacy shape, because the ':' before the fractional seconds and the bare trailing ' Z' are recognised by nothing else. Readers have to tolerate both in a SHIPPED release before that can happen. Without this, a 6.x sender meeting a 5.x receiver during a migration reintroduces the silent null for ScheduledTime / DeliverBy. 5.x is the old side of that migration, so it is the side that has to be taught tolerance. ## What deliberately does not change The 5.x writing side. A 5.x host keeps emitting the legacy format, which is what 5.x receivers already understand; flipping writers here would create the very mixed-version break this backport exists to prevent. A test pins that. This is purely additive tolerance for a format a 5.x reader will now meet. ## Divergence from the 6.x original 6.x reads timestamps through compiled-delegate setters as well as the two protected helpers, so #3656 had four parse sites to route. The 5.x mapper dispatches through reflection (getDateTimeOffset / getNullableDateTimeOffset), so the two helpers are the only parse sites and the change is correspondingly smaller. The format string also stays a private const here rather than moving to EnvelopeConstants.TransportHeaderDateTimeFormat, which does not exist on this branch -- less churn on a maintenance line. Not touched, but worth recording: EnvelopeSerializer on 5.x reads these values with a bare DateTimeOffset.TryParse, so it already accepts "o" and does NOT accept the legacy mapper format. That is the mirror of this bug and it is 6.x's #3641; it is a separate change and not needed for the writer flip. ## Tests envelope_mapper_reads_both_timestamp_formats_3645, ported from 6.x. On the unmodified branch, 3 of its 6 portable tests fail -- reads_the_round_trippable_format, reads_a_deliver_by_in_the_round_trippable_format, and a_value_written_by_the_envelope_serializer_is_readable_by_the_mapper -- the same 3-of-6 split #3656 reported on main, with the baseline's own build asserted green first so it could not be a stale binary. The other three pin unchanged behaviour, including that the writer still emits the legacy format. One test is new here: the_legacy_format_is_unreadable_by_a_stock_parser asserts DateTimeOffset.TryParse rejects what this mapper writes, which is why TryParseTimestamp cannot collapse to a single loose parse. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VDUrBeB4tTnKj4AExCS1nj
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.
5.x backport of #3656 (step one of #3645). Targets
5.0, notmain.Why this is needed on the 5.x line
6.x is going to flip
EnvelopeMapper's timestamp writers to"o"— the one format a stock date parser can read back. Measured against what the mapper writes today (2026-07-30 18:25:40:482802 Z):DateTime.ParseFormatExceptionDateTimeOffset.TryParsefalseXmlConvert.ToDateTimeFormatExceptionTryParseExactwith the literal formatThe
:before the fractional seconds and the bare trailingZare recognised by nothing else.Readers have to tolerate both formats in a shipped release before the writers can flip. Without this, a 6.x sender that has flipped meeting a 5.x receiver binds
nullforScheduledTime/DeliverByin silence — the message is delivered, the schedule is simply gone. That is exactly the failure mode of #1716 and #3613.5.x is the old side of that migration, so 5.x is the side that has to be taught tolerance.
mainalready has it (#3656, landed 2026-07-26);5.0has notryReadTimestampat all, which is what makes this a blocker for step two.What changes
EnvelopeMapper's two read helpers now go through oneTryParseTimestampthat tries the legacy format first — it is still what every 5.x mapper writes, so it is the common case on this line's wire — then falls back to a standard invariant parse.What deliberately does not change
The 5.x writing side. A 5.x host keeps emitting the legacy format, which is what 5.x receivers already understand; flipping writers here would create the very mixed-version break this backport exists to prevent.
the_writer_still_emits_the_legacy_formatpins that.This is purely additive tolerance for a format a 5.x reader will now meet.
Divergence from the 6.x original, deliberate
getDateTimeOffset/getNullableDateTimeOffset), so the two helpers are the only parse sites.EnvelopeConstants.TransportHeaderDateTimeFormatdoes not exist on this branch, and introducing it would be churn on a maintenance line for no behavioural gain.One thing found and not changed, recorded so it is not lost:
EnvelopeSerializeron 5.x reads these values with a bareDateTimeOffset.TryParse, so it already accepts"o"and does not accept the legacy mapper format. That is the mirror of this bug — 6.x's #3641 — and it is a separate change that the writer flip does not depend on.Verification
envelope_mapper_reads_both_timestamp_formats_3645, ported from 6.x.Red baseline: on the unmodified branch, 3 of its 6 portable tests fail —
reads_the_round_trippable_format,reads_a_deliver_by_in_the_round_trippable_format, anda_value_written_by_the_envelope_serializer_is_readable_by_the_mapper. That is the same 3-of-6 split #3656 reported onmain, which is the best evidence the backport is faithful rather than merely green. The baseline's own build was asserted clean first, so it could not have been a stale binary reporting a false result.The other three pin unchanged behaviour. One test is new here:
the_legacy_format_is_unreadable_by_a_stock_parserassertsDateTimeOffset.TryParserejects what this mapper writes, which is whyTryParseTimestampcannot collapse into a single loose parse.Full CoreTests on
5.0green, and a clean pinned Release build ofwolverine.slnx -c Release -f net9.0with no new warnings in the touched files.What comes next
#3645 stays open. Once this ships in a 5.x release, step two flips the 6.x writers to
"o"and the assertion pinning the legacy writer onmainflips with it — deliberately, not by accident.🤖 Generated with Claude Code
https://claude.ai/code/session_01VDUrBeB4tTnKj4AExCS1nj