Getting Started with Pragmatic.Temporal
This guide will get you up and running with Pragmatic.Temporal in minutes.
Installation
Section titled “Installation”# Core packagedotnet add package Pragmatic.Temporal
# Optional packagesdotnet add package Pragmatic.Temporal.Json # System.Text.Json convertersdotnet add package Pragmatic.Temporal.EntityFrameworkCore # EF Core integrationdotnet add package Pragmatic.Temporal.AspNetCore # ASP.NET Core middlewaredotnet add package Pragmatic.Temporal.Testing # TestClock and utilitiesThe DateTime Problem
Section titled “The DateTime Problem”.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 casesvar dto = new DateTimeOffset(2024, 6, 15, 14, 30, 0, TimeSpan.FromHours(2));// What happens when DST changes?The Solution: Type = Scope
Section titled “The Solution: Type = Scope”Pragmatic.Temporal provides types where the type itself defines the temporal scope:
using Pragmatic.Temporal.Types;
// Wall clock date - no timezone, no ambiguityvar orderDate = new LocalDate(2024, 6, 15);
// Wall clock time - no timezonevar deliveryTime = new LocalTime(14, 30);
// Combine themvar appointment = orderDate.At(deliveryTime);
// Full timezone awareness when neededvar romeTime = ZonedDateTime.FromUtc(DateTimeOffset.UtcNow, "Europe/Rome");Core Types
Section titled “Core Types”| Type | Use Case | Example |
|---|---|---|
LocalDate | Calendar date without time | Birth dates, holidays, deadlines |
LocalTime | Time of day without date | Store hours, meeting times |
LocalDateTime | Date + time without timezone | Wall clock appointments |
ZonedDateTime | Full timezone-aware datetime | Flight times, global events |
Duration | Time spans | Processing time, intervals |
Quick Examples
Section titled “Quick Examples”Date Arithmetic
Section titled “Date Arithmetic”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 31Business Days
Section titled “Business Days”var calculator = new TemporalCalculator();
// Add 5 business days (skips weekends)var delivery = calculator.AddBusinessDays(today, 5);
// With holidaysvar 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);Timezone Conversion
Section titled “Timezone Conversion”// Current time in a timezonevar now = ZonedDateTime.FromUtc(DateTimeOffset.UtcNow, "America/New_York");
// Convert to another timezonevar londonTime = now.InZone("Europe/London");
// Get UTC offsetvar offset = now.Offset; // TimeSpanCron Expressions
Section titled “Cron Expressions”// Every day at 9:00 AMvar daily = CronExpression.Daily(new TimeOnly(9, 0));
// Every Monday at 8:00 AMvar weekly = CronExpression.Weekly(DayOfWeek.Monday, new TimeOnly(8, 0));
// Custom: Every 15 minutes during business hoursvar custom = CronExpression.Parse("0/15 9-17 * * 1-5");
// Get next occurrencevar nextRun = daily.GetNextOccurrence(DateTimeOffset.UtcNow);Golden Rules
Section titled “Golden Rules”- Store UTC Only - Databases should only contain UTC timestamps
- Convert at Boundaries - Timezone conversion at API input/output only
- Never Query with Local Time - Pre-calculate UTC ranges before querying
- Use IClock, Never DateTime.Now - For testability
Testable Time
Section titled “Testable Time”// Production: use system clockservices.AddSingleton<IClock, SystemClock>();
// Tests: use controllable clockvar clock = new TestClock();clock.SetDateTime(2024, 6, 15, 10, 0, 0);
// Advance timeclock.Advance(TimeSpan.FromHours(2));Next Steps
Section titled “Next Steps”- Core Types - Detailed type documentation
- DST Handling - Daylight Saving Time edge cases
- Business Days - Business day calculations
- Testing - TestClock and test utilities