From dcb4a048dd808ede73cfb7eea104d92ec4ea875e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20Strehovsk=C3=BD?= Date: Fri, 31 Mar 2017 15:08:40 -0700 Subject: [PATCH] Merge pull request dotnet/corertdotnet/coreclr#3175 from jkotas/nmirror-merge Merge nmirror to master Signed-off-by: dotnet-bot Commit migrated from https://github.com/dotnet/coreclr/commit/f1bedf024cf3b0ea795688161cd94f0ccf289cdc --- .../shared/System.Private.CoreLib.Shared.projitems | 2 + .../shared/System/CurrentSystemTimeZone.cs | 199 +++++++++++++++ src/coreclr/src/mscorlib/shared/System/TimeZone.cs | 281 +++++++++++++++++++++ 3 files changed, 482 insertions(+) create mode 100644 src/coreclr/src/mscorlib/shared/System/CurrentSystemTimeZone.cs create mode 100644 src/coreclr/src/mscorlib/shared/System/TimeZone.cs diff --git a/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems b/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems index b4d6bf3..b24eda3 100644 --- a/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems +++ b/src/coreclr/src/mscorlib/shared/System.Private.CoreLib.Shared.projitems @@ -110,6 +110,7 @@ + @@ -286,6 +287,7 @@ + diff --git a/src/coreclr/src/mscorlib/shared/System/CurrentSystemTimeZone.cs b/src/coreclr/src/mscorlib/shared/System/CurrentSystemTimeZone.cs new file mode 100644 index 0000000..2d84839 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/CurrentSystemTimeZone.cs @@ -0,0 +1,199 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** Purpose: +** This class represents the current system timezone. It is +** the only meaningful implementation of the TimeZone class +** available in this version. +** +** The only TimeZone that we support in version 1 is the +** CurrentTimeZone as determined by the system timezone. +** +** +============================================================*/ + +using System; +using System.Diagnostics.Contracts; +using System.Text; +using System.Collections; +using System.Globalization; +using System.Runtime.CompilerServices; +using System.Runtime.Versioning; + +namespace System +{ + [Obsolete("System.CurrentSystemTimeZone has been deprecated. Please investigate the use of System.TimeZoneInfo.Local instead.")] + [Serializable] + internal partial class CurrentSystemTimeZone : TimeZone + { + // Standard offset in ticks to the Universal time if + // no daylight saving is in used. + // E.g. the offset for PST (Pacific Standard time) should be -8 * 60 * 60 * 1000 * 10000. + // (1 millisecond = 10000 ticks) + private long m_ticksOffset; + private String m_standardName; + private String m_daylightName; + + internal CurrentSystemTimeZone() + { + TimeZoneInfo local = TimeZoneInfo.Local; + + m_ticksOffset = local.BaseUtcOffset.Ticks; + m_standardName = local.StandardName; + m_daylightName = local.DaylightName; + } + + public override String StandardName + { + get + { + return m_standardName; + } + } + + public override String DaylightName + { + get + { + return m_daylightName; + } + } + + internal long GetUtcOffsetFromUniversalTime(DateTime time, ref Boolean isAmbiguousLocalDst) + { + // Get the daylight changes for the year of the specified time. + TimeSpan offset = new TimeSpan(m_ticksOffset); + DaylightTime daylightTime = GetDaylightChanges(time.Year); + isAmbiguousLocalDst = false; + + if (daylightTime == null || daylightTime.Delta.Ticks == 0) + { + return offset.Ticks; + } + + // The start and end times represent the range of universal times that are in DST for that year. + // Within that there is an ambiguous hour, usually right at the end, but at the beginning in + // the unusual case of a negative daylight savings delta. + DateTime startTime = daylightTime.Start - offset; + DateTime endTime = daylightTime.End - offset - daylightTime.Delta; + DateTime ambiguousStart; + DateTime ambiguousEnd; + + if (daylightTime.Delta.Ticks > 0) + { + ambiguousStart = endTime - daylightTime.Delta; + ambiguousEnd = endTime; + } + else + { + ambiguousStart = startTime; + ambiguousEnd = startTime - daylightTime.Delta; + } + + Boolean isDst = false; + 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); + } + + if (isDst) + { + offset += daylightTime.Delta; + + // See if the resulting local time becomes ambiguous. This must be captured here or the + // DateTime will not be able to round-trip back to UTC accurately. + if (time >= ambiguousStart && time < ambiguousEnd) + { + isAmbiguousLocalDst = true; + } + } + return offset.Ticks; + } + + public override DateTime ToLocalTime(DateTime time) + { + if (time.Kind == DateTimeKind.Local) + { + return time; + } + Boolean isAmbiguousLocalDst = false; + Int64 offset = GetUtcOffsetFromUniversalTime(time, ref isAmbiguousLocalDst); + long tick = time.Ticks + offset; + if (tick > DateTime.MaxTicks) + { + return new DateTime(DateTime.MaxTicks, DateTimeKind.Local); + } + if (tick < DateTime.MinTicks) + { + return new DateTime(DateTime.MinTicks, DateTimeKind.Local); + } + return new DateTime(tick, DateTimeKind.Local, isAmbiguousLocalDst); + } + + public override DaylightTime GetDaylightChanges(int year) + { + if (year < 1 || year > 9999) + { + throw new ArgumentOutOfRangeException(nameof(year), SR.Format(SR.ArgumentOutOfRange_Range, 1, 9999)); + } + + return GetCachedDaylightChanges(year); + } + + private static DaylightTime CreateDaylightChanges(int year) + { + DaylightTime currentDaylightChanges = null; + + if (TimeZoneInfo.Local.SupportsDaylightSavingTime) + { + DateTime start; + DateTime end; + TimeSpan delta; + + foreach (var rule in TimeZoneInfo.Local.GetAdjustmentRules()) + { + if (rule.DateStart.Year <= year && rule.DateEnd.Year >= year && rule.DaylightDelta != TimeSpan.Zero) + { + start = TimeZoneInfo.TransitionTimeToDateTime(year, rule.DaylightTransitionStart); + end = TimeZoneInfo.TransitionTimeToDateTime(year, rule.DaylightTransitionEnd); + delta = rule.DaylightDelta; + + currentDaylightChanges = new DaylightTime(start, end, delta); + break; + } + } + } + + if (currentDaylightChanges == null) + { + currentDaylightChanges = new DaylightTime(DateTime.MinValue, DateTime.MinValue, TimeSpan.Zero); + } + + return currentDaylightChanges; + } + + public override TimeSpan GetUtcOffset(DateTime time) + { + if (time.Kind == DateTimeKind.Utc) + { + return TimeSpan.Zero; + } + else + { + return new TimeSpan(TimeZone.CalculateUtcOffset(time, GetDaylightChanges(time.Year)).Ticks + m_ticksOffset); + } + } + } // class CurrentSystemTimeZone +} diff --git a/src/coreclr/src/mscorlib/shared/System/TimeZone.cs b/src/coreclr/src/mscorlib/shared/System/TimeZone.cs new file mode 100644 index 0000000..88e2e21 --- /dev/null +++ b/src/coreclr/src/mscorlib/shared/System/TimeZone.cs @@ -0,0 +1,281 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +/*============================================================ +** +** +** +** Purpose: +** This class is used to represent a TimeZone. It +** has methods for converting a DateTime to UTC from local time +** and to local time from UTC and methods for getting the +** standard name and daylight name of the time zone. +** +** The only TimeZone that we support in version 1 is the +** CurrentTimeZone as determined by the system timezone. +** +** +============================================================*/ + +using System; +using System.Text; +using System.Threading; +using System.Collections; +using System.Globalization; + +namespace System +{ + [Serializable] + [Obsolete("System.TimeZone has been deprecated. Please investigate the use of System.TimeZoneInfo instead.")] + public abstract class TimeZone + { + private static volatile TimeZone currentTimeZone = null; + + // Private object for locking instead of locking on a public type for SQL reliability work. + private static Object s_InternalSyncObject; + private static Object InternalSyncObject + { + get + { + if (s_InternalSyncObject == null) + { + Object o = new Object(); + Interlocked.CompareExchange(ref s_InternalSyncObject, o, null); + } + return s_InternalSyncObject; + } + } + + + protected TimeZone() + { + } + + public static TimeZone CurrentTimeZone + { + get + { + //Grabbing the cached value is required at the top of this function so that + //we don't incur a race condition with the ResetTimeZone method below. + TimeZone tz = currentTimeZone; + if (tz == null) + { + lock (InternalSyncObject) + { + if (currentTimeZone == null) + { + currentTimeZone = new CurrentSystemTimeZone(); + } + tz = currentTimeZone; + } + } + return (tz); + } + } + + //This method is called by CultureInfo.ClearCachedData in response to control panel + //change events. It must be synchronized because otherwise there is a race condition + //with the CurrentTimeZone property above. + internal static void ResetTimeZone() + { + if (currentTimeZone != null) + { + lock (InternalSyncObject) + { + currentTimeZone = null; + } + } + } + + public abstract String StandardName + { + get; + } + + public abstract String DaylightName + { + get; + } + + public abstract TimeSpan GetUtcOffset(DateTime time); + + // + // Converts the specified datatime to the Universal time base on the current timezone + // + public virtual DateTime ToUniversalTime(DateTime time) + { + if (time.Kind == DateTimeKind.Utc) + { + return time; + } + long tickCount = time.Ticks - GetUtcOffset(time).Ticks; + if (tickCount > DateTime.MaxTicks) + { + return new DateTime(DateTime.MaxTicks, DateTimeKind.Utc); + } + if (tickCount < DateTime.MinTicks) + { + return new DateTime(DateTime.MinTicks, DateTimeKind.Utc); + } + return new DateTime(tickCount, DateTimeKind.Utc); + } + + // + // Convert the specified datetime value from UTC to the local time based on the time zone. + // + public virtual DateTime ToLocalTime(DateTime time) + { + if (time.Kind == DateTimeKind.Local) + { + return time; + } + Boolean isAmbiguousLocalDst = false; + Int64 offset = ((CurrentSystemTimeZone)(TimeZone.CurrentTimeZone)).GetUtcOffsetFromUniversalTime(time, ref isAmbiguousLocalDst); + return new DateTime(time.Ticks + offset, DateTimeKind.Local, isAmbiguousLocalDst); + } + + // Return an array of DaylightTime which reflects the daylight saving periods in a particular year. + // We currently only support having one DaylightSavingTime per year. + // If daylight saving time is not used in this timezone, null will be returned. + public abstract DaylightTime GetDaylightChanges(int year); + + public virtual bool IsDaylightSavingTime(DateTime time) + { + return (IsDaylightSavingTime(time, GetDaylightChanges(time.Year))); + } + + // Check if the specified time is in a daylight saving time. Allows the user to + // specify the array of Daylight Saving Times. + public static bool IsDaylightSavingTime(DateTime time, DaylightTime daylightTimes) + { + return CalculateUtcOffset(time, daylightTimes) != TimeSpan.Zero; + } + + // + // NOTENOTE: Implementation detail + // In the transition from standard time to daylight saving time, + // if we convert local time to Universal time, we can have the + // following (take PST as an example): + // Local Universal UTC Offset + // ----- --------- ---------- + // 01:00AM 09:00 -8:00 + // 02:00 (=> 03:00) 10:00 -8:00 [This time doesn't actually exist, but it can be created from DateTime] + // 03:00 10:00 -7:00 + // 04:00 11:00 -7:00 + // 05:00 12:00 -7:00 + // + // So from 02:00 - 02:59:59, we should return the standard offset, instead of the daylight saving offset. + // + // In the transition from daylight saving time to standard time, + // if we convert local time to Universal time, we can have the + // following (take PST as an example): + // Local Universal UTC Offset + // ----- --------- ---------- + // 01:00AM 08:00 -7:00 + // 02:00 (=> 01:00) 09:00 -8:00 + // 02:00 10:00 -8:00 + // 03:00 11:00 -8:00 + // 04:00 12:00 -8:00 + // + // So in this case, the 02:00 does exist after the first 2:00 rolls back to 01:00. We don't need to special case this. + // But note that there are two 01:00 in the local time. + + // + // And imagine if the daylight saving offset is negative (although this does not exist in real life) + // In the transition from standard time to daylight saving time, + // if we convert local time to Universal time, we can have the + // following (take PST as an example, but the daylight saving offset is -01:00): + // Local Universal UTC Offset + // ----- --------- ---------- + // 01:00AM 09:00 -8:00 + // 02:00 (=> 01:00) 10:00 -9:00 + // 02:00 11:00 -9:00 + // 03:00 12:00 -9:00 + // 04:00 13:00 -9:00 + // 05:00 14:00 -9:00 + // + // So in this case, the 02:00 does exist after the first 2:00 rolls back to 01:00. We don't need to special case this. + // + // In the transition from daylight saving time to standard time, + // if we convert local time to Universal time, we can have the + // following (take PST as an example, daylight saving offset is -01:00): + // + // Local Universal UTC Offset + // ----- --------- ---------- + // 01:00AM 10:00 -9:00 + // 02:00 (=> 03:00) 11:00 -9:00 + // 03:00 11:00 -8:00 + // 04:00 12:00 -8:00 + // 05:00 13:00 -8:00 + // 06:00 14:00 -8:00 + // + // So from 02:00 - 02:59:59, we should return the daylight saving offset, instead of the standard offset. + // + internal static TimeSpan CalculateUtcOffset(DateTime time, DaylightTime daylightTimes) + { + if (daylightTimes == null) + { + return TimeSpan.Zero; + } + DateTimeKind kind = time.Kind; + if (kind == DateTimeKind.Utc) + { + return TimeSpan.Zero; + } + + DateTime startTime; + DateTime endTime; + + // startTime and endTime represent the period from either the start of DST to the end and includes the + // potentially overlapped times + startTime = daylightTimes.Start + daylightTimes.Delta; + endTime = daylightTimes.End; + + // For normal time zones, the ambiguous hour is the last hour of daylight saving when you wind the + // clock back. It is theoretically possible to have a positive delta, (which would really be daylight + // reduction time), where you would have to wind the clock back in the begnning. + DateTime ambiguousStart; + DateTime ambiguousEnd; + if (daylightTimes.Delta.Ticks > 0) + { + ambiguousStart = endTime - daylightTimes.Delta; + ambiguousEnd = endTime; + } + else + { + ambiguousStart = startTime; + ambiguousEnd = startTime - daylightTimes.Delta; + } + + Boolean isDst = false; + 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. + if (time >= startTime || time < endTime) + { + isDst = true; + } + } + else if (time >= startTime && time < endTime) + { + // In northern hemisphere, the daylight saving time starts in the middle of the year. + isDst = true; + } + + // If this date was previously converted from a UTC date and we were able to detect that the local + // DateTime would be ambiguous, this data is stored in the DateTime to resolve this ambiguity. + if (isDst && time >= ambiguousStart && time < ambiguousEnd) + { + isDst = time.IsAmbiguousDaylightSavingTime(); + } + + if (isDst) + { + return daylightTimes.Delta; + } + return TimeSpan.Zero; + } + } +} -- 2.7.4