Cross‑platform DateTime Kind.Unspecified conversion with TimeZoneInfo
0 reputation · 18 Jan 2024, 14:27 UTC
0 reputation · 18 Jan 2024, 14:27 UTC
Goal: Obtain the same instant‑to‑zone conversion result when a DateTime value with Kind.Unspecified is passed to TimeZoneInfo.ConvertTime on both Windows and Linux.
Constraint: The current implementation treats Unspecified as the local time zone of the process, which can produce different offsets if the value actually represents UTC, especially in cloud‑native scenarios where the input originates from UTC‑based logs.
Uncertainty: Whether .NET Core should change the default assumption to UTC, provide an explicit Kind parameter, or retain the existing local‑time behavior for backward compatibility.
Questions: Should TimeZoneInfo.ConvertTime interpret Unspecified DateTime as UTC by default? Would adding an overload that accepts a DateTimeKind parameter resolve the ambiguity without breaking existing code? Should the documentation be updated to clarify the current assumption and recommend using DateTimeOffset or specifying Kind?
31275 reputation · 18 Jan 2024, 17:13 UTC
No — TimeZoneInfo.ConvertTime should not reinterpret DateTimeKind.Unspecified as UTC by default, and no DateTimeKind overload is needed. The reliable fix is on the caller side: give the value an explicit Kind with DateTime.SpecifyKind, or switch to DateTimeOffset. The documentation point is valid — the current assumption should be stated plainly so developers stop discovering it in production.
TimeZoneInfo.ConvertTime(DateTime, TimeZoneInfo) treats a DateTime whose Kind is Unspecified as if it were in TimeZoneInfo.Local — the process's local time zone.ConvertTime overload accepting a DateTimeKind. Interpretation is controlled entirely by the value's Kind at call time.Changing Unspecified to mean UTC would silently alter results for every existing application that passes local wall-clock values — a classic breaking change with no compile-time signal. An overload taking DateTimeKind would be redundant, because DateTime.SpecifyKind already expresses the same intent without new API surface. The reasonable ask is a documentation clarification recommending DateTimeOffset for instants.
If the value represents UTC, stamp it before converting:
DateTime utcValue = DateTime.SpecifyKind(rawValue, DateTimeKind.Utc);
DateTime converted = TimeZoneInfo.ConvertTime(utcValue, targetZone);Better, carry the instant explicitly so no assumption is ever made:
DateTimeOffset dto = new DateTimeOffset(rawValue, TimeSpan.Zero); // rawValue is UTC
DateTimeOffset convertedDto = TimeZoneInfo.ConvertTime(dto, targetZone);You can also use TimeZoneInfo.ConvertTimeFromUtc(utcValue, targetZone), which requires a UTC-kind input and throws otherwise — a useful guardrail.
Run the same snippet on both platforms and compare:
var v = new DateTime(2026, 10, 12, 3, 33, 0, DateTimeKind.Unspecified);
Console.WriteLine(TimeZoneInfo.Local.Id);
Console.WriteLine(TimeZoneInfo.ConvertTime(v, TimeZoneInfo.Utc));
Console.WriteLine(TimeZoneInfo.ConvertTime(DateTime.SpecifyKind(v, DateTimeKind.Utc), TimeZoneInfo.Utc));If TimeZoneInfo.Local.Id is UTC (common in containers), the first conversion already matches the second; if it is anything else, the first result is offset by the local zone and you must apply SpecifyKind or use DateTimeOffset.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.