From e5fc154858e71eeb3628a686fe5e74555aedcb76 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Fri, 21 Aug 2020 12:10:33 -0400 Subject: [PATCH] feat: Add IcdTimeZoneInfo --- CHANGELOG.md | 1 + .../ICD.Common.Utils_NetStandard.csproj | 3 + .../ICD.Common.Utils_SimplSharp.csproj | 6 + .../Sqlite/IcdSqliteDataReader.cs | 5 + .../TimeZoneInfo/IcdTimeZoneInfo.cs | 455 ++++++++++++++++++ .../IcdTimeZoneInfoAdjustmentRule.cs | 41 ++ .../IcdTimeZoneInfoTransitionTime.cs | 19 + ICD.Common.Utils/TimeZones.sqlite | Bin 0 -> 90112 bytes 8 files changed, 530 insertions(+) create mode 100644 ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfo.cs create mode 100644 ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoAdjustmentRule.cs create mode 100644 ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoTransitionTime.cs create mode 100644 ICD.Common.Utils/TimeZones.sqlite diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1baf7..2c6c3cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added a method for getting the total number of seconds in a date - Added extensions to raise events with common event args using the data directly - Added property to IcdEnvironment to determine whether SSL communication is enabled + - Added IcdTimeZoneInfo, a very light implementation of System.TimeZoneInfo for the .NET Compact Framework ### Changed - Repeater changed to use configured callbacks instead of a dumb event diff --git a/ICD.Common.Utils/ICD.Common.Utils_NetStandard.csproj b/ICD.Common.Utils/ICD.Common.Utils_NetStandard.csproj index 4fda672..80fd826 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_NetStandard.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_NetStandard.csproj @@ -49,6 +49,9 @@ PreserveNewest + + PreserveNewest + \ No newline at end of file diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index 9951fb1..ad6616e 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -110,6 +110,7 @@ PreserveNewest + @@ -222,6 +223,8 @@ + + @@ -238,6 +241,9 @@ + + PreserveNewest + diff --git a/ICD.Common.Utils/Sqlite/IcdSqliteDataReader.cs b/ICD.Common.Utils/Sqlite/IcdSqliteDataReader.cs index 006d112..a271e82 100644 --- a/ICD.Common.Utils/Sqlite/IcdSqliteDataReader.cs +++ b/ICD.Common.Utils/Sqlite/IcdSqliteDataReader.cs @@ -40,6 +40,11 @@ namespace ICD.Common.Utils.Sqlite return m_Reader.GetInt32(ordinal); } + public long GetInt64(int ordinal) + { + return m_Reader.GetInt64(ordinal); + } + public bool GetBoolean(int ordinal) { return m_Reader.GetBoolean(ordinal); diff --git a/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfo.cs b/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfo.cs new file mode 100644 index 0000000..ae54d5f --- /dev/null +++ b/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfo.cs @@ -0,0 +1,455 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +#if SIMPLSHARP +using Crestron.SimplSharp.CrestronIO; +#else +using System.IO; +#endif +using ICD.Common.Utils.IO; +using ICD.Common.Utils.Sqlite; + +namespace ICD.Common.Utils.TimeZoneInfo +{ + public sealed class IcdTimeZoneInfo + { + #region Private Members + + private const string SQL_LOCAL_DATABASE_FILE = "TimeZones.sqlite"; + private const string SQL_CONNECTION_STRING_FORMAT = +#if SIMPLSHARP + "Data Source={0};Version=3;ReadOnly=True"; +#else + "Data Source={0}"; +#endif + + private static readonly Dictionary s_Cache; + + private IcdTimeZoneInfoAdjustmentRule[] m_AdjustmentRules; + + #endregion + + #region Properties + + public string StandardName { get; private set; } + + public TimeSpan BaseUtcOffset { get; private set; } + + public bool SupportsDaylightSavingTime { get; private set; } + + public IcdTimeZoneInfoAdjustmentRule[] GetAdjustmentRules() + { + return m_AdjustmentRules.ToArray(); + } + + #endregion + + #region Constructors + + static IcdTimeZoneInfo() + { + s_Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); + + try + { + PopulateCache(); + } + catch (Exception e) + { + IcdErrorLog.Exception(e, "Error populating IcdTimeZoneInfo cache - {0}", e.Message); + } + } + + public static IcdTimeZoneInfo FindSystemTimeZoneById(string timeZoneId) + { + return s_Cache[timeZoneId]; + } + + #endregion + + #region Cache Building + + private static void PopulateCache() + { + string databasePath = IcdPath.Combine(PathUtils.ProgramPath, SQL_LOCAL_DATABASE_FILE); + if (!IcdFile.Exists(databasePath)) + throw new FileNotFoundException("Failed to find database at path " + databasePath); + + string sqlConnectionString = string.Format(SQL_CONNECTION_STRING_FORMAT, databasePath); + + using (IcdSqliteConnection sQLiteConnection = new IcdSqliteConnection(sqlConnectionString)) + { + sQLiteConnection.Open(); + + foreach (IcdTimeZoneInfo info in ReadTimeZoneInfos(sQLiteConnection)) + s_Cache.Add(info.StandardName, info); + } + } + + private static IEnumerable ReadTimeZoneInfos(IcdSqliteConnection sQLiteConnection) + { + const string command = "select id, name, baseOffsetTicks, hasDstRule from timeZones"; + + using (IcdSqliteCommand cmd = new IcdSqliteCommand(command, sQLiteConnection)) + { + using (IcdSqliteDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int id = reader.GetInt32(0); + string name = reader.GetString(1); + long offset = reader.GetInt64(2); + bool hasRule = reader.GetBoolean(3); + + IcdTimeZoneInfoAdjustmentRule[] adjustmentRules = LoadAdjustmentRules(id, sQLiteConnection).ToArray(); + + IcdTimeZoneInfo info = new IcdTimeZoneInfo + { + StandardName = name, + BaseUtcOffset = TimeSpan.FromTicks(offset), + SupportsDaylightSavingTime = hasRule, + m_AdjustmentRules = adjustmentRules + }; + + yield return info; + } + } + } + } + + private static IEnumerable LoadAdjustmentRules(int timeZoneId, IcdSqliteConnection sQLiteConnection) + { + string command = string.Format("select id, timeZone, deltaTicks, ruleStart, ruleEnd from rules where timeZone = {0}", timeZoneId); + + using (IcdSqliteCommand cmd = new IcdSqliteCommand(command, sQLiteConnection)) + { + using (IcdSqliteDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + int id = reader.GetInt32(0); + long delta = reader.GetInt64(2); + string ruleStart = reader.GetString(3); + string ruleEnd = reader.GetString(4); + IcdTimeZoneInfoTransitionTime transitionTimeStart = LoadTransitionTimeStart(id, sQLiteConnection); + IcdTimeZoneInfoTransitionTime transitionTimeEnd = LoadTransitionTimeEnd(id, sQLiteConnection); + + IcdTimeZoneInfoAdjustmentRule rule = new IcdTimeZoneInfoAdjustmentRule + { + DaylightDelta = TimeSpan.FromTicks(delta), + DateStart = DateTime.Parse(ruleStart), + DateEnd = DateTime.Parse(ruleEnd), + DaylightTransitionStart = transitionTimeStart, + DaylightTransitionEnd = transitionTimeEnd + }; + + yield return rule; + } + } + } + } + + private static IcdTimeZoneInfoTransitionTime LoadTransitionTimeStart(int ruleId, IcdSqliteConnection sQLiteConnection) + { + string command = + string.Format("select rule, isTransitionStart, isFixedRule, timeOfDay, dayOfWeek, day, week, month " + + "from ruleTransitions " + + "where rule = {0} and isTransitionStart = true", + ruleId); + + using (IcdSqliteCommand cmd = new IcdSqliteCommand(command, sQLiteConnection)) + { + using (IcdSqliteDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + bool isFixed = reader.GetBoolean(2); + string timeOfDay = reader.GetString(3); + int dayOfWeek = reader.GetInt32(4); + int day = reader.GetInt32(5); + int week = reader.GetInt32(6); + int month = reader.GetInt32(7); + + return new IcdTimeZoneInfoTransitionTime + { + IsFixedDateRule = isFixed, + TimeOfDay = DateTime.Parse(timeOfDay), + DayOfWeek = (DayOfWeek)dayOfWeek, + Day = day, + Week = week, + Month = month + }; + } + } + } + + throw new ArgumentOutOfRangeException("ruleId", "There was no TransitionTime for the specified rule"); + } + + private static IcdTimeZoneInfoTransitionTime LoadTransitionTimeEnd(int ruleId, IcdSqliteConnection sQLiteConnection) + { + string command = + string.Format("select rule, isTransitionStart, isFixedRule, timeOfDay, dayOfWeek, day, week, month " + + "from ruleTransitions " + + "where rule = {0} and isTransitionStart = false", + ruleId); + + using (IcdSqliteCommand cmd = new IcdSqliteCommand(command, sQLiteConnection)) + { + using (IcdSqliteDataReader reader = cmd.ExecuteReader()) + { + while (reader.Read()) + { + bool isFixed = reader.GetBoolean(2); + string timeOfDay = reader.GetString(3); + int dayOfWeek = reader.GetInt32(4); + int day = reader.GetInt32(5); + int week = reader.GetInt32(6); + int month = reader.GetInt32(7); + + return new IcdTimeZoneInfoTransitionTime + { + IsFixedDateRule = isFixed, + TimeOfDay = DateTime.Parse(timeOfDay), + DayOfWeek = (DayOfWeek)dayOfWeek, + Day = day, + Week = week, + Month = month + }; + } + } + } + + throw new ArgumentOutOfRangeException("ruleId", "There was no TransitionTime for the specified rule"); + } + + #endregion + + #region Methods + + public DateTime ConvertToUtc(DateTime time) + { + return time.Kind == DateTimeKind.Utc ? time : ConvertToUtc(time, this); + } + + #endregion + + #region Private Methods + + private static DateTime ConvertToUtc(DateTime dateTime, IcdTimeZoneInfo sourceTimeZone) + { + if (sourceTimeZone == null) + throw new ArgumentNullException("sourceTimeZone"); + + IcdTimeZoneInfoAdjustmentRule sourceRule = sourceTimeZone.GetAdjustmentRuleForTime(dateTime); + TimeSpan sourceOffset = sourceTimeZone.BaseUtcOffset; + + if (sourceRule != null) + { + if (sourceTimeZone.SupportsDaylightSavingTime) + { + DaylightTime sourceDaylightTime = GetDaylightTime(dateTime.Year, sourceRule); + + bool sourceIsDaylightSavings = GetIsDaylightSavings(dateTime, sourceRule, sourceDaylightTime); + + // adjust the sourceOffset according to the Adjustment Rule / Daylight Saving Rule + sourceOffset += (sourceIsDaylightSavings ? sourceRule.DaylightDelta : TimeSpan.Zero + /*FUTURE: sourceRule.StandardDelta*/); + } + } + + const DateTimeKind targetKind = DateTimeKind.Utc; + + long utcTicks = dateTime.Ticks - sourceOffset.Ticks; + return new DateTime(utcTicks, targetKind); + } + + // assumes dateTime is in the current time zone's time + private IcdTimeZoneInfoAdjustmentRule GetAdjustmentRuleForTime(DateTime dateTime) + { + if (m_AdjustmentRules == null || m_AdjustmentRules.Length == 0) + { + return null; + } + + // Only check the whole-date portion of the dateTime - + // This is because the AdjustmentRule DateStart & DateEnd are stored as + // Date-only values {4/2/2006 - 10/28/2006} but actually represent the + // time span {4/2/2006@00:00:00.00000 - 10/28/2006@23:59:59.99999} + DateTime date = dateTime.Date; + + return m_AdjustmentRules.FirstOrDefault(t => t.DateStart <= date && t.DateEnd >= date); + } + + // + // GetDaylightTime - + // + // Helper function that returns a DaylightTime from a year and AdjustmentRule + // + private static DaylightTime GetDaylightTime(int year, IcdTimeZoneInfoAdjustmentRule rule) + { + TimeSpan delta = rule.DaylightDelta; + DateTime startTime = TransitionTimeToDateTime(year, rule.DaylightTransitionStart); + DateTime endTime = TransitionTimeToDateTime(year, rule.DaylightTransitionEnd); + return new DaylightTime(startTime, endTime, delta); + } + + // + // TransitionTimeToDateTime - + // + // Helper function that converts a year and TransitionTime into a DateTime + // + private static DateTime TransitionTimeToDateTime(int year, IcdTimeZoneInfoTransitionTime transitionTime) + { + DateTime value; + DateTime timeOfDay = transitionTime.TimeOfDay; + + if (transitionTime.IsFixedDateRule) + { + // create a DateTime from the passed in year and the properties on the transitionTime + + // if the day is out of range for the month then use the last day of the month + int day = DateTime.DaysInMonth(year, transitionTime.Month); + + value = new DateTime(year, transitionTime.Month, (day < transitionTime.Day) ? day : transitionTime.Day, + timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + } + else + { + if (transitionTime.Week <= 4) + { + // + // Get the (transitionTime.Week)th Sunday. + // + value = new DateTime(year, transitionTime.Month, 1, + timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + + int dayOfWeek = (int)value.DayOfWeek; + int delta = (int)transitionTime.DayOfWeek - dayOfWeek; + if (delta < 0) + { + delta += 7; + } + delta += 7 * (transitionTime.Week - 1); + + if (delta > 0) + { + value = value.AddDays(delta); + } + } + else + { + // + // If TransitionWeek is greater than 4, we will get the last week. + // + Int32 daysInMonth = DateTime.DaysInMonth(year, transitionTime.Month); + value = new DateTime(year, transitionTime.Month, daysInMonth, + timeOfDay.Hour, timeOfDay.Minute, timeOfDay.Second, timeOfDay.Millisecond); + + // This is the day of week for the last day of the month. + int dayOfWeek = (int)value.DayOfWeek; + int delta = dayOfWeek - (int)transitionTime.DayOfWeek; + if (delta < 0) + { + delta += 7; + } + + if (delta > 0) + { + value = value.AddDays(-delta); + } + } + } + return value; + } + + // + // GetIsDaylightSavings - + // + // Helper function that checks if a given dateTime is in Daylight Saving Time (DST) + // This function assumes the dateTime and AdjustmentRule are both in the same time zone + // + static private bool GetIsDaylightSavings(DateTime time, IcdTimeZoneInfoAdjustmentRule rule, DaylightTime daylightTime) + { + if (rule == null) + { + return false; + } + + DateTime startTime; + DateTime endTime; + + if (time.Kind == DateTimeKind.Local) + { + // startTime and endTime represent the period from either the start of DST to the end and ***includes*** the + // potentially overlapped times + startTime = rule.IsStartDateMarkerForBeginningOfYear() ? new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : daylightTime.Start + daylightTime.Delta; + endTime = rule.IsEndDateMarkerForEndOfYear() ? new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : daylightTime.End; + } + else + { + // startTime and endTime represent the period from either the start of DST to the end and + // ***does not include*** the potentially overlapped times + // + // -=-=-=-=-=- Pacific Standard Time -=-=-=-=-=-=- + // April 2, 2006 October 29, 2006 + // 2AM 3AM 1AM 2AM + // | +1 hr | | -1 hr | + // | | | | + // [========== DST ========>) + // + // -=-=-=-=-=- Some Weird Time Zone -=-=-=-=-=-=- + // April 2, 2006 October 29, 2006 + // 1AM 2AM 2AM 3AM + // | -1 hr | | +1 hr | + // | | | | + // [======== DST ========>) + // + bool invalidAtStart = rule.DaylightDelta > TimeSpan.Zero; + startTime = rule.IsStartDateMarkerForBeginningOfYear() ? new DateTime(daylightTime.Start.Year, 1, 1, 0, 0, 0) : daylightTime.Start + (invalidAtStart ? rule.DaylightDelta : TimeSpan.Zero); /* FUTURE: - rule.StandardDelta; */ + endTime = rule.IsEndDateMarkerForEndOfYear() ? new DateTime(daylightTime.End.Year + 1, 1, 1, 0, 0, 0).AddTicks(-1) : daylightTime.End + (invalidAtStart ? -rule.DaylightDelta : TimeSpan.Zero); + } + + return CheckIsDst(startTime, time, endTime, false); + } + + private static bool CheckIsDst(DateTime startTime, DateTime time, DateTime endTime, bool ignoreYearAdjustment) + { + bool isDst; + + if (!ignoreYearAdjustment) + { + int startTimeYear = startTime.Year; + int endTimeYear = endTime.Year; + + if (startTimeYear != endTimeYear) + { + endTime = endTime.AddYears(startTimeYear - endTimeYear); + } + + int timeYear = time.Year; + + if (startTimeYear != timeYear) + { + time = time.AddYears(startTimeYear - timeYear); + } + } + + if (startTime > endTime) + { + // In southern hemisphere, the daylight saving time starts later in the year, and ends in the beginning of next year. + // Note, the summer in the southern hemisphere begins late in the year. + isDst = (time < endTime || time >= startTime); + } + else + { + // In northern hemisphere, the daylight saving time starts in the middle of the year. + isDst = (time >= startTime && time < endTime); + } + return isDst; + } + + #endregion + } +} diff --git a/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoAdjustmentRule.cs b/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoAdjustmentRule.cs new file mode 100644 index 0000000..53b3901 --- /dev/null +++ b/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoAdjustmentRule.cs @@ -0,0 +1,41 @@ +using System; + +namespace ICD.Common.Utils.TimeZoneInfo +{ + public sealed class IcdTimeZoneInfoAdjustmentRule + { + public DateTime DateStart { get; set; } + + public DateTime DateEnd { get; set; } + + public TimeSpan DaylightDelta { get; set; } + + public IcdTimeZoneInfoTransitionTime DaylightTransitionStart { get; set; } + + public IcdTimeZoneInfoTransitionTime DaylightTransitionEnd { get; set; } + + // ----- SECTION: internal utility methods ----------------* + + // + // When Windows sets the daylight transition start Jan 1st at 12:00 AM, it means the year starts with the daylight saving on. + // We have to special case this value and not adjust it when checking if any date is in the daylight saving period. + // + public bool IsStartDateMarkerForBeginningOfYear() + { + return DaylightTransitionStart.Month == 1 && DaylightTransitionStart.Day == 1 && DaylightTransitionStart.TimeOfDay.Hour == 0 && + DaylightTransitionStart.TimeOfDay.Minute == 0 && DaylightTransitionStart.TimeOfDay.Second == 0 && + DateStart.Year == DateEnd.Year; + } + + // + // When Windows sets the daylight transition end Jan 1st at 12:00 AM, it means the year ends with the daylight saving on. + // We have to special case this value and not adjust it when checking if any date is in the daylight saving period. + // + public bool IsEndDateMarkerForEndOfYear() + { + return DaylightTransitionEnd.Month == 1 && DaylightTransitionEnd.Day == 1 && DaylightTransitionEnd.TimeOfDay.Hour == 0 && + DaylightTransitionEnd.TimeOfDay.Minute == 0 && DaylightTransitionEnd.TimeOfDay.Second == 0 && + DateStart.Year == DateEnd.Year; + } + } +} diff --git a/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoTransitionTime.cs b/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoTransitionTime.cs new file mode 100644 index 0000000..4c1c0a4 --- /dev/null +++ b/ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfoTransitionTime.cs @@ -0,0 +1,19 @@ +using System; + +namespace ICD.Common.Utils.TimeZoneInfo +{ + public sealed class IcdTimeZoneInfoTransitionTime + { + public bool IsFixedDateRule { get; set; } + + public DateTime TimeOfDay { get; set; } + + public DayOfWeek DayOfWeek { get; set; } + + public int Day { get; set; } + + public int Week { get; set; } + + public int Month { get; set; } + } +} diff --git a/ICD.Common.Utils/TimeZones.sqlite b/ICD.Common.Utils/TimeZones.sqlite new file mode 100644 index 0000000000000000000000000000000000000000..a9720924463ff01a80f31576c4d4befc22303765 GIT binary patch literal 90112 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