A library that enforces UTC time handling in .NET applications. Includes UtcSchedule and UtcTimeWindow value types for common scheduling scenarios.
$ dotnet add package Knara.UtcStrictOpinionated library that enforces UTC time handling in .NET applications.
Mixed environments with legacy systems create DateTime chaos:
DateTime.ToUniversalTime() makes assumptions about timezone that are often wrongUtcStrict is an opinionated library that forces developers to work exclusively in UTC, eliminating bugs caused by mixing DateTime, DateTimeOffset, DateTime.Now, and DateTime.UtcNow throughout the codebase.
The library normalizes all datetime values to UTC and provides convenient business logic types (UtcSchedule and UtcTimeWindow) that handle common scheduling scenarios without boilerplate code or timezone-related bugs.
dotnet add package Knara.UtcStrictEnforces UTC timezone on all DateTime values:
// All these become UTC automatically
UtcDateTime utcFromLocal = DateTime.Now; // Converts local → UTC
UtcDateTime utcFromOffset = DateTimeOffset.Now; // Extracts UTC portion
UtcDateTime utcFromDb = dbDateTime; // Assumes server timezone
// Safe comparisons - everything is UTC
if (utcFromDb < utcFromLocal) { /* ... */ }
// Explicit timezone conversion when you know the source
var easternTime = new DateTime(2024, 6, 15, 14, 30, 0);
var utc = new UtcDateTime(easternTime, "Eastern Standard Time");Enable/disable features on a schedule:
// Create new schedule (must be future)
var schedule = UtcSchedule.CreateSchedule(
enableOn: new UtcDateTime(DateTime.UtcNow.AddHours(1)),
disableOn: new UtcDateTime(DateTime.UtcNow.AddDays(7))
);
// Load existing schedule from database (any dates allowed)
var existing = new UtcSchedule(enabledDate, disabledDate);
// Check if feature should be active
var (isActive, reason) = schedule.IsActiveAt(UtcDateTime.UtcNow);
if (isActive)
{
// Feature is enabled
}
// Unscheduled = always inactive
var unscheduled = UtcSchedule.Unscheduled;
Console.WriteLine(unscheduled.HasSchedule()); // FalseControl service availability by time of day and timezone:
// Business hours in Eastern timezone
var businessHours = new UtcTimeWindow(
startOn: new TimeSpan(9, 0, 0), // 9 AM
stopOn: new TimeSpan(17, 0, 0), // 5 PM
timeZone: "Eastern Standard Time",
daysActive: new[] { DayOfWeek.Monday, DayOfWeek.Tuesday,
DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }
);
// Check availability (UTC time gets converted to Eastern automatically)
var (isOpen, reason) = businessHours.IsActiveAt(UtcDateTime.UtcNow);
// Overnight window (10 PM to 6 AM)
var maintenance = new UtcTimeWindow(
startOn: new TimeSpan(22, 0, 0), // 10 PM
stopOn: new TimeSpan(6, 0, 0) // 6 AM next day
);
// Always available
var alwaysOpen = UtcTimeWindow.AlwaysOpen;
Console.WriteLine(alwaysOpen.HasWindow()); // False (no restrictions)API Controllers:
public class EventController : ControllerBase
{
public IActionResult CreateEvent(CreateEventRequest request)
{
// Frontend might send local time - normalize to UTC
var eventTime = new UtcDateTime(request.EventDate);
var evt = new Event
{
StartTime = eventTime.DateTime, // Always UTC in database
Schedule = request.EnableSchedule
? UtcSchedule.CreateSchedule(eventTime, eventTime.DateTime.AddHours(4))
: UtcSchedule.Unscheduled
};
return Ok(evt);
}
}Database Queries:
public class EventRepository
{
public List<Event> GetActiveEvents()
{
var now = UtcDateTime.UtcNow;
return context.Events
.Where(e => e.StartTime <= now.DateTime && e.EndTime >= now.DateTime)
.ToList();
}
public Event LoadEvent(int id)
{
var evt = context.Events.Find(id);
// Convert database DateTime to UtcDateTime for business logic
evt.NormalizedStartTime = new UtcDateTime(evt.StartTime);
return evt;
}
}Business Logic:
public class FeatureService
{
public bool IsFeatureEnabled(string featureId, UtcDateTime atTime)
{
var feature = GetFeature(featureId);
// Check schedule
var (scheduleActive, _) = feature.Schedule.IsActiveAt(atTime);
if (!scheduleActive) return false;
// Check time window
var (windowActive, _) = feature.TimeWindow.IsActiveAt(atTime);
return windowActive;
}
}HasSchedule() and HasWindow() methodsMIT License. Copyright 2025 Tatyana Asriyan