mirror of
https://github.com/ICDSystems/ICD.Common.Utils.git
synced 2026-02-15 20:54:58 +00:00
feat: Add IcdTimeZoneInfo
This commit is contained in:
@@ -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 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 extensions to raise events with common event args using the data directly
|
||||||
- Added property to IcdEnvironment to determine whether SSL communication is enabled
|
- 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
|
### Changed
|
||||||
- Repeater changed to use configured callbacks instead of a dumb event
|
- Repeater changed to use configured callbacks instead of a dumb event
|
||||||
|
|||||||
@@ -49,6 +49,9 @@
|
|||||||
<None Update="CultureInfo.sqlite">
|
<None Update="CultureInfo.sqlite">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
|
<None Update="TimeZones.sqlite">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
@@ -110,6 +110,7 @@
|
|||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<Compile Include="Extensions\CollectionExtensions.cs" />
|
<Compile Include="Extensions\CollectionExtensions.cs" />
|
||||||
|
<Compile Include="TimeZoneInfo\IcdTimeZoneInfo.cs" />
|
||||||
<Compile Include="ObfuscationSettings.cs" />
|
<Compile Include="ObfuscationSettings.cs" />
|
||||||
<Compile Include="Extensions\BoolExtensions.cs" />
|
<Compile Include="Extensions\BoolExtensions.cs" />
|
||||||
<Compile Include="Extensions\ByteExtensions.cs" />
|
<Compile Include="Extensions\ByteExtensions.cs" />
|
||||||
@@ -222,6 +223,8 @@
|
|||||||
<Compile Include="Timers\IcdTimer.cs" />
|
<Compile Include="Timers\IcdTimer.cs" />
|
||||||
<Compile Include="Timers\Repeater.cs" />
|
<Compile Include="Timers\Repeater.cs" />
|
||||||
<Compile Include="Timers\SafeTimer.cs" />
|
<Compile Include="Timers\SafeTimer.cs" />
|
||||||
|
<Compile Include="TimeZoneInfo\IcdTimeZoneInfoAdjustmentRule.cs" />
|
||||||
|
<Compile Include="TimeZoneInfo\IcdTimeZoneInfoTransitionTime.cs" />
|
||||||
<Compile Include="TryUtils.cs" />
|
<Compile Include="TryUtils.cs" />
|
||||||
<Compile Include="UriQueryBuilder.cs" />
|
<Compile Include="UriQueryBuilder.cs" />
|
||||||
<Compile Include="UriUtils.cs" />
|
<Compile Include="UriUtils.cs" />
|
||||||
@@ -238,6 +241,9 @@
|
|||||||
<Compile Include="Xml\XmlReaderExtensions.cs" />
|
<Compile Include="Xml\XmlReaderExtensions.cs" />
|
||||||
<Compile Include="Xml\XmlUtils.cs" />
|
<Compile Include="Xml\XmlUtils.cs" />
|
||||||
<None Include="Properties\ControlSystem.cfg" />
|
<None Include="Properties\ControlSystem.cfg" />
|
||||||
|
<None Include="TimeZones.sqlite">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="$(MSBuildBinPath)\Microsoft.CompactFramework.CSharp.targets" />
|
<Import Project="$(MSBuildBinPath)\Microsoft.CompactFramework.CSharp.targets" />
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ namespace ICD.Common.Utils.Sqlite
|
|||||||
return m_Reader.GetInt32(ordinal);
|
return m_Reader.GetInt32(ordinal);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public long GetInt64(int ordinal)
|
||||||
|
{
|
||||||
|
return m_Reader.GetInt64(ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
public bool GetBoolean(int ordinal)
|
public bool GetBoolean(int ordinal)
|
||||||
{
|
{
|
||||||
return m_Reader.GetBoolean(ordinal);
|
return m_Reader.GetBoolean(ordinal);
|
||||||
|
|||||||
455
ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfo.cs
Normal file
455
ICD.Common.Utils/TimeZoneInfo/IcdTimeZoneInfo.cs
Normal file
@@ -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<string, IcdTimeZoneInfo> 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<string, IcdTimeZoneInfo>(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<IcdTimeZoneInfo> 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<IcdTimeZoneInfoAdjustmentRule> 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 |
|
||||||
|
// | <invalid time> | | <ambiguous time> |
|
||||||
|
// [========== DST ========>)
|
||||||
|
//
|
||||||
|
// -=-=-=-=-=- Some Weird Time Zone -=-=-=-=-=-=-
|
||||||
|
// April 2, 2006 October 29, 2006
|
||||||
|
// 1AM 2AM 2AM 3AM
|
||||||
|
// | -1 hr | | +1 hr |
|
||||||
|
// | <ambiguous time> | | <invalid time> |
|
||||||
|
// [======== 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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
ICD.Common.Utils/TimeZones.sqlite
Normal file
BIN
ICD.Common.Utils/TimeZones.sqlite
Normal file
Binary file not shown.
Reference in New Issue
Block a user