Date Arithmetic on FHIR Values Without Regretting It

Editorial illustration in ink-calligraphy style depicting date arithmetic categories including elapsed vs calendar days, DST, and partial-date compare

Age. Days since discharge. Time to next appointment. Every clinical workflow does date arithmetic on FHIR temporal values, and every one has a category of bugs specific to that arithmetic. Partial dates, timezones, DST edges, and leap years each break arithmetic in different ways. The site's FHIR timestamp inspector shows what the value actually carries. For the wider FHIR framing, additional integration patterns has more.

Age Calculation

age = today - birthDate looks trivial. Complications:

  • birthDate may be partial (year-only)
  • Timezone may shift the calendar day boundary
  • DST edges may add or subtract an hour on the fringe
  • Leap year birthdays (February 29) need special handling

For partial dates, partial dates: birthDate is not always a full date is the entry.

Rule: Same-Zone Arithmetic

Arithmetic on two timestamps in different zones is a source of subtle bugs. Convert both to the same zone (usually UTC) before subtracting.

  • Both UTC — direct subtraction works
  • Both local same zone — direct subtraction works
  • Mixed — convert first, then subtract

Elapsed Days vs Calendar Days

"How many days between two dates" has two answers:

  • Elapsed days — total seconds divided by 86400 (approximate for DST)
  • Calendar days — count of day boundaries crossed

For clinical decisions, calendar days is usually what you want. Two dates within a calendar day count as zero calendar days apart even if they are 23 hours apart.

Age In Years — The Year-Boundary Rule

Someone born July 11, 1980 turned 45 on July 11, 2025. On July 10, 2025 they were still 44.

Age in years = (today - birthDate) truncated at the year boundary. Do not just divide by 365 — leap years and calendar boundaries produce off-by-one errors near birthdays.

Libraries with a Period.years() method handle this. Naive subtraction does not.

DST And Arithmetic

Adding 24 hours to a local timestamp can produce a shift of one calendar day, or two, or zero depending on DST. Adding "1 calendar day" is not the same as adding 24 hours.

For arithmetic on local timestamps, use a library that respects zone rules. luxon, zoneinfo, java.time all do.

For the storage-format side, storing timestamps as UTC vs local: FHIR-flavored guidance is the entry.

Time Windows

  • Business hours (9 AM to 5 PM Monday-Friday)
  • Follow-up window (7-14 days after discharge)
  • Vaccination schedule (dose 2 at least 21 days after dose 1)

Each window is date arithmetic with edge cases. Business hours ignore DST unless configured otherwise. Follow-up windows depend on whether "days" means calendar or elapsed. Vaccination "at least N days" is a lower bound, not an equality.

Write each window as an explicit expression. Do not shortcut with naive arithmetic.

Comparison With Partial Dates

Comparing a partial date to a full timestamp requires precision-aware logic. "2026" > "2026-01-01T00:00:00Z" has no single right answer.

FHIRPath handles this with range semantics. External code should either normalize precision first or use a precision-aware comparison library.

Leap Seconds

FHIR does not model leap seconds. Values like 23:59:60 (leap second insertion) are not valid FHIR.

For clinical workflows, leap seconds are irrelevant. For anything involving astronomical or scientific precision, look elsewhere.

Testing Date Arithmetic

Boundary values that catch most bugs:

  • Birthdays (age calculation edge)
  • Year boundaries (December 31 / January 1)
  • DST spring-forward and fall-back days
  • February 29 in leap years
  • Month-end (January 31 + 1 month = February 28/29)

Test on these. Naive subtraction fails at least one.

The Short Version

Convert to same zone before arithmetic. Distinguish elapsed days from calendar days. Use year-boundary logic for age. Handle DST with zone-aware libraries. Write windows as explicit expressions. Test on the boundary dates.

Ink-calligraphy diagram of date arithmetic categories with elapsed vs calendar days, year boundaries, DST edges, and partial-date comparisons annotated, drawn as brush strokes with black ink accents on natural paper

Sources