Skip to content

Getting Started with Pragmatic.Temporal

This guide will get you up and running with Pragmatic.Temporal in minutes.

Terminal window
# Core package
dotnet add package Pragmatic.Temporal
# Optional packages
dotnet add package Pragmatic.Temporal.Json # System.Text.Json converters
dotnet add package Pragmatic.Temporal.EntityFrameworkCore # EF Core integration
dotnet add package Pragmatic.Temporal.AspNetCore # ASP.NET Core middleware
dotnet add package Pragmatic.Temporal.Testing # TestClock and utilities

.NET’s DateTime and DateTimeOffset are ambiguous:

// What timezone is this? Local? UTC? Unknown?
var date = new DateTime(2024, 6, 15, 14, 30, 0);
// DateTimeOffset is better but still has edge cases
var dto = new DateTimeOffset(2024, 6, 15, 14, 30, 0, TimeSpan.FromHours(2));
// What happens when DST changes?

Pragmatic.Temporal provides types where the type itself defines the temporal scope:

using Pragmatic.Temporal.Types;
// Wall clock date - no timezone, no ambiguity
var orderDate = new LocalDate(2024, 6, 15);
// Wall clock time - no timezone
var deliveryTime = new LocalTime(14, 30);
// Combine them
var appointment = orderDate.At(deliveryTime);
// Full timezone awareness when needed
var romeTime = ZonedDateTime.FromUtc(DateTimeOffset.UtcNow, "Europe/Rome");
TypeUse CaseExample
LocalDateCalendar date without timeBirth dates, holidays, deadlines
LocalTimeTime of day without dateStore hours, meeting times
LocalDateTimeDate + time without timezoneWall clock appointments
ZonedDateTimeFull timezone-aware datetimeFlight times, global events
DurationTime spansProcessing time, intervals
var today = LocalDate.Today;
var nextWeek = today.AddDays(7);
var nextMonth = today.AddMonths(1);
var nextYear = today.AddYears(1);
var daysBetween = nextMonth.DaysBetween(today); // 30 or 31
var calculator = new TemporalCalculator();
// Add 5 business days (skips weekends)
var delivery = calculator.AddBusinessDays(today, 5);
// With holidays
var holidays = new StaticHolidayProvider(new[]
{
new Holiday(new LocalDate(2024, 12, 25), "Christmas"),
new Holiday(new LocalDate(2024, 12, 26), "Boxing Day")
});
var deliveryWithHolidays = calculator.AddBusinessDays(today, 5, holidays);
// Current time in a timezone
var now = ZonedDateTime.FromUtc(DateTimeOffset.UtcNow, "America/New_York");
// Convert to another timezone
var londonTime = now.InZone("Europe/London");
// Get UTC offset
var offset = now.Offset; // TimeSpan
// Every day at 9:00 AM
var daily = CronExpression.Daily(new TimeOnly(9, 0));
// Every Monday at 8:00 AM
var weekly = CronExpression.Weekly(DayOfWeek.Monday, new TimeOnly(8, 0));
// Custom: Every 15 minutes during business hours
var custom = CronExpression.Parse("0/15 9-17 * * 1-5");
// Get next occurrence
var nextRun = daily.GetNextOccurrence(DateTimeOffset.UtcNow);
  1. Store UTC Only - Databases should only contain UTC timestamps
  2. Convert at Boundaries - Timezone conversion at API input/output only
  3. Never Query with Local Time - Pre-calculate UTC ranges before querying
  4. Use IClock, Never DateTime.Now - For testability
// Production: use system clock
services.AddSingleton<IClock, SystemClock>();
// Tests: use controllable clock
var clock = new TestClock();
clock.SetDateTime(2024, 6, 15, 10, 0, 0);
// Advance time
clock.Advance(TimeSpan.FromHours(2));