Files
haemka.de/content/en/howto-fhir-date-parameter.md
T
haemka cac298e745 New article "fhir date parameters", config changes for multilanguage
support, tarnslations for existing pages and articles
2026-08-13 12:44:58 +02:00

14 KiB
Raw Blame History

Title: HowTo: The FHIR date Search Parameter Understanding Prefixes, Intervals, and Combinations Slug: howto-fhir-date-parameter Category: work Tags: FHIR, howto Date: 2026-08-12 Lang: en Status: published

Anyone building or querying a FHIR server sooner or later runs into the date search parameter. At first glance it looks simple: ?date=2023-06-15. But as soon as prefixes like ge, sa, or ap come into play, and multiple date parameters get combined, things get confusing fast. This article brings order to the chaos: step by step, with timeline graphics for every case, and a quick-reference table for fast lookups.

Table of Contents

[TOC]


Quick Reference: All Prefixes at a Glance

For a fast glance in between. Details and graphics for every row follow in the sections below.

Prefix Name Short rule Example
(none) implicit eq Precision of the value determines the interval date=2023-06-15
eq equal to Resource interval fully within the search interval date=eq2023-06-15
ne not equal to Complement of eq date=ne2023-06-15
gt greater than Extends beyond the end (overlap is sufficient) date=gt2023-06-15
lt less than Begins before the start (overlap is sufficient) date=lt2023-06-15
ge greater or equal gt or fully contained (eq) date=ge2023-06-15
le less or equal lt or fully contained (eq) date=le2023-06-15
sa starts after Entirely after, no overlap date=sa2023-06-15
eb ends before Entirely before, no overlap date=eb2023-06-15
ap approximately Overlap + similar order of magnitude date=ap2023-06-15

Missing start/end on a Period: a missing start means -∞, a missing end means +∞. That's why sa can never match when start is missing, and eb can never match when end is missing (details in Section 4).


The Core Principle: FHIR Compares Intervals, Not Points in Time

The most important mental reset first: FHIR never treats date values as an exact point in time. It always treats them as an interval. How wide that interval is depends on the precision of the given value:

  • 2023 → the entire year 2023
  • 2023-06 → all of June 2023
  • 2023-06-15 → all of June 15, 2023 (00:0024:00)
  • 2023-06-15T10:00:00 → (almost) an exact point in time

Every comparison operator then compares two intervals with each other: the search interval from your query parameter, and the resource interval from the resource's field (e.g. Patient.birthDate or Encounter.period).

No prefix means eq, but precision still matters

If you don't specify a prefix, the server assumes eq. The resource date must then lie fully within your search interval. The coarser the precision, the wider the interval, and the more resource values fit inside it:

Date without a prefix FHIR date search parameter WITHOUT a prefix

date=2023-06-15 only matches this single day. date=2023-06, on the other hand, matches every day in June, including the 15th. That's the most common trap for beginners: a "less precise" search query isn't stricter, it's looser.


The Nine Prefixes at a Glance

FHIR defines nine prefixes for date in total. Each describes a different relationship between the search interval (red dashed in the graphic) and the matching range (colored/shaded):

All prefixes at a glance All prefixes at a glance

Prefix Name Meaning
eq equal to Resource interval lies fully within the search interval
ne not equal to Complement of eq
gt greater than Resource interval extends beyond the end of the search interval
lt less than Resource interval begins before the start of the search interval
ge greater or equal gt or fully contained within the search interval (eq)
le less or equal lt or fully contained within the search interval (eq)
sa starts after Begins entirely after the end of the search interval, no overlap
eb ends before Ends entirely before the start of the search interval, no overlap
ap approximately Overlap, plus a similar order of magnitude between the intervals

Important, containment vs. overlap: Only eq (and, implicitly, sa/eb) require the resource interval to lie fully within, or fully separated from, the search interval. For gt, lt, ge, and le, an overlap is enough: the resource interval doesn't have to lie completely outside the search range, it just needs to extend past the respective boundary. More on that in the next section.


Matching Against Period and Range

The graphics above show the resource date as a thin red marker. That works for simple date/dateTime fields like Patient.birthDate. As soon as the target field is itself an interval (a Period, e.g. Encounter.period, Condition.onsetPeriod, or a Range), the distinction between containment (fully contained) and overlap (mere overlap) becomes crucial:

  • eq requires containment: the entire resource interval must lie within the search interval.
  • gt / lt / ge / le only require overlap: it's enough for the resource interval to extend past the respective boundary, even if part of it lies on the "wrong" side.
  • sa / eb require the opposite of overlap: the resource interval must lie fully, and without any overlap, on the respective side.

For a simple point-in-time field (no Period), this distinction barely shows, since the resource's start and end are (almost) identical. There, gt and sa behave de facto the same. The difference only becomes relevant for actual time spans. That's also why sa/eb are, per the specification, primarily intended for Period/Range values.

Example: An encounter runs from June 10 to June 20. The search date=gt2023-06-15 should find it, even though the encounter started before the 15th. What matters is only that it extends past the 15th (overlap with the range "after the 15th"). With date=sa2023-06-15, the same encounter would not match, because it doesn't lie entirely after the 15th. That's exactly the practical difference between gt and sa.

The following graphic makes this visible for all four "one-sided" prefixes (gt, sa, lt, eb) using the same three example periods: one entirely before, one overlapping the boundary, and one entirely after:

Matching against Period/Range Matching against Period/Range (containment vs. overlap)

You can clearly see: gt and lt (blue shaded area, solid boundary line) let the overlapping Period B through as a match, while sa and eb (dashed boundary line) reject it. Only a period lying entirely on the correct side counts there.

Encounter timeframe date=gt2023-06-15 date=sa2023-06-15
June 1020 (overlaps the boundary) Match (overlap) No match (not entirely after)
June 1620 (entirely after) Match Match
June 110 (entirely before) No match No match

Open (Incomplete) Periods

In practice, periods are often not closed, for example an ongoing encounter without an end, or an imported record with an unknown start. The FHIR standard handles this clearly:

A missing lower bound (start) is implicitly treated as less than any real date (i.e. -∞). A missing upper bound (end) is implicitly treated as greater than any real date (i.e. +∞).

This has noticeable consequences, especially for sa and eb:

  • An ongoing encounter (start set, end missing, so end = +∞) can never match eb (ends before). Its end never lies before any finite date. It will, however, match gt, because its (infinite) end always extends beyond any search boundary.
  • An encounter with an unknown start (start missing, so start = -∞) can never match sa (starts after). Its beginning never lies after any finite date. It will, however, match lt, because its (infinite) beginning always reaches before any search boundary.
  • A period with neither start nor end overlaps practically every overlap-based filter (gt, lt, ge, le). By definition, it "extends" past every boundary.

The same four prefixes as in Section 3, now with open instead of closed periods:

Matching against open Period/Range Matching against open (incomplete) Period/Range

Practical consequence: If you want to specifically exclude "ongoing" resources (e.g. active encounters, open orders) from a date-range search, eb/sa alone won't do it. You'll additionally need to filter by status, or explicitly by end:missing=true/start:missing=true, depending on what your server supports.


Combining Multiple date Parameters

In practice, a single prefix is rarely enough. Usually you want to define a range. FHIR servers always combine multiple date parameters in the same query with AND. Four typical patterns:

Combining multiple parameters Combining multiple parameters (AND logic)

Combination Example Character
ge + le ge2023-01-01&le2023-12-31 Closed interval (both bounds inclusive)
gt + lt gt2023-01-01&lt2023-12-31 Open interval (both bounds exclusive)
sa + eb sa2023-01-01&eb2023-12-31 Strictly separated range, primarily for Period values
ge + lt ge2023-06-01&lt2023-07-01 Half-open interval [start, end), the standard pattern for "exactly one month"

If you work with [start, end) in timestamp-heavy ETL pipelines, ge + lt will look familiar: it's exactly the same pattern used for classic time-window queries in SQL.


The Full 3×3 Matrix of Range Combinations

The four patterns above are only the most common ones. In fact, any lower bound can be combined with any upper bound:

  • Lower bounds: ge, gt, sa
  • Upper bounds: le, lt, eb

That gives 3 × 3 = 9 possible combinations, all spec-compliant:

3x3 matrix of range combinations 3x3 matrix of range combinations (lower bound × upper bound)

They differ only in whether the respective boundary is inclusive (solid line) or exclusive (dashed line), and whether it's a simple value comparison (ge/gt/le/lt) or a strict Period comparison (sa/eb). There is no explicit exclusion rule in the FHIR standard. Every cell in this matrix is a valid, functioning query.


Edge Cases: When Combinations Become Redundant or Contradictory

Not every syntactically valid combination is practically meaningful. There are three categories you should know about before one of them ends up in a generated query by accident:

Edge cases in parameter combinations Edge cases in parameter combinations

Redundant combinations

Don't change the result, but aren't technically an error either:

  • eq plus a boundary that's already satisfied by the eq interval (eq2023-06-15&ge2020-01-01): the extra condition is superfluous.
  • Two conditions of the same directional type (ge2020-01-01&ge2023-01-01): only the stricter (later) lower bound actually takes effect.

Combinations with a guaranteed empty result

Structurally impossible to satisfy:

  • eq + ne on the same value (eq2023-06-15&ne2023-06-15): mutually exclusive conditions.
  • The lower bound lies after the upper bound in time (ge2023-06-01&le2023-01-01): no date can satisfy both at once.

The server will syntactically accept such queries and simply return an empty result set. Not an error, but not a helpful response either. It's worth validating this client-side before sending the request.

Unusual but valid edge cases

  • Three or more date parameters, e.g. a year range with a single day excluded: ge2023-01-01&le2023-12-31&ne2023-06-15.
  • ap combined with a boundary, e.g. ap2023-06-15&ge2023-01-01: rarely used, since ap is already fuzzy by nature, and the tolerance calculation remains server-dependent.

Practical Recommendations

  • For simple range filters, ge + lt (half-open interval) is usually the most robust pattern. It avoids rounding issues at precision boundaries (no off-by-one when you mean "through December 31st inclusive").
  • Use sa/eb deliberately when you need complete separation on Period/Range fields (see Section 3), e.g. "encounter entirely after discharge X". For a simple "from date X onward" filter, you usually mean ge/gt, not sa.
  • For Period fields with possible open ends (see Section 4), don't forget: sa/eb structurally never match there. Additionally filter by :missing or status if needed.
  • Before sending a multi-parameter query, it's worth a quick sanity check on the logical consistency between the lower and upper bound, to avoid empty results caused by configuration mistakes in your own pipeline.

With the quick reference above and the six graphics in mind, no prefix, all nine prefixes individually, containment vs. overlap for Period/Range (both open and closed), typical combinations, the full 3×3 matrix, and the edge cases, your next date query in FHIR should no longer be a mystery.