File: src\libraries\System.Private.CoreLib\src\System\CurrentSystemTimeZone.cs
Web Access
Project: src\src\coreclr\System.Private.CoreLib\System.Private.CoreLib.csproj (System.Private.CoreLib)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Globalization;
 
namespace System
{
    /// <summary>
    /// Represents the current system timezone.
    /// </summary>
    [Obsolete("System.CurrentSystemTimeZone has been deprecated. Investigate the use of System.TimeZoneInfo.Local instead.")]
    internal sealed 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 readonly long m_ticksOffset;
        private readonly string m_standardName;
        private readonly 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 => m_standardName;
 
        public override string DaylightName => m_daylightName;
 
        internal long GetUtcOffsetFromUniversalTime(DateTime time, ref bool 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;
            }
 
            bool isDst;
            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;
            }
            bool isAmbiguousLocalDst = false;
            long 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)
        {
            DateTime start = DateTime.MinValue;
            DateTime end = DateTime.MinValue;
            TimeSpan delta = TimeSpan.Zero;
 
            if (TimeZoneInfo.Local.SupportsDaylightSavingTime)
            {
                foreach (TimeZoneInfo.AdjustmentRule 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;
                        break;
                    }
                }
            }
 
            return new DaylightTime(start, end, delta);
        }
 
        public override TimeSpan GetUtcOffset(DateTime time)
        {
            if (time.Kind == DateTimeKind.Utc)
            {
                return TimeSpan.Zero;
            }
            else
            {
                return new TimeSpan(CalculateUtcOffset(time, GetDaylightChanges(time.Year)).Ticks + m_ticksOffset);
            }
        }
 
        private DaylightTime GetCachedDaylightChanges(int year) =>
            m_CachedDaylightChanges.GetOrAdd(year, CreateDaylightChanges);
 
        // The per-year information is cached in this instance value. As a result it can
        // be cleaned up by CultureInfo.ClearCachedData, which will clear the instance of this object
        private readonly ConcurrentDictionary<int, DaylightTime> m_CachedDaylightChanges = [];
    } // class CurrentSystemTimeZone
}