Table of Contents

Persistence and time conversions

This article describes how to store absolute and zoned time with PrimeTime, when to use library conversion helpers versus raw NodaTime or BCL APIs, and where those helpers live in each package.

Canonical persistence shapes

  • Absolute instant (UTC timeline): Prefer a single unambiguous point on the UTC line. In KZDev.PrimeTime, Instant is the natural type; serialize as a string or as ticks since the NodaTime epoch (or another stable numeric encoding your team agrees on). In KZDev.SystemClock.PrimeTime, the same idea is a DateTimeOffset with UTC offset zero (or equivalent contract in your schema).
  • Zoned wall clock: If you must recover local calendar and clock in a specific zone, store the instant plus a time zone id (for example IANA America/New_York), or store a ZonedDateTime in a format that preserves zone and offset rules. Do not persist only a local date/time without the zone id if the value is meant to be interpreted in that zone across DST changes.
  • Local time of day (no date): Use LocalTimeOfDay / UtcTimeOfDay (or BCL TimeOnly) when the domain meaning is “this clock time every day” and you combine it with a calendar date separately—typically for time-of-day timers (see Timers, daylight saving, and testing).

Schedule zone vs “UTC day”

For why public APIs use the word Schedule in names such as LocalScheduleTimeZone and ToScheduleLocalDate, see Schedule zone naming (why “Schedule” in API names?).

PrimeTime’s local schedule uses the same zone as IPrimeClock.LocalScheduleTimeZone (BCL) or IPrimeClock.LocalScheduleDateTimeZone (NodaTime, KZDev.PrimeTime only). Extensions on IPrimeTime such as ToScheduleLocalDate, ToScheduleDateTimeOffset, and related members project a stored absolute value into that zone—so “what day is it on the operations calendar?” and “what is the wall clock there?” align with how time-of-day timers interpret local time.

Those extensions are implemented in:

IPrimeTime must be an IPrimeClock

Schedule-zone conversions need LocalScheduleTimeZone / LocalScheduleDateTimeZone. The extensions therefore treat the IPrimeTime receiver as IPrimeClock. If the receiver is not a clock, they throw ArgumentException with a message that states schedule-zone projection requires IPrimeClock. In typical applications and tests you resolve IPrimeTime from the same DI registration as IPrimeClock (or use PrimeTestClock), so this rarely surfaces.

NodaTime-only helpers (KZDev.PrimeTime)

The superset package also exposes small static conversions that do not need schedule context:

  • PrimeTimeOfDayConversion — between LocalTime, LocalTimeOfDay / UtcTimeOfDay, and TimeOnly (modern targets), preserving tick resolution.
  • NodaDurationBclConversion — policy-preserving mapping of Duration to TimeSpan for delays (values beyond BCL range are clamped to TimeSpan.MaxValue so timer/delay paths stay safe).
  • NodaDateTimeZoneBclConversion — BCL TimeZoneInfo ↔ Noda DateTimeZone where the library already encodes interop rules.

For conversions that are straightforward one-liners in NodaTime or the BCL (for example Instant.ToDateTimeUtc(), LocalDate + LocalTimeLocalDateTime), prefer the official NodaTime/BCL APIs; PrimeTime does not attempt a full N×M type matrix.

Runnable examples

Production

Testing (virtual time)