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.

Sources
- HL7 canonical FHIRPath specification covering date/duration - HL7 canonical FHIRPath specification covering date/duration semantics
