Title: HowTo: Der FHIR `date`-Suchparameter – Präfixe, Intervalle und Kombinationen richtig verstehen Slug: howto-fhir-date-parameter Category: work Tags: FHIR, howto Date: 2026-08-12 Lang: de Status: published Wer FHIR-Server baut oder abfragt, stolpert früher oder später über den `date`-Suchparameter. Auf den ersten Blick sieht er simpel aus: `?date=2023-06-15`. Aber sobald Präfixe wie `ge`, `sa` oder `ap` ins Spiel kommen und mehrere `date`-Parameter kombiniert werden, wird es schnell unübersichtlich. Dieser Artikel bringt Ordnung rein: Schritt für Schritt, mit Zeitleisten-Grafiken zu jedem Fall, und als Kurzreferenz zum schnellen Nachschlagen. ## Inhaltsverzeichnis [TOC] --- ## Kurzreferenz: Alle Präfixe auf einen Blick Für den schnellen Blick zwischendurch. Details und Grafiken zu jeder Zeile folgen in den Abschnitten unten. | Präfix | Name | Kurzregel | Beispiel | |---|---|---|---| | *(keins)* | implizit `eq` | Precision des Werts bestimmt das Intervall | `date=2023-06-15` | | `eq` | gleich | Resource-Intervall vollständig im Such-Intervall | `date=eq2023-06-15` | | `ne` | ungleich | Komplement von `eq` | `date=ne2023-06-15` | | `gt` | größer als | Ragt über das Ende hinaus (Overlap genügt) | `date=gt2023-06-15` | | `lt` | kleiner als | Beginnt vor dem Anfang (Overlap genügt) | `date=lt2023-06-15` | | `ge` | größer/gleich | `gt` oder vollständig enthalten (`eq`) | `date=ge2023-06-15` | | `le` | kleiner/gleich | `lt` oder vollständig enthalten (`eq`) | `date=le2023-06-15` | | `sa` | starts after | Vollständig danach, **keine** Überlappung | `date=sa2023-06-15` | | `eb` | ends before | Vollständig davor, **keine** Überlappung | `date=eb2023-06-15` | | `ap` | approximately | Überlappung + ähnliche Größenordnung | `date=ap2023-06-15` | **Fehlender `start`/`end` bei `Period`:** fehlender `start` bedeutet `-∞`, fehlendes `end` bedeutet `+∞`. Deshalb kann `sa` bei fehlendem `start` nie matchen, und `eb` kann bei fehlendem `end` nie matchen (Details in [Abschnitt 4](#4-offene-unvollständige-periods)). --- ## Das Grundprinzip: FHIR vergleicht Intervalle, keine Zeitpunkte Der wichtigste gedankliche Reset zuerst: FHIR behandelt Datumswerte nie als exakten Zeitpunkt, sondern immer als **Intervall**. Wie breit dieses Intervall ist, hängt von der Precision des angegebenen Werts ab: - `2023` → das ganze Jahr 2023 - `2023-06` → der ganze Juni 2023 - `2023-06-15` → der ganze 15. Juni 2023 (00:00–24:00 Uhr) - `2023-06-15T10:00:00` → (fast) ein exakter Zeitpunkt Jeder Vergleichsoperator vergleicht anschließend **zwei Intervalle** miteinander: das Such-Intervall aus deinem Query-Parameter und das Resource-Intervall aus dem Feld der Ressource (z. B. `Patient.birthDate` oder `Encounter.period`). ### Ohne Präfix = `eq`, aber die Precision entscheidet mit Gibst du kein Präfix an, nimmt der Server `eq` an. Das Resource-Datum muss dann **vollständig** innerhalb deines Such-Intervalls liegen. Je gröber die Precision, desto breiter das Intervall, und desto mehr Resource-Werte passen hinein: ![Datum ohne Präfix]({static}/images/fhir-date-no-prefix_de.svg) *FHIR date-Suchparameter OHNE Präfix* `date=2023-06-15` matched nur diesen einen Tag. `date=2023-06` matched dagegen jeden Tag im Juni, auch den 15. Das ist der häufigste Stolperstein für Einsteiger: eine „ungenaue" Suchanfrage ist nicht strenger, sondern lockerer. --- ## Die neun Präfixe im Überblick FHIR definiert insgesamt neun Präfixe für `date`. Jeder davon beschreibt eine andere Beziehung zwischen Such-Intervall (rot gestrichelt in der Grafik) und Treffer-Bereich (grün/farbig schattiert): ![Alle Präfixe im Überblick]({static}/images/fhir-date-all-prefixes_de.svg) *Alle Präfixe im Überblick* | Präfix | Name | Bedeutung | |---|---|---| | `eq` | gleich | Resource-Intervall liegt vollständig im Such-Intervall | | `ne` | ungleich | Komplement von `eq` | | `gt` | größer als | Resource-Intervall **ragt über** das Ende des Such-Intervalls hinaus | | `lt` | kleiner als | Resource-Intervall **beginnt vor** dem Anfang des Such-Intervalls | | `ge` | größer/gleich | `gt` **oder** vollständig im Such-Intervall enthalten (`eq`) | | `le` | kleiner/gleich | `lt` **oder** vollständig im Such-Intervall enthalten (`eq`) | | `sa` | starts after | Beginnt vollständig **nach** dem Ende des Such-Intervalls, keine Überlappung | | `eb` | ends before | Endet vollständig **vor** dem Beginn des Such-Intervalls, keine Überlappung | | `ap` | approximately | Überlappung, plus ähnliche Größenordnung der Intervalle | **Wichtig, Containment vs. Overlap:** Nur `eq` (und implizit `sa`/`eb`) verlangen, dass das Resource-Intervall **vollständig** innerhalb bzw. **vollständig getrennt** vom Such-Intervall liegt. Bei `gt`, `lt`, `ge` und `le` reicht dagegen eine **Überlappung**: das Resource-Intervall muss nicht komplett außerhalb des Suchbereichs liegen, es genügt, dass es über die jeweilige Grenze hinausragt. Details dazu im nächsten Abschnitt. --- ## Matching gegen `Period` und `Range` Die Grafiken oben zeigen das Resource-Datum als schmalen roten Marker. Das passt für einfache `date`/`dateTime`-Felder wie `Patient.birthDate`. Sobald das Zielfeld selbst ein **Intervall** ist (`Period`, z. B. `Encounter.period`, `Condition.onsetPeriod`, oder `Range`), wird die Unterscheidung zwischen **Containment** (vollständiges Enthaltensein) und **Overlap** (bloße Überlappung) entscheidend: - **`eq`** verlangt Containment: das gesamte Resource-Intervall muss im Such-Intervall liegen. - **`gt` / `lt` / `ge` / `le`** verlangen nur Overlap: es reicht, wenn das Resource-Intervall über die jeweilige Grenze hinausragt, auch wenn ein Teil davon auf der "falschen" Seite liegt. - **`sa` / `eb`** verlangen das Gegenteil von Overlap: das Resource-Intervall muss vollständig und ohne jede Überlappung auf der jeweiligen Seite liegen. Bei einem einfachen Zeitpunkt-Feld (kein `Period`) fällt dieser Unterschied praktisch nicht auf, da Resource-Start und -Ende (fast) identisch sind. Dort verhalten sich `gt` und `sa` de facto gleich. Der Unterschied wird erst bei echten Zeitspannen relevant. Das ist auch der Grund, warum `sa`/`eb` laut Spezifikation in erster Linie für `Period`/`Range`-Werte gedacht sind. **Beispiel:** Ein Encounter läuft vom 10. bis 20. Juni. Die Suche `date=gt2023-06-15` soll ihn finden, obwohl der Encounter *vor* dem 15. begonnen hat. Entscheidend ist nur, dass er über den 15. hinausreicht (Overlap mit dem Bereich "nach dem 15."). Bei `date=sa2023-06-15` würde derselbe Encounter dagegen **nicht** matchen, weil er nicht vollständig nach dem 15. liegt. Genau das ist der praktische Unterschied zwischen `gt` und `sa`. Die folgende Grafik macht das für alle vier "einseitigen" Präfixe (`gt`, `sa`, `lt`, `eb`) an denselben drei Beispiel-Periods sichtbar: einer Period komplett davor, einer, die die Suchgrenze überlappt, und einer komplett danach: ![Matching gegen Period/Range]({static}/images/fhir-date-period-range-matching_de.svg) *Matching gegen Period/Range (Containment vs. Overlap)* Man sieht deutlich: `gt` und `lt` (blau schattierter Bereich, durchgezogene Grenzlinie) lassen die überlappende Period B als Treffer durch, während `sa` und `eb` (gestrichelte Grenzlinie) sie ablehnen. Nur eine Period, die komplett auf der richtigen Seite liegt, zählt dort. | Encounter-Zeitraum | `date=gt2023-06-15` | `date=sa2023-06-15` | |---|---|---| | 10.–20. Juni (überlappt die Grenze) | ✅ Treffer (Overlap) | ❌ kein Treffer (nicht vollständig danach) | | 16.–20. Juni (komplett danach) | ✅ Treffer | ✅ Treffer | | 01.–10. Juni (komplett davor) | ❌ kein Treffer | ❌ kein Treffer | --- ## Offene (unvollständige) Periods In der Praxis sind Periods oft **nicht abgeschlossen**, zum Beispiel ein laufender Encounter ohne `end`, oder ein importierter Datensatz mit unbekanntem `start`. Der FHIR-Standard regelt das eindeutig: > Ein fehlender unterer Rand (`start`) gilt implizit als **kleiner als jedes > reale Datum** (also `-∞`). Ein fehlender oberer Rand (`end`) gilt implizit > als **größer als jedes reale Datum** (also `+∞`). Das hat spürbare Konsequenzen, gerade für `sa` und `eb`: - Ein laufender Encounter (`start` gesetzt, `end` fehlt, also `end = +∞`) kann **niemals** `eb` (ends before) matchen. Sein Ende liegt ja nie vor irgendeinem endlichen Datum. Er matcht aber sehr wohl `gt`, weil sein (unendliches) Ende immer über jede Suchgrenze hinausragt. - Ein Encounter mit unbekanntem Start (`start` fehlt, also `start = -∞`) kann **niemals** `sa` (starts after) matchen. Sein Beginn liegt ja nie nach irgendeinem endlichen Datum. Er matcht aber `lt`, weil sein (unendlicher) Anfang immer vor jede Suchgrenze hineinreicht. - Eine Period ganz ohne `start` und `end` überlappt praktisch **jeden** overlap-basierten Filter (`gt`, `lt`, `ge`, `le`). Sie „reicht" ja per Definition über jede Grenze hinaus. Dieselben vier Präfixe wie in [Abschnitt 3](#3-matching-gegen-period-und-range), jetzt mit offenen statt geschlossenen Periods: ![Matching gegen offene Period/Range]({static}/images/fhir-date-open-periods_de.svg) *Matching gegen offene (unvollständige) Period/Range* **Praktische Konsequenz:** Wenn du „laufende" Ressourcen (z. B. aktive Encounter, offene Verordnungen) gezielt aus einer Zeitraumsuche ausschließen willst, reicht `eb`/`sa` allein nicht. Du musst zusätzlich nach dem Status oder explizit nach `end:missing=true`/`start:missing=true` filtern, je nachdem, was dein Server unterstützt. --- ## Mehrere `date`-Parameter kombinieren In der Praxis reicht ein einzelner Präfix selten, meist willst du einen **Bereich** definieren. FHIR-Server verknüpfen mehrere `date`-Parameter in derselben Query immer per **UND**. Vier typische Muster: ![Kombination mehrerer Parameter]({static}/images/fhir-date-combinations_de.svg) *Kombination mehrerer Parameter (UND-Verknüpfung)* | Kombination | Beispiel | Charakter | |---|---|---| | `ge` + `le` | `ge2023-01-01&le2023-12-31` | Geschlossenes Intervall (beide Grenzen inklusive) | | `gt` + `lt` | `gt2023-01-01<2023-12-31` | Offenes Intervall (beide Grenzen exklusive) | | `sa` + `eb` | `sa2023-01-01&eb2023-12-31` | Strikt getrennter Bereich, primär für Period-Werte | | `ge` + `lt` | `ge2023-06-01<2023-07-01` | Halboffenes Intervall `[start, ende)`, Standardmuster für „genau ein Monat" | Wenn du in Timestamp-lastigen ETL-Pipelines mit `[start, ende)` arbeitest, kommt dir `ge` + `lt` bekannt vor: es ist exakt dasselbe Muster wie bei klassischen Zeitfenster-Queries in SQL. --- ## Die vollständige 3×3-Matrix aller Bereichs-Kombinationen Die vier Muster oben sind nur die gebräuchlichsten. Tatsächlich lässt sich **jede** Untergrenze mit **jeder** Obergrenze kombinieren: - Untergrenzen: `ge`, `gt`, `sa` - Obergrenzen: `le`, `lt`, `eb` Das ergibt 3 × 3 = **9 mögliche Kombinationen**, alle spec-konform: ![3x3-Matrix der Bereichs-Kombinationen]({static}/images/fhir-date-matrix-3x3_de.svg) *3x3-Matrix der Bereichs-Kombinationen (Untergrenze x Obergrenze)* Sie unterscheiden sich nur darin, ob die jeweilige Grenze inklusiv (durchgezogene Linie) oder exklusiv (gestrichelte Linie) ist, und ob es sich um einen einfachen Werte-Vergleich (`ge`/`gt`/`le`/`lt`) oder einen strengen Period-Vergleich (`sa`/`eb`) handelt. Es gibt **keine explizite Ausschluss-Regel** im FHIR-Standard. Jede Zelle dieser Matrix ist eine gültige, funktionierende Query. --- ## Sonderfälle: Wenn Kombinationen redundant oder widersprüchlich werden Nicht jede syntaktisch gültige Kombination ist auch praktisch sinnvoll. Drei Kategorien solltest du kennen, bevor du sie versehentlich in einer generierten Query landen lässt: ![Sonderfälle bei Parameter-Kombinationen]({static}/images/fhir-date-edge-cases_de.svg) *Sonderfälle bei Parameter-Kombinationen* ### Redundante Kombinationen Ändern nichts am Ergebnis, sind aber technisch kein Fehler: - `eq` + eine Grenze, die vom `eq`-Intervall ohnehin erfüllt wird (`eq2023-06-15&ge2020-01-01`): die Zusatzbedingung ist überflüssig. - Zwei Bedingungen desselben Richtungstyps (`ge2020-01-01&ge2023-01-01`): nur die strengere (spätere) Untergrenze wirkt effektiv. ### Kombinationen mit garantiert leerem Ergebnis Strukturell unmöglich zu erfüllen: - `eq` + `ne` auf denselben Wert (`eq2023-06-15&ne2023-06-15`): sich gegenseitig ausschließende Bedingungen. - Untergrenze liegt zeitlich nach der Obergrenze (`ge2023-06-01&le2023-01-01`): kein Datum erfüllt beides gleichzeitig. Der Server wird solche Queries **syntaktisch akzeptieren** und einfach eine leere Ergebnismenge zurückgeben. Kein Fehler, aber auch keine hilfreiche Rückmeldung. Es lohnt sich, so etwas clientseitig vorab zu validieren. ### Ungewöhnliche, aber gültige Sonderfälle - **Drei oder mehr `date`-Parameter**, z. B. ein Jahresbereich mit ausgeschlossenem Einzeltag: `ge2023-01-01&le2023-12-31&ne2023-06-15`. - **`ap` kombiniert mit einer Grenze**, z. B. `ap2023-06-15&ge2023-01-01`: selten genutzt, da `ap` selbst schon unscharf ist und die Toleranzberechnung serverabhängig bleibt. --- ## Praxis-Empfehlungen zum Schluss - Für einfache Bereichsfilter ist **`ge` + `lt`** (halboffenes Intervall) meist das robusteste Muster: es vermeidet Rundungsprobleme an der Precision-Grenze (kein Off-by-one bei „bis einschließlich 31.12."). - **`sa`/`eb`** bewusst einsetzen, wenn du bei `Period`/`Range`-Feldern eine *vollständige* Trennung brauchst (siehe [Abschnitt 3](#3-matching-gegen-period-und-range)), z. B. „Encounter vollständig nach Entlassung X". Für einen simplen „ab Datum X"-Filter ist meist `ge`/`gt` gemeint, nicht `sa`. - Bei `Period`-Feldern mit möglichen offenen Enden (siehe [Abschnitt 4](#4-offene-unvollständige-periods)) nicht vergessen: `sa`/`eb` greifen dort strukturell nie. Ggf. zusätzlich `:missing` oder Status filtern. - Vor dem Absetzen einer Multi-Parameter-Query lohnt sich ein kurzer Check auf logische Konsistenz zwischen Unter- und Obergrenze, um leere Ergebnisse durch Konfigurationsfehler in der eigenen Pipeline zu vermeiden. Mit der Kurzreferenz oben und den sechs Grafiken im Hinterkopf, ohne Präfix, alle neun Präfixe einzeln, Containment vs. Overlap bei Period/Range (offen wie geschlossen), typische Kombinationen, die volle 3×3-Matrix und die Sonderfälle, sollte die nächste `date`-Query im FHIR-Suchparameter kein Rätsel mehr sein.