# HowTo: Der FHIR `date`-Suchparameter – Präfixe, Intervalle und Kombinationen richtig verstehen 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. --- ## 1. 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](fhir-date-ohne-praefix.svg) `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. --- ## 2. 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](fhir-date-alle-praefixe.svg) | 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. --- ## 3. Sonderfall: 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. **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: ![Period/Range Matching](fhir-date-period-range-matching.svg) 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** — z. B. 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 → `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 → `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 oben, jetzt mit offenen statt geschlossenen Periods: ![Offene Periods Matching](fhir-date-offene-periods.svg) **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. 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. --- ## 4. 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: ![Kombinationen](fhir-date-kombinationen.svg) | 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. --- ## 5. 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](fhir-date-matrix-3x3.svg) 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. --- ## 6. 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](fhir-date-sonderfaelle.svg) ### a) 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. ### b) 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. ### c) 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. --- ## 7. 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) – z. B. „Encounter vollständig nach Entlassung X". Für einen simplen „ab Datum X"-Filter ist meist `ge`/`gt` gemeint, nicht `sa`. - 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 diesen fünf Grafiken im Hinterkopf – ohne Präfix, alle neun Präfixe einzeln, typische Kombinationen, die volle 3×3-Matrix und die Sonderfälle – und dem Wissen um den Unterschied zwischen Containment und Overlap bei `Period`/`Range`-Feldern sollte die nächste `date`-Query im FHIR-Suchparameter kein Rätsel mehr sein.