using MeterVault.Core.Normalization; namespace MeterVault.Infrastructure.Options; /// /// The instance timezone (, SDD §10) as every part of the /// application uses it: the normalizer divides months in it, readers bucket in it and turn requested /// dates into instants with it. One resolution, so none of them disagrees about where a month starts. /// public static class InstanceTimeZone { /// The configured zone, or UTC when is empty or not a known zone. public static TimeZoneInfo Resolve(string? id) { if (string.IsNullOrWhiteSpace(id)) { return TimeZoneInfo.Utc; } return TimeZoneInfo.TryFindSystemTimeZoneById(id, out var zone) ? zone : TimeZoneInfo.Utc; } /// /// The IANA id for a configured zone. .NET also accepts Windows ids ("W. Europe Standard Time"), but /// PostgreSQL buckets by IANA names only, so a Windows id is translated rather than passed to SQL. /// Anything unrecognised is returned unchanged, to be reported at startup. /// public static string Canonical(string id) { if (!TimeZoneInfo.TryFindSystemTimeZoneById(id, out var zone) || zone.HasIanaId) { return id; } return TimeZoneInfo.TryConvertWindowsIdToIanaId(zone.Id, out var iana) ? iana : id; } /// The instant a local date starts — the lower bound a range of that date begins at. public static DateTimeOffset StartOf(DateOnly date, TimeZoneInfo zone) => GapAttribution.LocalMidnight(date, zone); }