using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Abp.Extensions;
using Abp.IO.Extensions;
using Abp.Logging;
using Abp.Reflection.Extensions;
using Abp.Xml.Extensions;
using TimeZoneConverter;
namespace Abp.Timing.Timezone
{
///
/// A helper class for timezone operations
///
public static class TimezoneHelper
{
static readonly Dictionary WindowsTimeZoneMappings = new Dictionary();
static readonly Dictionary IanaTimeZoneMappings = new Dictionary();
static readonly object SyncObj = new object();
///
/// Maps given windows timezone id to IANA timezone id
///
///
///
///
public static string WindowsToIana(string windowsTimezoneId)
{
if (windowsTimezoneId.Equals("UTC", StringComparison.OrdinalIgnoreCase))
{
return "Etc/UTC";
}
GetTimezoneMappings();
if (WindowsTimeZoneMappings.ContainsKey(windowsTimezoneId))
{
return WindowsTimeZoneMappings[windowsTimezoneId];
}
throw new Exception($"Unable to map {windowsTimezoneId} to iana timezone.");
}
///
/// Maps given IANA timezone id to windows timezone id
///
///
///
///
public static string IanaToWindows(string ianaTimezoneId)
{
if (ianaTimezoneId.Equals("Etc/UTC", StringComparison.OrdinalIgnoreCase))
{
return "UTC";
}
GetTimezoneMappings();
if (IanaTimeZoneMappings.ContainsKey(ianaTimezoneId))
{
return IanaTimeZoneMappings[ianaTimezoneId];
}
throw new Exception(string.Format("Unable to map {0} to windows timezone.", ianaTimezoneId));
}
///
/// Converts a date from one timezone to another
///
///
///
///
///
public static DateTime? Convert(DateTime? date, string fromTimeZoneId, string toTimeZoneId)
{
if (!date.HasValue)
{
return null;
}
var sourceTimeZone = FindTimeZoneInfo(fromTimeZoneId);
var destinationTimeZone = FindTimeZoneInfo(toTimeZoneId);
return TimeZoneInfo.ConvertTime(date.Value, sourceTimeZone, destinationTimeZone);
}
///
/// Converts a utc datetime to a local time based on a timezone
///
///
///
///
public static DateTime? ConvertFromUtc(DateTime? date, string toTimeZoneId)
{
return Convert(date, "UTC", toTimeZoneId);
}
///
/// Converts a utc datetime in to a datetimeoffset
///
///
///
///
public static DateTimeOffset? ConvertFromUtcToDateTimeOffset(DateTime? date, string timeZoneId)
{
var zonedDate = ConvertFromUtc(date, timeZoneId);
return ConvertToDateTimeOffset(zonedDate, timeZoneId);
}
///
/// Converts a nullable date with a timezone to a nullable datetimeoffset
///
///
///
///
public static DateTimeOffset? ConvertToDateTimeOffset(DateTime? date, string timeZoneId)
{
if (!date.HasValue)
{
return null;
}
return ConvertToDateTimeOffset(date.Value, timeZoneId);
}
///
/// Converts a date with a timezone to a datetimeoffset
///
///
///
///
public static DateTimeOffset ConvertToDateTimeOffset(DateTime date, string timeZoneId)
{
var timeZone = FindTimeZoneInfo(timeZoneId);
var offset = timeZone.BaseUtcOffset;
var rule = timeZone.GetAdjustmentRules().FirstOrDefault(x => date >= x.DateStart && date <= x.DateEnd);
if (!timeZone.SupportsDaylightSavingTime || rule == null)
{
return new DateTimeOffset(date, offset);
}
var daylightStart = GetDaylightTransition(date, rule.DaylightTransitionStart);
var daylightEnd = GetDaylightTransition(date, rule.DaylightTransitionEnd);
if (date >= daylightStart && date <= daylightEnd)
{
offset = offset.Add(rule.DaylightDelta);
}
return new DateTimeOffset(date, offset);
}
private static DateTime GetDaylightTransition(DateTime date, TimeZoneInfo.TransitionTime transitionTime)
{
var daylightTime = new DateTime(date.Year, transitionTime.Month, 1);
if (transitionTime.IsFixedDateRule)
{
daylightTime = new DateTime(daylightTime.Year, daylightTime.Month, transitionTime.Day, transitionTime.TimeOfDay.Hour, transitionTime.TimeOfDay.Minute, transitionTime.TimeOfDay.Second);
}
else
{
daylightTime = daylightTime.NthOf(transitionTime.Week, transitionTime.DayOfWeek);
}
daylightTime = new DateTime(daylightTime.Year,
daylightTime.Month,
daylightTime.Day,
transitionTime.TimeOfDay.Hour,
transitionTime.TimeOfDay.Minute,
transitionTime.TimeOfDay.Second);
return daylightTime;
}
//from https://stackoverflow.com/questions/6140018/how-to-calculate-2nd-friday-of-month-in-c-sharp
private static DateTime NthOf(this DateTime currentDate, int occurrence, DayOfWeek day)
{
var firstDay = new DateTime(currentDate.Year, currentDate.Month, 1);
var firstOccurrence = firstDay.DayOfWeek == day ? firstDay : firstDay.AddDays(day - firstDay.DayOfWeek);
if (firstOccurrence.Month < currentDate.Month) occurrence = occurrence + 1;
return firstOccurrence.AddDays(7 * (occurrence - 1));
}
public static DateTime? ConvertTimeByIanaTimeZoneId(DateTime? date, string fromIanaTimeZoneId, string toIanaTimeZoneId)
{
if (!date.HasValue)
{
return null;
}
var sourceTimeZone = FindTimeZoneInfo(IanaToWindows(fromIanaTimeZoneId));
var destinationTimeZone = FindTimeZoneInfo(IanaToWindows(toIanaTimeZoneId));
return TimeZoneInfo.ConvertTime(date.Value, sourceTimeZone, destinationTimeZone);
}
public static DateTime? ConvertTimeFromUtcByIanaTimeZoneId(DateTime? date, string toIanaTimeZoneId)
{
return ConvertTimeByIanaTimeZoneId(date, "Etc/UTC", toIanaTimeZoneId);
}
public static DateTime? ConvertTimeToUtcByIanaTimeZoneId(DateTime? date, string fromIanaTimeZoneId)
{
return ConvertTimeByIanaTimeZoneId(date, fromIanaTimeZoneId, "Etc/UTC");
}
public static TimeZoneInfo FindTimeZoneInfo(string windowsOrIanaTimeZoneId)
{
return TZConvert.GetTimeZoneInfo(windowsOrIanaTimeZoneId);
}
public static List GetWindowsTimeZoneIds(bool ignoreTimeZoneNotFoundException = true)
{
return TZConvert.KnownWindowsTimeZoneIds.ToList();
}
private static void GetTimezoneMappings()
{
if (WindowsTimeZoneMappings.Count > 0 && IanaTimeZoneMappings.Count > 0)
{
return;
}
lock (SyncObj)
{
if (WindowsTimeZoneMappings.Count > 0 && IanaTimeZoneMappings.Count > 0)
{
return;
}
var assembly = typeof(TimezoneHelper).GetAssembly();
var resourceNames = assembly.GetManifestResourceNames();
var resourceName = resourceNames.First(r => r.Contains("WindowsZones.xml"));
using (Stream stream = assembly.GetManifestResourceStream(resourceName))
{
var bytes = stream.GetAllBytes();
var xmlString = Encoding.UTF8.GetString(bytes, 3, bytes.Length - 3); //Skipping byte order mark
var xmlDocument = new XmlDocument();
xmlDocument.LoadXml(xmlString);
var windowsMappingNodes = xmlDocument.SelectNodes("//supplementalData/windowsZones/mapTimezones/mapZone[@territory='001']");
var ianaMappingNodes = xmlDocument.SelectNodes("//supplementalData/windowsZones/mapTimezones/mapZone");
AddWindowsMappingsToDictionary(WindowsTimeZoneMappings, windowsMappingNodes);
AddIanaMappingsToDictionary(IanaTimeZoneMappings, ianaMappingNodes);
}
}
}
private static void AddWindowsMappingsToDictionary(Dictionary timeZoneMappings, XmlNodeList defaultMappingNodes)
{
foreach (XmlNode defaultMappingNode in defaultMappingNodes)
{
var windowsTimezoneId = defaultMappingNode.GetAttributeValueOrNull("other");
var ianaTimezoneId = defaultMappingNode.GetAttributeValueOrNull("type");
if (windowsTimezoneId.IsNullOrEmpty() || ianaTimezoneId.IsNullOrEmpty())
{
continue;
}
timeZoneMappings.Add(windowsTimezoneId, ianaTimezoneId);
}
}
private static void AddIanaMappingsToDictionary(Dictionary timeZoneMappings, XmlNodeList defaultMappingNodes)
{
foreach (XmlNode defaultMappingNode in defaultMappingNodes)
{
var ianaTimezoneId = defaultMappingNode.GetAttributeValueOrNull("type");
var windowsTimezoneId = defaultMappingNode.GetAttributeValueOrNull("other");
if (ianaTimezoneId.IsNullOrEmpty() || windowsTimezoneId.IsNullOrEmpty())
{
continue;
}
ianaTimezoneId
.Split(" ", StringSplitOptions.RemoveEmptyEntries)
.Where(id => !timeZoneMappings.ContainsKey(id))
.ToList()
.ForEach(ianaId => timeZoneMappings.Add(ianaId, windowsTimezoneId));
}
}
}
}