Stop Breaking Clocks in ASP.NET Core with UTC and TimeZoneInfo
Time bugs are sneaky and expensive, but they do not have to be. This post shows a practical UTC first approach for ASP.NET Core using DateTimeOffset and TimeZoneInfo. You will learn how to design your data model, return UTC from APIs, convert to a user’s local time, and avoid DST traps without hard coded offsets. We will also wrap conversions in a clean service and make time testable with TimeProvider.
If you have ever shipped a feature on a Friday and woken up Monday to a wall of screenshots that all say "Why is my order time wrong", welcome to the club. Time is the boss battle of backend code. Daylight saving time steals an hour, servers move to new regions, and somewhere a user in London insists they bought sneakers tomorrow. Let’s turn that chaos into boringly correct code.
The sneaky villain hiding in your code
It feels natural to stamp a record with DateTime.Now. It also quietly ties your data to whatever timezone your server is set to. Ship to a new region or container image and your audit trail suddenly speaks a different language.
var serverLocal = DateTime.Now; // Local to the machine
Console.WriteLine(serverLocal.Kind); // Local
var portable = DateTimeOffset.UtcNow; // Stable everywhere
Console.WriteLine(portable.ToString("O")); // ISO 8601 with Z
Use DateTimeOffset.UtcNow when you capture moments in time. It always carries the UTC context.