Files
archived-discord-bot/CompatBot/Utils/TimeParser.cs
2025-03-08 07:01:22 +05:00

106 lines
4.2 KiB
C#

namespace CompatBot.Utils;
public static class TimeParser
{
public static readonly Dictionary<string, TimeZoneInfo> TimeZoneMap;
public static readonly Dictionary<string, string[]> TimeZoneAcronyms = new()
{
["PT"] = ["Pacific Standard Time", "America/Los_Angeles"],
["PST"] = ["Pacific Standard Time", "America/Los_Angeles"],
["PDT"] = ["Pacific Standard Time", "Pacific Daylight Time", "America/Los_Angeles"],
["EST"] = ["Eastern Standard Time", "America/New_York"],
["EDT"] = ["Eastern Standard Time", "Eastern Daylight Time", "America/New_York"],
["CEST"] = ["Central European Standard Time", "Europe/Berlin"],
["BST"] = ["British Summer Time", "GMT Standard Time", "Europe/London"],
["JST"] = ["Japan Standard Time", "Tokyo Standard Time", "Asia/Tokyo"],
};
static TimeParser()
{
var uniqueNames = new HashSet<string>(
from tznl in TimeZoneAcronyms.Values
from tzn in tznl
select tzn,
StringComparer.InvariantCultureIgnoreCase
);
Config.Log.Trace("[TZI] Unique TZA names: " + uniqueNames.Count);
var tzList = TimeZoneInfo.GetSystemTimeZones();
Config.Log.Trace("[TZI] System TZI count: " + tzList.Count);
var result = new Dictionary<string, TimeZoneInfo>();
var standardNames = new Dictionary<string, TimeZoneInfo>();
var daylightNames = new Dictionary<string, TimeZoneInfo>();
foreach (var tzi in tzList)
{
Config.Log.Trace($"[TZI] Checking {tzi.Id} ({tzi.DisplayName} / {tzi.StandardName} / {tzi.DaylightName})");
if (uniqueNames.Contains(tzi.StandardName) || uniqueNames.Contains(tzi.DaylightName) || uniqueNames.Contains(tzi.Id))
{
Config.Log.Trace("[TZI] Looks like it's a match!");
var acronyms = (
from tza in TimeZoneAcronyms
where tza.Value.Contains(tzi.StandardName) || tza.Value.Contains(tzi.DaylightName) || tza.Value.Contains(tzi.Id)
select tza.Key
).ToList();
Config.Log.Trace("[TZI] Matched acronyms: " + string.Join(", ", acronyms));
foreach (var tza in acronyms)
result[tza] = tzi;
}
else
{
var a = tzi.StandardName.GetAcronym(includeAllCaps: true, includeAllDigits: true);
if (TimeZoneAcronyms.ContainsKey(a))
continue;
standardNames.TryAdd(a, tzi);
a = tzi.DaylightName.GetAcronym(includeAllCaps: true, includeAllDigits: true);
if (TimeZoneAcronyms.ContainsKey(a) || standardNames.ContainsKey(a))
continue;
daylightNames.TryAdd(a, tzi);
}
}
Config.Log.Trace("[TZI] Total matched acronyms: " + result.Count);
TimeZoneMap = result.Concat(standardNames).Concat(daylightNames)
.DistinctBy(kvp => kvp.Key)
.ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
}
public static bool TryParse(string dateTime, out DateTime result)
{
result = default;
if (string.IsNullOrEmpty(dateTime))
return false;
dateTime = dateTime.ToUpperInvariant();
if (char.IsDigit(dateTime[^1]))
return DateTime.TryParse(dateTime, out result);
var cutIdx = dateTime.LastIndexOf(' ');
if (cutIdx < 0)
return false;
var tza = dateTime[(cutIdx+1)..];
dateTime = dateTime[..cutIdx];
if (TimeZoneMap.TryGetValue(tza, out var tzi))
{
if (!DateTime.TryParse(dateTime, out result))
return false;
result = TimeZoneInfo.ConvertTimeToUtc(result, tzi);
return true;
}
return false;
}
public static DateTime Normalize(this DateTime date) =>
date.Kind switch
{
DateTimeKind.Utc => date,
DateTimeKind.Local => date.ToUniversalTime(),
_ => date.AsUtc(),
};
public static List<string> GetSupportedTimeZoneAbbreviations() => TimeZoneMap.Keys.ToList();
}