Skip to content

DateTime ordering can be incorrect for mixed precision operands with fractional time zone differences #397

Open

Description

@dehall

As noted in #392 (comment) , ordering DateTimes that have mixed precision, and whose time zone offsets differ by a fractional amount (eg +3:30) can result in asymmetric results, where one operation is correct but its inverse is incorrect

Consider examples
A = @2025-01-02T05-03:30 and
B = @2025-01-02T08:15:00.000Z:

A before B returns false (correct, every possible precise value for A is after B), but then
B after A returns null (incorrect, should be true)

The issue is that 2025-01-02T05-03:30 has a timezone offset with greater precision (minutes) than the DateTime itself (minutes unspecified). As of now when we convert this to a timezone with 0-minute offset like UTC, it becomes 2025-01-02T08+00:00 which is not equivalent, and I don't think it would be correct to change it to 2025-01-02T08:30+00:00 since that's a finer precision than the original DateTime.
The way to get around this would be to treat the not-fully-specified DateTime as an uncertainty instead, as the spec does suggest here https://cql.hl7.org/02-authorsguide.html#comparing-dates-and-times .

In general, CQL treats Date and Time values that are only known to some specific precision as an uncertainty over the range at the first unspecified precision.

The fix for this should involve using uncertainty to model an imprecise DateTime as a range of values. Because the issue only occurs for the combination of mixed precision and time zones differing by a non-full-hour value, we can decide at what level we want to implement the fix: either

  • anytime we compare DateTimes of mixed precision
  • anytime we compare DateTimes of mixed precision and different timezones, or
  • anytime we compare DateTimes of mixed precision and a fractional timezone difference

Codex also suggests the following:

  1. Reuse the existing range machinery, with comparison-specific adjustments. toLuxonUncertainty() (src/datatypes/datetime.ts:638) already constructs lower and upper bounds in the original offset. The existing Uncertainty (src/datatypes/uncertainty.ts:143) comparison methods already implement these boundary rules. Numeric endpoint values would allow their use without recursively invoking DateTime comparisons.
    However, comparison range construction must preserve CQL’s special treatment of seconds and milliseconds: for an implicit-precision comparison, a specified second with omitted milliseconds behaves as a decimal second, rather than an uncertainty spanning that second.

  2. Apply requested precision after normalizing the endpoints. For an explicit hour comparison, for example, convert both endpoints to the evaluation request’s offset and truncate them to hours. Your a then spans hour buckets [08, 09], while b occupies [08, 08], so ordering at hour precision is uncertain. With no explicit precision, preserve the endpoints through the finest relevant precision, producing the definite ordering above. For day or coarser comparisons, retain local calendar fields because those comparisons ignore offsets.

  3. Reconsider the single compareTo() result. Today all comparison operators derive their answer from -1, 0, 1, or null. General uncertainty needs operator-specific answers. For example:

A = [08:30, 09:30]
B = [08:30, 08:30]

A.before(B)       → false
A.after(B)        → null
A.sameOrAfter(B)  → true

One comparison code cannot express all three results. A shared helper returning prepared ranges, followed by separate relational operations, would fit better

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions