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,
Instantis 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 aDateTimeOffsetwith 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 aZonedDateTimein 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 BCLTimeOnly) 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:
KZDev.PrimeTime:PrimeTimeScheduleZoneExtensions(Instant/ZonedDateTime→ Noda and BCL schedule-local shapes).KZDev.SystemClock.PrimeTime:PrimeTimeScheduleZoneExtensions(DateTimeOffset/ UTCDateTime→ BCL schedule-localDateTimeOffset,DateOnly,TimeOnly, and wallDateTimehelpers).
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— betweenLocalTime,LocalTimeOfDay/UtcTimeOfDay, andTimeOnly(modern targets), preserving tick resolution.NodaDurationBclConversion— policy-preserving mapping ofDurationtoTimeSpanfor delays (values beyond BCL range are clamped toTimeSpan.MaxValueso timer/delay paths stay safe).NodaDateTimeZoneBclConversion— BCLTimeZoneInfo↔ NodaDateTimeZonewhere the library already encodes interop rules.
For conversions that are straightforward one-liners in NodaTime or the BCL (for example Instant.ToDateTimeUtc(), LocalDate + LocalTime → LocalDateTime), prefer the official NodaTime/BCL APIs; PrimeTime does not attempt a full N×M type matrix.
Runnable examples
Production
Testing (virtual time)
- Persistence and conversions — PrimeTime (NodaTime) testing
- Persistence and conversions — System Clock testing
Related
- Choosing a package
- Schedule zone naming (why “Schedule” in API names?)
- Timers, daylight saving, and testing
- Event monitoring (ETW) — persistence shapes for monitored events, if applicable
- API: System Clock stack · PrimeTime / NodaTime stack