namespace ScadaLink.CentralUI.Components; /// /// Converts <input type="datetime-local"> values — which are always /// expressed in the user's browser-local time zone — into UTC /// s for querying. /// /// CLAUDE.md mandates UTC throughout the system, but a datetime-local /// value carries no offset, so it must be converted to UTC, not relabelled /// as UTC. Relabelling (the CentralUI-008 bug) shifts every query window by the /// user's offset for any non-UTC browser. /// /// public static class BrowserTime { /// /// Converts a browser-local to UTC using the /// browser's Date.getTimezoneOffset() result. /// /// /// The wall-clock value from a datetime-local input, or null. /// /// /// The value of JavaScript new Date().getTimezoneOffset(): the number /// of minutes that, added to local time, yields UTC. It is positive /// for time zones behind UTC (e.g. +300 for UTC-5) and negative for zones /// ahead (e.g. -120 for UTC+2). /// /// The equivalent instant in UTC, or null when the input is null. public static DateTimeOffset? LocalInputToUtc(DateTime? localValue, int browserUtcOffsetMinutes) { if (localValue is not { } local) return null; // getTimezoneOffset() is defined as (UTC - local) in minutes, so // UTC = local + offset. var utc = DateTime.SpecifyKind(local, DateTimeKind.Unspecified) .AddMinutes(browserUtcOffsetMinutes); return new DateTimeOffset(utc, TimeSpan.Zero); } }