Pragmatic.Temporal
Type-safe date/time for .NET 10 with DST-aware arithmetic and explicit timezone handling.
The Problem
Section titled “The Problem”DateTime and DateTimeOffset are ambiguous: DateTime conflates date, time, and zone behind an
easily-ignored Kind; DateTimeOffset keeps an offset but not a zone; calendar arithmetic surprises
(Jan 31 + 1 month); DST transitions cause silent bugs; and DateTime.Now makes tests flaky.
The Solution: Type = Scope
Section titled “The Solution: Type = Scope”The type itself defines the temporal scope, and the compiler enforces it. A LocalDate can’t carry
time; a ZonedDateTime can’t lose its zone.
using Pragmatic.Temporal.Types;
var birthday = new LocalDate(1990, 5, 15); // date onlyvar opening = new LocalTime(9, 0); // time onlyvar appointment = new LocalDateTime(2026, 3, 21, 14, 30); // wall clock, no zonevar flight = ZonedDateTime.FromUtc(DateTimeOffset.UtcNow, "Europe/Rome"); // zone-awareIClock makes “now” injectable and tests deterministic; calendar arithmetic, business days, and DST
transitions are handled correctly.
Packages
Section titled “Packages”| Package | Role |
|---|---|
Pragmatic.Temporal |
Core types, calendar arithmetic, business days, cron, IClock |
Pragmatic.Temporal.Json |
System.Text.Json converters for all temporal types |
Pragmatic.Temporal.EFCore |
EF Core value converters, type mapping, conventions |
Pragmatic.Temporal.AspNetCore |
Middleware, timezone detection, model binding |
Pragmatic.Temporal.Testing |
TestClock, TestTemporalContext, TestHolidayProvider |
Pragmatic.Temporal.Internationalization |
Culture-aware display formatting for LocalDateTime/ZonedDateTime via Pragmatic.Internationalization |
Pragmatic.Temporal.Analyzers / .CodeFixers |
Diagnostics PRAG0900-0904: DateTime.Now/Today instead of IClock, a DateTime without a Kind, a DateTimeOffset compared with relational operators, with fixes |
Installation
Section titled “Installation”dotnet add package Pragmatic.TemporalQuick Start
Section titled “Quick Start”using Pragmatic.Temporal.Types;
var checkIn = new LocalDate(2026, 6, 1);var checkOut = checkIn.AddDays(3); // calendar-correctint nights = checkOut.DaysBetween(checkIn); // 3
// Inject IClock instead of DateTime.Now → deterministic testspublic class BookingService(IClock clock){ // IClock.Today is a DateOnly; LocalDate converts both ways, so name the type you compare in public bool IsPast(LocalDate date) => date < (LocalDate)clock.Today;}Full walkthrough: Getting Started.
Status
Section titled “Status”Stable within 1.0.0-alpha: the type set, calendar/business-day arithmetic, DST handling, and the JSON/EF Core/ASP.NET integrations are settled. See the roadmap.
| Concepts | Type = scope, IClock, the mental model, NodaTime comparison |
| Getting Started | First temporal types, arithmetic, injecting IClock |
| Core Types | LocalDate/LocalTime/LocalDateTime/ZonedDateTime/Duration/…: full reference |
| Business Days | Holiday providers, business-day arithmetic, cron |
| DST Handling | Ambiguous/non-existent local times, safe conversions |
| Testing | TestClock and deterministic time |
| JSON Integration | System.Text.Json converters, wire formats, Minimal API setup |
| EF Core Integration | Value converters, column mappings, temporal query extensions |
| ASP.NET Core Integration | Middleware, timezone detection, model binding |
| Common Mistakes | The most frequent date/time pitfalls |
| Troubleshooting | Problem/solution guide |
Requirements
Section titled “Requirements”- .NET 10.0+
License
Section titled “License”Part of the Pragmatic.Design ecosystem. See Licensing. Pragmatic.Temporal is MIT-licensed.
