Partial Dates: birthDate Is Not Always a Full Date

Editorial illustration in ink-calligraphy style depicting partial date precisions with year, year-month, and full-date shapes

Every developer who has stored Patient.birthDate as a full Date object at some point encounters a payload that carries "birthDate": "1957". FHIR allows partial dates — year alone, year-month, or year-month-day — for exactly the cases where the full date is unknown or protected. Handling partial dates well is a small discipline that keeps a lot of downstream code correct. The site's FHIR timestamp inspector surfaces the precision explicitly. For the wider FHIR framing, the FHIR comparison index has more.

Where Partial Dates Show Up

  • Historical patients whose exact birth date was never recorded
  • Privacy contexts where only year is retained
  • Deceased dates where the exact day is unknown
  • Encounter periods with partial precision
  • Observations where only the year is clinically meaningful

Every one produces a legitimately partial date value.

The Three Shapes

  • "2026" — year only
  • "2026-07" — year-month
  • "2026-07-11" — full year-month-day

Each is a valid FHIR date. Each carries different precision. Downstream code that assumes all three are full dates produces wrong results.

For the type framing, FHIR date, dateTime, instant: three types, one confusion is the entry.

The Age-Calculation Trap

age = today - birthDate fails on partial dates. today - "1957" is not a straightforward subtraction.

Options:

  • Reject partial dates for age calculation
  • Assume mid-year for year-only, mid-month for year-month
  • Return a range instead of a single age

Each has different semantics. Pick per the workload. Age reported for a clinical decision needs precision; age reported for a demographic summary can tolerate uncertainty.

For the arithmetic side, date arithmetic on FHIR values without regretting it is the entry.

The Sort-Order Trap

"2026" < "2026-01-01" lexically. Semantically, "2026" spans the whole year and could be before, during, or after January 1.

Sorting partial dates by string works but produces surprising results. Sort by precision-aware comparison instead.

The Display Trap

Rendering "1957" as "January 1, 1957" is a lie. The date is not January 1; the date is somewhere in 1957.

Render as "1957" when the value is year-only. Render as "July 1957" when year-month. Only render full dates when the precision is full.

For the UI-formatting mechanic, formatting a FHIR dateTime for a UI without lying about precision is the entry.

Storage Considerations

  • Store the original string — preserves precision explicitly
  • Store precision as a separate field — enables SQL queries
  • Store as a range — start of period, end of period

Each is a valid choice. The range representation is easiest for downstream arithmetic; the string representation is closest to the wire.

FHIRPath And Partial Dates

FHIRPath handles partial dates natively. %today > birthDate works whether birthDate is partial or full — the comparison uses precision-aware semantics.

FHIR invariants written in FHIRPath can rely on this. External code cannot assume the same semantics.

The Inspector's Report

The inspector shows:

  • The literal value
  • The detected precision (year / year-month / year-month-day)
  • The implied range

That readout is what tells you "the sender sent a year, not a full date."

The Short Version

Partial dates are legal FHIR. Handle year, year-month, and year-month-day distinctly. Render at the precision provided. Age calculation needs a precision-aware approach. Sorting by string is wrong. Store the original string plus a precision field for easy downstream reasoning.

Ink-calligraphy diagram of partial date precisions with year, year-month, and full-date shapes and their implied ranges annotated, drawn as brush strokes with black ink accents on natural paper

Sources