Converting Local Time to a FHIR Instant Without Dropping Precision

Editorial illustration in ink-calligraphy style depicting local time converted to a FHIR instant through timezone-aware steps

A clinical event happens at 2:30 PM in a hospital in Chicago. Storing that as a FHIR instant sounds trivial and is not. Between converting to UTC, deciding whether to preserve the offset, handling milliseconds, and coping with DST edges, there are three or four places to drop precision or introduce ambiguity. The site's FHIR timestamp inspector verifies the round-trip. For the wider FHIR framing, additional FHIR architecture notes have more.

The Source Cases

  • Local time with known timezone (2026-07-11 14:30 America/Chicago)
  • Local time with only an offset (2026-07-11 14:30 -05:00)
  • Local time with no zone information (bad input)
  • Naive datetime from a system that never recorded a zone

Each requires different handling. The last two need policy decisions before conversion.

For the timezone discussion, timezones in clinical timestamps and where they hide is the entry.

The Conversion Path

  1. Parse the source local time
  2. Apply the source timezone (from context if not on the string)
  3. Compute the UTC equivalent
  4. Emit as YYYY-MM-DDTHH:MM:SS.sssZ or YYYY-MM-DDTHH:MM:SS-05:00

Each step is a place a bug hides. The library you use should handle each explicitly.

The DST Edge

Chicago in March: 2:30 AM does not exist (spring forward). Chicago in November: 1:30 AM exists twice (fall back).

A conversion library that silently picks one interpretation produces silently wrong results. Well-designed libraries surface the ambiguity — they raise or return both interpretations. Handle it explicitly in the calling code.

Precision Preservation

  • Second precision: 2026-07-11T14:30:00Z
  • Millisecond precision: 2026-07-11T14:30:00.123Z
  • Sub-millisecond precision: not standard FHIR, do not use

If the source has milliseconds, preserve them. Dropping them is a small precision loss that shows up in event ordering.

The Offset vs UTC Choice

Both are legal FHIR:

  • 2026-07-11T19:30:00Z — UTC
  • 2026-07-11T14:30:00-05:00 — local time with offset

UTC is easier for arithmetic and sorting. Offset preserves the local clock context.

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

The instant Datatype Specifically

FHIR instant requires second precision or better and a timezone. That is stricter than dateTime.

Every emit into an instant field has to:

  • Include seconds (not just YYYY-MM-DDTHH:MM)
  • Include a timezone (Z or +HH:MM / -HH:MM)
  • Optionally include milliseconds

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

Round-Trip Verification

Convert local → FHIR instant → back to local. The result should equal the original.

Roundtrip failures indicate:

  • DST handling bug
  • Precision loss on milliseconds
  • Wrong timezone applied
  • Silent naive interpretation

Test the conversion library on your specific timezone before shipping.

Library Choices

  • Node — luxon handles DST and named zones well
  • Python — zoneinfo (Python 3.9+) or pytz
  • Java — java.time (ZonedDateTime + Instant)
  • Go — time.Time with time.LoadLocation

All handle FHIR instant emission if you use them correctly. Naive datetime libraries (Python's naive datetime, JavaScript's raw Date) produce bugs.

The Short Version

Parse with an explicit zone. Handle DST edges deliberately. Preserve millisecond precision. Choose UTC or offset per workload. Round-trip test the conversion. Use timezone-aware datetime libraries; naive ones ship bugs.

Ink-calligraphy diagram of a local time being converted to FHIR instant through timezone-aware steps with DST edge annotations, drawn as brush strokes with black ink accents on natural paper

Sources