Skip to content

Translate DateTime.Ticks on SQLite without losing precision - #39598

Open
Abde1rahman1 wants to merge 1 commit into
dotnet:mainfrom
Abde1rahman1:fix/sqlite-datetime-ticks-precision
Open

Abde1rahman1 wants to merge 1 commit into
dotnet:mainfrom
Abde1rahman1:fix/sqlite-datetime-ticks-precision

Conversation

@Abde1rahman1

Copy link
Copy Markdown
Contributor

Fixes #39531

DateTime.Ticks on SQLite was translated to CAST((julianday(x) - 1721425.5) * 864000000000.0 AS INTEGER), which has two problems:

  • The multiplication produces a REAL around 6e17, beyond the range where a double represents integers exactly, so even whole-millisecond values come back wrong (e.g. ...10.123 gave 630298926101229952 instead of 630298926101230000).
  • SQLite's date and time functions only keep milliseconds, so the remaining fractional digits are lost.

As a result, a query such as Where(e => e.DateTime.Ticks == ticks) could miss rows whose value has a fractional second.

Changes

DateTime columns are now translated using integer arithmetic for the whole seconds, with the fractional digits read directly from the stored text (yyyy-MM-dd HH:mm:ss.FFFFFFF), which is exact to the tick:

(CAST(strftime('%s', substr("b"."DateTime", 1, 19)) AS INTEGER) + 62135596800) * 10000000
    + CAST(substr(substr("b"."DateTime", 21) || '0000000', 1, 7) AS INTEGER)

The fraction is cut off before calling strftime, so the result doesn't depend on how SQLite rounds it.

Any other operand (e.g. the result of another date/time function, which is already limited to milliseconds) is evaluated once, and the Julian day is rounded to whole milliseconds while it's still exactly representable as a double:

(CAST(round(julianday(x) * 86400000.0) AS INTEGER) - 148731163200000) * 10000

The exact translation is only used when the operand is a column mapped with SqliteDateTimeTypeMapping and no value converter, since it references the operand twice and relies on the text format.

Tests

  • Updated the baseline for NorthwindSelectQuerySqliteTest.Select_datetime_Ticks_component.

  • Added Ticks, Ticks_projection and Ticks_on_computed_value to DateTimeTranslationsSqliteTest, covering a sub-millisecond match on a column, all seeded values against the client result, and the non-column translation.

  • I've read the guidelines for contributing and seen the walkthrough

  • I've posted a comment on an issue with a detailed description of how I am planning to contribute and got approval from a member of the team

  • The code builds and tests pass locally (also verified by our automated build checks)

  • Commit messages follow this format:

  • Tests for the changes have been added (for bug fixes / features)

  • Code follows the same patterns and style as existing code in this repo

@Abde1rahman1
Abde1rahman1 requested a review from a team as a code owner November 10, 2026 23:55
Copilot AI lite review requested due to automatic review settings November 10, 2026 23:55

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

A critical active finding shows the new translation breaks DateTime properties mapped to REAL columns by applying text operations to numeric Julian-day values, so the change requires correction and a regression test.

1 open issue
What changed in this PR

This pull request fixes SQLite DateTime.Ticks translation by preserving sub-millisecond precision for compatible text-backed columns and avoiding floating-point precision loss.

Changes:

  • Uses integer arithmetic and direct fractional-text extraction for eligible DateTime columns.
  • Uses rounded millisecond Julian-day arithmetic for computed or unsupported operands.
  • Adds SQLite regression tests and updates the Northwind SQL baseline.
  • Requires restricting the text-based translation to non-REAL mappings, correcting the fractional tick test value, and covering both synchronous and asynchronous execution.
File Description
test/​EFCore.Sqlite.FunctionalTests/​Query/​Translations/​Temporal/​DateTimeTranslationsSqliteTest.cs Adds regression tests for column, projection, and computed-value DateTime.Ticks translations.
test/​EFCore.Sqlite.FunctionalTests/​Query/​NorthwindSelectQuerySqliteTest.cs Updates the expected SQLite SQL baseline for the Northwind DateTime.Ticks projection.
src/​EFCore.Sqlite.Core/​Query/​Internal/​Translators/​SqliteDateTimeMemberTranslator.cs Implements precise text-based tick translation for eligible columns and millisecond-rounded fallback translation for other date/time expressions.

Review effort: Lite


private SqlExpression TranslateTicks(SqlExpression instance)
{
if (instance is ColumnExpression { TypeMapping: SqliteDateTimeTypeMapping { Converter: null } })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SQLite: DateTime.Ticks translation loses sub-millisecond precision and is off by floating-point error

2 participants