Support ISO 8601 24:00:00 (endOfDayFrag) in XSD DateTime validation#124437
Support ISO 8601 24:00:00 (endOfDayFrag) in XSD DateTime validation#124437
Conversation
Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
…eTime Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com>
|
@tarekgh Tagged you for review next week. Not urgent. |
|
@copilot invoke the code-review skill and post your analysis/results as a comment on this pr |
🤖 Copilot Code Review — PR #124437Holistic AssessmentMotivation: Real bug. The W3C XML Schema specification explicitly defines Approach: The approach is sound — it fixes the parser to accept hour=24 (with minute/second/fraction=0 constraint), then normalizes to hour=0 + next day in the initialization method, with overflow protection for 9999-12-31T24:00:00. Summary: Detailed Findings✅ Correctness —
|
|
@copilot could you address the test suggestions mentioned in the comment #124437 (comment)? |
src/libraries/System.Private.Xml/src/System/Xml/Schema/XsdDateTime.cs
Outdated
Show resolved
Hide resolved
Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com>
…th DateTimeKind; remove section separators Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com>
Addressed in 2cfb49c:
Regarding the XDR coverage suggestion: |
|
Thanks, @tarekgh. Good catches! |
|
/ba-g the failure is unrelated Android issue. |
…otnet#124437) ## Description XML Schema validation rejected `24:00:00` (endOfDayFrag) even though W3C XML Schema 1.1 Part 2 explicitly defines it as valid. PR dotnet#124142 added the underlying ISO 8601 24:00 support to `DateTime`/`DateTimeOffset`/`TimeOnly` parsing; this extends that support to the XSD layer used by `XmlConvert` and XML Schema validation. ### Changes - **`XsdDateTime.ParseTime()`**: `hour < 24` → `hour <= 24`, with post-parse validation rejecting hour=24 when minute, second, or fraction are non-zero - **`XsdDateTime.InitiateXsdDateTime()` → `TryInitiateXsdDateTime()`**: When hour=24, sets hour=0 and adds one day. Returns `false` on overflow (e.g., `9999-12-31T24:00:00`) instead of throwing. Includes `Debug.Assert` to validate the minute/second/fraction=0 invariant when hour=24 - **`s_maxDateTimeTicksForEndOfDay` static field**: Caches the `DateTime.MaxValue.Ticks - TimeSpan.TicksPerDay` overflow threshold to avoid recomputation - **`TryParse()` / constructor**: Updated to use `TryInitiateXsdDateTime` for exception-free overflow handling - **47 new test cases**: Valid/invalid endOfDay across `xs:dateTime`, `xs:time`, XDR variants; schema validation end-to-end; leap year/year boundary transitions; fractional seconds; overflow; regression coverage for hours 0–23. Tests use consolidated `Assert.Equal(new DateTime(..., expectedKind))` assertions with `DateTimeKind` validation. ```csharp // Previously threw FormatException; now parses correctly var dt = XmlConvert.ToDateTime("2007-04-05T24:00:00", XmlDateTimeSerializationMode.RoundtripKind); // dt == 2007-04-06T00:00:00 (next day's midnight, per spec) // Schema validation now accepts endOfDayFrag var settings = new XmlReaderSettings { ValidationType = ValidationType.Schema }; settings.Schemas.Add("ns", XmlReader.Create(new StringReader(xsd))); using var reader = XmlReader.Create(new StringReader(xml), settings); new XmlDocument().Load(reader); // no longer throws ``` ### Breaking change considerations This is a **relaxation** — previously rejected inputs now parse successfully: - `2007-04-05T24:00:00` → `2007-04-06T00:00:00` (was `FormatException`) - `24:00:00` as `xs:time` → valid (was validation error) - `24:01:00`, `24:00:01`, `24:00:00.0000001` remain invalid - `9999-12-31T24:00:00` remains invalid (DateTime overflow) - Round-trip: serializing the parsed result produces `00:00:00` on the next day, not the original `24:00:00` input — consistent with ISO 8601 semantic equivalence Code relying on `FormatException` for `24:00:00` inputs or on schema validation rejecting them will observe changed behavior. ### Adjacent scope All XSD types with time components share the same `ParseTime()` path: `xs:dateTime`, `xs:time`, and XDR datetime/time variants are all covered. Types without time components (`xs:date`, `xs:gYear`, etc.) are unaffected. <!-- START COPILOT CODING AGENT TIPS --> --- 💬 We'd love your input! Share your thoughts on Copilot coding agent in our [2 minute survey](https://gh.io/copilot-coding-agent-survey). --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: jeffhandley <1031940+jeffhandley@users.noreply.github.com> Co-authored-by: tarekgh <10833894+tarekgh@users.noreply.github.com>
Description
XML Schema validation rejected
24:00:00(endOfDayFrag) even though W3C XML Schema 1.1 Part 2 explicitly defines it as valid. PR #124142 added the underlying ISO 8601 24:00 support toDateTime/DateTimeOffset/TimeOnlyparsing; this extends that support to the XSD layer used byXmlConvertand XML Schema validation.Changes
XsdDateTime.ParseTime():hour < 24→hour <= 24, with post-parse validation rejecting hour=24 when minute, second, or fraction are non-zeroXsdDateTime.InitiateXsdDateTime()→TryInitiateXsdDateTime(): When hour=24, sets hour=0 and adds one day. Returnsfalseon overflow (e.g.,9999-12-31T24:00:00) instead of throwing. IncludesDebug.Assertto validate the minute/second/fraction=0 invariant when hour=24s_maxDateTimeTicksForEndOfDaystatic field: Caches theDateTime.MaxValue.Ticks - TimeSpan.TicksPerDayoverflow threshold to avoid recomputationTryParse()/ constructor: Updated to useTryInitiateXsdDateTimefor exception-free overflow handlingxs:dateTime,xs:time, XDR variants; schema validation end-to-end; leap year/year boundary transitions; fractional seconds; overflow; regression coverage for hours 0–23. Tests use consolidatedAssert.Equal(new DateTime(..., expectedKind))assertions withDateTimeKindvalidation.Breaking change considerations
This is a relaxation — previously rejected inputs now parse successfully:
2007-04-05T24:00:00→2007-04-06T00:00:00(wasFormatException)24:00:00asxs:time→ valid (was validation error)24:01:00,24:00:01,24:00:00.0000001remain invalid9999-12-31T24:00:00remains invalid (DateTime overflow)00:00:00on the next day, not the original24:00:00input — consistent with ISO 8601 semantic equivalenceCode relying on
FormatExceptionfor24:00:00inputs or on schema validation rejecting them will observe changed behavior.Adjacent scope
All XSD types with time components share the same
ParseTime()path:xs:dateTime,xs:time, and XDR datetime/time variants are all covered. Types without time components (xs:date,xs:gYear, etc.) are unaffected.💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.