Encode a DateTime as ticks rather than a file time - #95
Draft
HowardvanRooijen wants to merge 1 commit into
Draft
HowardvanRooijen wants to merge 1 commit into
HowardvanRooijen wants to merge 1 commit into
Conversation
A DateTime travelled through Z3 as a Windows file time, which counts from 1601-01-01 and cannot express anything earlier. A constant before that threw out of ToFileTimeUtc during translation, and a satisfiable theorem whose models lay before it - t.X1 < 1601 - solved and then threw while its solution was read, blaming a fileTime parameter no caller had supplied. Two thirds of the type's range was unusable. It now travels as its ticks, which cover the whole range. The kind convention from #56 is kept exactly: a Local value is converted to UTC on the way in, Unspecified is taken as UTC, and everything is read back as UTC. An unconstrained symbol now completes to DateTime.MinValue rather than 1601-01-01; nothing asserted the old value. The read is guarded. The symbol is an unbounded integer, so a constraint like t.X1 > DateTime.MaxValue still has a model in Z3, and the guard turns that into an OverflowException naming the symbol and the range instead of the constructor's complaint about a parameter. The same trade-off as the checked read of a short, and #87 applies here too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
jsboige
added a commit
to MyIntelligenceAgency/Z3.Linq
that referenced
this pull request
Sep 4, 2026
…rded read-back Port of endjin#95 adapted to the 4.12.2 snapshot: - ExpressionVisitor.VisitConstant: a DateTime constant is now encoded as its UTC ticks (Kind-aware: Local values are normalized via ToUniversalTime() before encoding), instead of ToFileTime() which is undefined before 1601-01-01 and loses the Kind - Theorem read-back (ConvertZ3Expression + ConvertScalarExpr): values for DateTime symbols are rebuilt with new DateTime(ticks, DateTimeKind.Utc); an out-of-range model value raises an OverflowException naming the symbol instead of letting the DateTime ctor throw a bare ArgumentOutOfRangeException Red run (4 DateTime tests failing, all others green): https://github.com/MyIntelligenceAgency/Z3.Linq/actions/runs/33862719891 Green run: see next commit's dispatch See #14445
jsboige
added a commit
to MyIntelligenceAgency/Z3.Linq
that referenced
this pull request
Sep 4, 2026
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.
Fixes #83.
The defect
A
DateTimetravelled through Z3 as a Windows file time - 100ns intervals counted from1601-01-01 UTC - so nothing earlier could be written or read:
Roughly two thirds of the type's range was unusable, and the second shape is the worse one:
nothing about it is malformed, Z3 solves it, and the read path then refuses to express the answer
fileTimeparameter no caller supplied.Measured before the change, fifteen shapes. Beyond the issue's rows: one tick before 1601 fails
on the write; a
DateTime[]element before 1601 fails the same way; andt.X1 > DateTime.MaxValue- which has no solution inDateTimeterms - returns a model anddies on the read with the
fileTimemessage.The change
Ticks instead of a file time.
DateTime.Tickscounts from 0001-01-01 and covers the wholerange, fits an
Int64exactly as the file time did, and the encoding is internal, so nothingoutside the library depends on the choice. The write path becomes
ExpressionVisitor.ToUtcTicks,the two read paths become
Theorem.ToDateTime.#56's kind convention is kept exactly.
ToFileTimeUtcconverted aLocalvalue to UTC andtook
Unspecifiedto be UTC already;ToUtcTicksdoes the same, and the read path producesUtcas before. Rows K and L of the probe - a Local constant coming back as the same instant inUTC, an Unspecified constant coming back with identical ticks - are unchanged before and after,
and the #56 tests pass untouched.
The read is guarded, and the guard names the symbol. The symbol is an unbounded integer, so
t.X1 > DateTime.MaxValuestill has a model in Z3; the guard turns that intorather than the constructor's complaint. The same trade-off as the checked read of a
short(#63): loud rather than wrong, until #87 bounds the symbol so Z3 cannot choose such a value. With
the encoding starting at tick zero, the bottom of the range is now reachable only by constraining
below
DateTime.MinValueitself.One visible change beyond the fix. An unconstrained
DateTimesymbol completes toDateTime.MinValuerather than1601-01-01, because completion supplies zero and zero now meanssomething else. The issue flagged this; no test asserted the old value, and the remarks that
mentioned it are updated.
Measured after the change
== 1500-01-01,== DateTime.MinValue, one tick before 1601ArgumentOutOfRangeExceptionon the writeUtcDateTime[]element== 1500-01-01ArgumentOutOfRangeExceptionon the writeUtc< 1601-01-01ArgumentOutOfRangeExceptionon the read1600-12-31T23:59:59.9999999Z> DateTime.MaxValue,< DateTime.MinValueArgumentOutOfRangeException (Parameter 'fileTime')OverflowExceptionnamingX1and the range== DateTime.MaxValue,== 1601-01-01, Local kind, Unspecified kind1601-01-01Z0001-01-01ZTests
247 → 254. The two #83 pins in
SymbolTypeMarshallingTestsare replaced, and the remarks acrossthree files that described the encoding as a file time now describe it as ticks.
Solve_DateTimeSymbolBefore1601_RoundTripsTheValueMinValueas the bottom boundarySolve_DateTimeSymbolOneTickBefore1601_RoundTripsTheValueSolve_DateTimeSymbolConstrainedBefore1601_ReturnsAnInstantBefore1601Solve_DateTimeSymbolConstrainedBeyondMaxValue_ThrowsOverflowExceptionNamingIt/..BelowMinValue..Solve_DateTimeArrayElementBefore1601_RoundTripsTheValue(inCollectionSymbolTests)DateTimearmMutation results
OrderByDescendingDateTime test. Only on a machine with a non-zero UTC offset - this one is on BST - which is the same blind spot #56 recorded: UTC CI cannot see a Local/UTC confusionArgumentOutOfRangeExceptionfrom the constructorUnspecifiedrather thanUtcVerification
dotnet build solutions/Z3.Linq.slnx -c Release- clean,TreatWarningsAsErrorsanddocumentation generation on
./build.ps1 -Configuration Release- 46 tasks, 0 errors, 0 warningsRelease note
Releases remain on hold under #60 until Microsoft.Z3 5.x reaches nuget.org, so this reaches
mainbut not consumers. Nothing about the hold changes.