Beispiel #1
0
        /// <summary>
        /// Creates a new <see cref="BclDateTimeZone" /> from a <see cref="TimeZoneInfo"/> from the Base Class Library.
        /// </summary>
        /// <param name="bclZone">The original time zone to take information from.</param>
        /// <returns>A <see cref="BclDateTimeZone"/> wrapping the given <c>TimeZoneInfo</c>.</returns>
        public static BclDateTimeZone FromTimeZoneInfo(TimeZoneInfo bclZone)
        {
            Preconditions.CheckNotNull(bclZone, nameof(bclZone));
            Offset standardOffset = bclZone.BaseUtcOffset.ToOffset();
            var    rules          = bclZone.GetAdjustmentRules();

            if (!bclZone.SupportsDaylightSavingTime || rules.Length == 0)
            {
                var fixedInterval = new ZoneInterval(bclZone.StandardName, Instant.BeforeMinValue, Instant.AfterMaxValue, standardOffset, Offset.Zero);
                return(new BclDateTimeZone(bclZone, new SingleZoneIntervalMap(fixedInterval)));
            }

            BclAdjustmentRule[] convertedRules;
            if (AreWindowsStyleRules(rules))
            {
                convertedRules = Array.ConvertAll(rules, rule => BclAdjustmentRule.FromWindowsAdjustmentRule(bclZone, rule));
            }
            else
            {
                convertedRules = Array.ConvertAll(rules, rule => BclAdjustmentRule.FromUnixAdjustmentRule(bclZone, rule));
                FixUnixTransitions(convertedRules);
            }

            IZoneIntervalMap uncachedMap = BuildMap(convertedRules, standardOffset, bclZone.StandardName);
            IZoneIntervalMap cachedMap   = CachingZoneIntervalMap.CacheMap(uncachedMap);

            return(new BclDateTimeZone(bclZone, cachedMap));
        }
Beispiel #2
0
        /// <summary>
        /// Gets the zone offset period for the given instant.
        /// </summary>
        /// <param name="instant">The Instant to find.</param>
        /// <returns>The ZoneInterval including the given instant.</returns>
        public override ZoneInterval GetZoneInterval(Instant instant)
        {
            if (tailZone != null && instant >= tailZoneStart)
            {
                // Clamp the tail zone interval to start at the end of our final period, if necessary, so that the
                // join is seamless.
                ZoneInterval intervalFromTailZone = tailZone.GetZoneInterval(instant);
                return(intervalFromTailZone.RawStart < tailZoneStart ? firstTailZoneInterval : intervalFromTailZone);
            }

            int lower = 0;              // Inclusive
            int upper = periods.Length; // Exclusive

            while (lower < upper)
            {
                int current   = (lower + upper) / 2;
                var candidate = periods[current];
                if (candidate.RawStart > instant)
                {
                    upper = current;
                }
                // Safe to use RawEnd, as it's just for the comparison.
                else if (candidate.RawEnd <= instant)
                {
                    lower = current + 1;
                }
                else
                {
                    return(candidate);
                }
            }
            // Note: this would indicate a bug. The time zone is meant to cover the whole of time.
            throw new InvalidOperationException($"Instant {instant} did not exist in time zone {Id}");
        }
        private IEnumerable <ZoneInterval> CoalesceIntervals(IEnumerable <ZoneInterval> zoneIntervals)
        {
            ZoneInterval current = null;

            foreach (var zoneInterval in zoneIntervals)
            {
                if (current == null)
                {
                    current = zoneInterval;
                    continue;
                }
                if (zoneIntervalComparer.EqualExceptStartAndEnd(current, zoneInterval))
                {
                    current = current.WithEnd(zoneInterval.RawEnd);
                }
                else
                {
                    yield return(current);

                    current = zoneInterval;
                }
            }
            // current will only be null if start == end...
            if (current != null)
            {
                yield return(current);
            }
        }
        /// <summary>
        /// Reads a time zone from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="id">The id.</param>
        /// <returns>The time zone.</returns>
        public static DateTimeZone ReadLegacy(LegacyDateTimeZoneReader reader, string id)
        {
            string[] stringPool = new string[reader.ReadCount()];
            for (int i = 0; i < stringPool.Length; i++)
            {
                stringPool[i] = reader.ReadString();
            }

            int size    = reader.ReadCount();
            var periods = new ZoneInterval[size];
            var start   = reader.ReadZoneIntervalTransition(null);

            for (int i = 0; i < size; i++)
            {
                int nameIndex = stringPool.Length < 256 ? reader.ReadByte() : reader.ReadInt32();
                var name      = stringPool[nameIndex];
                var offset    = reader.ReadOffset();
                var savings   = reader.ReadOffset();
                var nextStart = reader.ReadZoneIntervalTransition(start);
                periods[i] = new ZoneInterval(name, start, nextStart, offset, savings);
                start      = nextStart;
            }
            var tailZone = reader.ReadTimeZone(id + "-tail");

            return(new PrecalculatedDateTimeZone(id, periods, tailZone));
        }
Beispiel #5
0
        /// <summary>
        /// Creates a new <see cref="BclDateTimeZone" /> from a <see cref="TimeZoneInfo"/> from the Base Class Library.
        /// </summary>
        /// <param name="bclZone">The original time zone to take information from.</param>
        /// <returns>A <see cref="BclDateTimeZone"/> wrapping the given <c>TimeZoneInfo</c>.</returns>
        public static BclDateTimeZone FromTimeZoneInfo(TimeZoneInfo bclZone)
        {
            Preconditions.CheckNotNull(bclZone, nameof(bclZone));
            Offset standardOffset = bclZone.BaseUtcOffset.ToOffset();
            var    rules          = bclZone.GetAdjustmentRules();

            if (!bclZone.SupportsDaylightSavingTime || rules.Length == 0)
            {
                var fixedInterval = new ZoneInterval(bclZone.StandardName, Instant.BeforeMinValue, Instant.AfterMaxValue, standardOffset, Offset.Zero);
                return(new BclDateTimeZone(bclZone, standardOffset, standardOffset, new SingleZoneIntervalMap(fixedInterval)));
            }

            int windowsRules  = rules.Count(IsWindowsRule);
            var ruleConverter = AreWindowsStyleRules(rules)
                ? rule => BclAdjustmentRule.FromWindowsAdjustmentRule(bclZone, rule)
                : (Converter <TimeZoneInfo.AdjustmentRule, BclAdjustmentRule>)(rule => BclAdjustmentRule.FromUnixAdjustmentRule(bclZone, rule));

            BclAdjustmentRule[] convertedRules = Array.ConvertAll(rules, ruleConverter);

            Offset minRuleOffset = convertedRules.Aggregate(Offset.MaxValue, (min, rule) => Offset.Min(min, rule.Savings + rule.StandardOffset));
            Offset maxRuleOffset = convertedRules.Aggregate(Offset.MinValue, (min, rule) => Offset.Max(min, rule.Savings + rule.StandardOffset));

            IZoneIntervalMap uncachedMap = BuildMap(convertedRules, standardOffset, bclZone.StandardName);
            IZoneIntervalMap cachedMap   = CachingZoneIntervalMap.CacheMap(uncachedMap);

            return(new BclDateTimeZone(bclZone, Offset.Min(standardOffset, minRuleOffset), Offset.Max(standardOffset, maxRuleOffset), cachedMap));
        }
 internal ZoneLocalMapping(DateTimeZone zone, LocalDateTime localDateTime, ZoneInterval earlyInterval, ZoneInterval lateInterval, int count)
 {
     this.zone          = Preconditions.CheckNotNull(zone, "zone");
     this.localDateTime = localDateTime;
     this.earlyInterval = Preconditions.CheckNotNull(earlyInterval, "earlyInterval");
     this.lateInterval  = Preconditions.CheckNotNull(lateInterval, "lateInterval");
     Preconditions.CheckArgumentRange("count", count, 0, 2);
     this.count = count;
 }
 internal ZoneLocalMapping([Trusted][NotNull] DateTimeZone zone, LocalDateTime localDateTime,
                           [Trusted][NotNull] ZoneInterval earlyInterval, [Trusted][NotNull] ZoneInterval lateInterval, int count)
 {
     Preconditions.DebugCheckNotNull(zone, nameof(zone));
     Preconditions.DebugCheckNotNull(earlyInterval, nameof(earlyInterval));
     Preconditions.DebugCheckNotNull(lateInterval, nameof(lateInterval));
     Preconditions.DebugCheckArgumentRange(nameof(count), count, 0, 2);
     this.Zone          = zone;
     this.EarlyInterval = earlyInterval;
     this.LateInterval  = lateInterval;
     this.LocalDateTime = localDateTime;
     this.Count         = count;
 }
 internal PrecalculatedDateTimeZone([NotNull] string id, [NotNull] ZoneInterval[] intervals, IZoneIntervalMapWithMinMax tailZone)
     : base(id, false,
            ComputeOffset(intervals, tailZone, Offset.Min),
            ComputeOffset(intervals, tailZone, Offset.Max))
 {
     this.periods       = intervals;
     this.tailZone      = tailZone;
     this.tailZoneStart = intervals[intervals.Length - 1].RawEnd; // We want this to be AfterMaxValue for tail-less zones.
     if (tailZone != null)
     {
         // Cache a "clamped" zone interval for use at the start of the tail zone.
         firstTailZoneInterval = tailZone.GetZoneInterval(tailZoneStart).WithStart(tailZoneStart);
     }
     ValidatePeriods(intervals, tailZone);
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="PrecalculatedDateTimeZone"/> class.
 /// This is only visible to make testing simpler.
 /// </summary>
 /// <param name="id">The id.</param>
 /// <param name="periods">The periods.</param>
 /// <param name="tailZone">The tail zone.</param>
 internal PrecalculatedDateTimeZone(string id, ZoneInterval[] periods, DateTimeZone tailZone)
     : base(id, false,
            ComputeOffset(periods, p => p.WallOffset, tailZone, Offset.Min),
            ComputeOffset(periods, p => p.WallOffset, tailZone, Offset.Max))
 {
     this.tailZone      = tailZone;
     this.periods       = periods;
     this.tailZone      = tailZone;
     this.tailZoneStart = periods[periods.Length - 1].End;
     if (tailZone != null)
     {
         // Cache a "clamped" zone interval for use at the start of the tail zone.
         firstTailZoneInterval = tailZone.GetZoneInterval(tailZoneStart).WithStart(tailZoneStart);
     }
     ValidatePeriods(periods, tailZone);
 }
Beispiel #10
0
                /// <summary>
                /// Creates a hash table node with all the information for this period.
                /// We start off by finding the interval for the start of the period, and
                /// then repeatedly check whether that interval ends after the end of the
                /// period - at which point we're done. If not, find the next interval, create
                /// a new node referring to that interval and the previous interval, and keep going.
                /// </summary>
                internal static HashCacheNode CreateNode(int period, IZoneIntervalMap map)
                {
                    var periodStart = new Instant((long)period << PeriodShift);
                    var periodEnd   = new Instant((long)(period + 1) << PeriodShift);

                    var interval = map.GetZoneInterval(periodStart);
                    var node     = new HashCacheNode(interval, period, null);

                    // Keep going while the current interval ends before the period.
                    while (interval.End < periodEnd)
                    {
                        interval = map.GetZoneInterval(interval.End);
                        node     = new HashCacheNode(interval, period, node);
                    }

                    return(node);
                }
        /// <summary>
        /// Reads a time zone from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="id">The id.</param>
        /// <returns>The time zone.</returns>
        public static DateTimeZone Read(IDateTimeZoneReader reader, string id)
        {
            int size    = reader.ReadCount();
            var periods = new ZoneInterval[size];
            var start   = reader.ReadZoneIntervalTransition(null);

            for (int i = 0; i < size; i++)
            {
                var name      = reader.ReadString();
                var offset    = reader.ReadOffset();
                var savings   = reader.ReadOffset();
                var nextStart = reader.ReadZoneIntervalTransition(start);
                periods[i] = new ZoneInterval(name, start, nextStart, offset, savings);
                start      = nextStart;
            }
            var tailZone = reader.ReadByte() == 1 ? DaylightSavingsDateTimeZone.Read(reader, id + "-tail") : null;

            return(new PrecalculatedDateTimeZone(id, periods, tailZone));
        }
Beispiel #12
0
        /// <summary>
        /// Creates a new <see cref="BclDateTimeZone" /> from a <see cref="TimeZoneInfo"/> from the Base Class Library.
        /// </summary>
        /// <param name="bclZone">The original time zone to take information from.</param>
        /// <returns>A <see cref="BclDateTimeZone"/> wrapping the given <c>TimeZoneInfo</c>.</returns>
        [NotNull] public static BclDateTimeZone FromTimeZoneInfo([NotNull] TimeZoneInfo bclZone)
        {
            Preconditions.CheckNotNull(bclZone, nameof(bclZone));
            Offset standardOffset = bclZone.BaseUtcOffset.ToOffset();
            var    rules          = bclZone.GetAdjustmentRules();

            if (!bclZone.SupportsDaylightSavingTime || rules.Length == 0)
            {
                var fixedInterval = new ZoneInterval(bclZone.StandardName, Instant.BeforeMinValue, Instant.AfterMaxValue, standardOffset, Offset.Zero);
                return(new BclDateTimeZone(bclZone, standardOffset, standardOffset, new SingleZoneIntervalMap(fixedInterval)));
            }

            BclAdjustmentRule[] convertedRules = Array.ConvertAll(rules, rule => new BclAdjustmentRule(bclZone, rule));
            Offset minRuleOffset = convertedRules.Aggregate(Offset.MaxValue, (min, rule) => Offset.Min(min, rule.Savings + rule.StandardOffset));
            Offset maxRuleOffset = convertedRules.Aggregate(Offset.MinValue, (min, rule) => Offset.Max(min, rule.Savings + rule.StandardOffset));

            IZoneIntervalMap uncachedMap = BuildMap(convertedRules, standardOffset, bclZone.StandardName);
            IZoneIntervalMap cachedMap   = CachingZoneIntervalMap.CacheMap(uncachedMap, CachingZoneIntervalMap.CacheType.Hashtable);

            return(new BclDateTimeZone(bclZone, Offset.Min(standardOffset, minRuleOffset), Offset.Max(standardOffset, maxRuleOffset), cachedMap));
        }
        /// <summary>
        /// Gets the zone offset period for the given instant.
        /// </summary>
        /// <param name="instant">The Instant to find.</param>
        /// <returns>The ZoneInterval including the given instant.</returns>
        public override ZoneInterval GetZoneInterval(Instant instant)
        {
            if (tailZone != null && instant >= tailZoneStart)
            {
                // Clamp the tail zone interval to start at the end of our final period, if necessary, so that the
                // join is seamless.
                ZoneInterval intervalFromTailZone = tailZone.GetZoneInterval(instant);
                return(intervalFromTailZone.Start < tailZoneStart ? firstTailZoneInterval : intervalFromTailZone);
            }

            // Special case to avoid the later logic being problematic
            if (instant == Instant.MaxValue)
            {
                return(periods[periods.Length - 1]);
            }

            int lower = 0;              // Inclusive
            int upper = periods.Length; // Exclusive

            while (lower < upper)
            {
                int current   = (lower + upper) / 2;
                var candidate = periods[current];
                if (candidate.Start > instant)
                {
                    upper = current;
                }
                else if (candidate.End <= instant)
                {
                    lower = current + 1;
                }
                else
                {
                    return(candidate);
                }
            }
            // Note: this would indicate a bug. The time zone is meant to cover the whole of time.
            throw new InvalidOperationException(string.Format("Instant {0} did not exist in time zone {1}", instant, Id));
        }
Beispiel #14
0
        /// <summary>
        /// Reads a time zone from the specified reader.
        /// </summary>
        /// <param name="reader">The reader.</param>
        /// <param name="id">The id.</param>
        /// <returns>The time zone.</returns>
        internal static DateTimeZone Read([Trusted][NotNull] IDateTimeZoneReader reader, [Trusted][NotNull] string id)
        {
            Preconditions.DebugCheckNotNull(reader, nameof(reader));
            Preconditions.DebugCheckNotNull(id, nameof(id));
            int size    = reader.ReadCount();
            var periods = new ZoneInterval[size];
            // It's not entirely clear why we don't just assume that the first zone interval always starts at Instant.BeforeMinValue
            // (given that we check that later) but we don't... and changing that now could cause compatibility issues.
            var start = reader.ReadZoneIntervalTransition(null);

            for (int i = 0; i < size; i++)
            {
                var name      = reader.ReadString();
                var offset    = reader.ReadOffset();
                var savings   = reader.ReadOffset();
                var nextStart = reader.ReadZoneIntervalTransition(start);
                periods[i] = new ZoneInterval(name, start, nextStart, offset, savings);
                start      = nextStart;
            }
            var tailZone = reader.ReadByte() == 1 ? DaylightSavingsDateTimeZone.Read(reader, id + "-tail") : null;

            return(new PrecalculatedDateTimeZone(id, periods, tailZone));
        }
 internal static ZoneIntervalPair Ambiguous(ZoneInterval early, ZoneInterval late)
 {
     return(new ZoneIntervalPair(early, late, 2));
 }
Beispiel #16
0
 // Visible for tests
 internal BclAdjustmentRule(ZoneInterval zoneInterval)
 {
     StandardOffset = zoneInterval.StandardOffset;
     Savings        = zoneInterval.Savings;
     PartialMap     = PartialZoneIntervalMap.ForZoneInterval(zoneInterval);
 }
Beispiel #17
0
            internal static BclAdjustmentRule ConvertUnixRuleToBclAdjustmentRule(TimeZoneInfo.AdjustmentRule rule,
                                                                                 string standardName, string daylightName, TimeSpan zoneStandardOffset, TimeSpan ruleStandardOffset,
                                                                                 bool forceDaylightSavings)
            {
                // On .NET Core on Unix, each "adjustment rule" is effectively just a zone interval. The transitions are only used
                // to give the time of day values to combine with rule.DateStart and rule.DateEnd. It's all a bit odd.
                // The *last* adjustment rule internally can work like a normal Windows standard/daylight rule, but that's only
                // exposed in .NET 6.0, and those rules are handled in FromUnixAdjustmentRule.
                // (Currently we don't have a way of handling a fixed-date rule to the end of time that really represents alternating
                // standard/daylight. Apparently that's not an issue.)

                // The start of each rule is indicated by the start date with the time-of-day of the transition, interpreted as being in the *zone* standard offset
                // (rather than the *rule* standard offset). Some rules effectively start in daylight time, but only when there are consecutive daylight time
                // rules. This is handled in FixOverlappingUnixRules.

                var bclLocalStart = rule.DateStart + rule.DaylightTransitionStart.TimeOfDay.TimeOfDay;
                var bclUtcStart   = DateTime.SpecifyKind(bclLocalStart == DateTime.MinValue ? DateTime.MinValue : bclLocalStart - zoneStandardOffset, DateTimeKind.Utc);
                var bclSavings    = rule.DaylightDelta;

                // The end of each rule is indicated by the start date with the time-of-day of the transition, interpreted as being in the *zone* standard offset
                // with the *rule* daylight delta.
                var bclLocalEnd = rule.DateEnd + rule.DaylightTransitionEnd.TimeOfDay.TimeOfDay;
                var bclUtcEnd   = DateTime.SpecifyKind(rule.DateEnd == MaxDate ? DateTime.MaxValue : bclLocalEnd - (zoneStandardOffset + bclSavings), DateTimeKind.Utc);

                // For just a couple of time zones in .NET 6, there are adjustment rules which appear to be invalid
                // in the normal expectation of "starts in standard, ends in daylight".
                // Example in America/Creston: 1944-01-01 - 1944-01-01: Daylight delta: +01; DST starts January 01 at 00:00:00 and ends January 01 at 00:00:59.999
                // Handle this by treating the rule as starting in daylight time.
                if (bclUtcStart >= bclUtcEnd)
                {
                    bclUtcStart -= bclSavings;
                }

                // If the zone says that the start of the rule is in DST, but the rule has no daylight savings,
                // assume we actually want an hour of DST but one less hour of standard offset.
                // See Europe/Dublin in 1960 for example, in .NET 6:
                // 1960-04-10 - 1960-10-02: Daylight delta: +00; DST starts April 10 at 03:00:00 and ends October 02 at 02:59:59.999 (force daylight)
                if (forceDaylightSavings)
                {
                    bclSavings          = TimeSpan.FromHours(1);
                    ruleStandardOffset -= bclSavings;
                }

                // Handle changes crossing the international date line, which used to be represented as savings of +/-23
                // hours (but could conceivably be more).
                // Note: I can't currently reproduce this in .NET Core 3.1 or .NET 6. It may be a legacy Mono artifact;
                // it does no harm to preserve it, however.
                if (bclSavings.Hours < -14)
                {
                    bclSavings += TimeSpan.FromDays(1);
                }
                else if (bclSavings.Hours > 14)
                {
                    bclSavings -= TimeSpan.FromDays(1);
                }

                // Now all the values are sensible - and in particular, now the daylight savings are in a range that can be represented by
                // Offset - we can converted everything to Noda Time types.
                var nodaStart = bclUtcStart == DateTime.MinValue ? Instant.BeforeMinValue : bclUtcStart.ToInstant();
                // The representation returned to us (not the internal representation) has an end point one second (before .NET 6)
                // or one millisecond (.NET 6 onwards) before the transition. We round up to a properly exclusive end instant.
                var endTimeCompensation = Duration.FromSeconds(1) - Duration.FromMilliseconds(bclLocalEnd.Millisecond);

                var nodaEnd        = bclUtcEnd == DateTime.MaxValue ? Instant.AfterMaxValue : bclUtcEnd.ToInstant() + endTimeCompensation;
                var nodaStandard   = ruleStandardOffset.ToOffset();
                var nodaSavings    = bclSavings.ToOffset();
                var nodaWallOffset = nodaStandard + nodaSavings;

                var zoneInterval = new ZoneInterval(nodaSavings == Offset.Zero ? standardName : daylightName, nodaStart, nodaEnd, nodaWallOffset, nodaSavings);

                return(new BclAdjustmentRule(zoneInterval));
            }
 private ZonedDateTime BuildZonedDateTime(ZoneInterval interval) =>
 new ZonedDateTime(LocalDateTime.WithOffset(interval.WallOffset), Zone);
 /// <summary>
 /// Initializes a new instance of the <see cref="HashCacheNode"/> class.
 /// </summary>
 /// <param name="interval">The zone interval.</param>
 /// <param name="period"></param>
 /// <param name="previous">The previous <see cref="HashCacheNode"/> node.</param>
 private HashCacheNode(ZoneInterval interval, int period, HashCacheNode previous)
 {
     this.Period   = period;
     this.Interval = interval;
     this.Previous = previous;
 }
Beispiel #20
0
 internal BclZoneIntervalMap(List <AdjustmentInterval> adjustmentIntervals, ZoneInterval headInterval, ZoneInterval tailInterval)
 {
     this.adjustmentIntervals = adjustmentIntervals;
     this.headInterval        = headInterval;
     this.tailInterval        = tailInterval;
 }
Beispiel #21
0
 internal FixedZoneIntervalMap(ZoneInterval interval)
 {
     this.interval = interval;
 }
 internal static ZoneIntervalPair Unambiguous(ZoneInterval interval)
 {
     return(new ZoneIntervalPair(interval, null, 1));
 }
Beispiel #23
0
 /// <summary>
 /// Returns whether this zone interval has the same offsets and name as another.
 /// </summary>
 internal bool EqualIgnoreBounds([Trusted] ZoneInterval other)
 {
     Preconditions.DebugCheckNotNull(other, nameof(other));
     return(other.WallOffset == WallOffset && other.Savings == Savings && other.Name == Name);
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="FixedDateTimeZone"/> class.
 /// </summary>
 /// <param name="id">The id.</param>
 /// <param name="offset">The offset.</param>
 public FixedDateTimeZone(string id, Offset offset) : base(id, true, offset, offset)
 {
     this.offset  = offset;
     interval     = new ZoneInterval(id, Instant.MinValue, Instant.MaxValue, offset, Offset.Zero);
     intervalPair = ZoneIntervalPair.Unambiguous(interval);
 }
 /// <summary>
 /// Builds a PartialZoneIntervalMap wrapping the given zone interval, taking its start and end as the start and end of
 /// the portion of the time line handled by the partial map.
 /// </summary>
 internal static PartialZoneIntervalMap ForZoneInterval(ZoneInterval interval) =>
 new PartialZoneIntervalMap(interval.RawStart, interval.RawEnd, new SingleZoneIntervalMap(interval));
Beispiel #26
0
 internal AdjustmentInterval(Instant start, DaylightSavingsDateTimeZone adjustmentZone, ZoneInterval seam)
 {
     this.start          = start;
     this.seam           = seam;
     this.adjustmentZone = adjustmentZone;
 }
Beispiel #27
0
 /// <summary>
 /// Initializes a new instance of the <see cref="FixedDateTimeZone"/> class.
 /// </summary>
 /// <remarks>The name (for the <see cref="ZoneInterval"/>) is deemed to be the same as the ID.</remarks>
 /// <param name="id">The id.</param>
 /// <param name="offset">The offset.</param>
 /// <param name="name">The name to use in the sole <see cref="ZoneInterval"/> in this zone.</param>
 internal FixedDateTimeZone([NotNull] string id, Offset offset, [NotNull] string name) : base(id, true, offset, offset)
 {
     interval = new ZoneInterval(name, Instant.BeforeMinValue, Instant.AfterMaxValue, offset, Offset.Zero);
 }
 private ZonedDateTime BuildZonedDateTime(ZoneInterval interval)
 {
     return(new ZonedDateTime(localDateTime, interval.WallOffset, zone));
 }
 private ZoneIntervalPair(ZoneInterval early, ZoneInterval late, int matchingIntervals)
 {
     this.earlyInterval     = early;
     this.lateInterval      = late;
     this.matchingIntervals = matchingIntervals;
 }
Beispiel #30
0
        /// <summary>
        /// Creates a new <see cref="BclDateTimeZone" /> from a <see cref="TimeZoneInfo"/> from the Base Class Library.
        /// </summary>
        /// <param name="bclZone">The original time zone to take information from.</param>
        /// <returns>A <see cref="BclDateTimeZone"/> wrapping the given <c>TimeZoneInfo</c>.</returns>
        public static BclDateTimeZone FromTimeZoneInfo([NotNull] TimeZoneInfo bclZone)
        {
            Preconditions.CheckNotNull(bclZone, "bclZone");
            Offset standardOffset = Offset.FromTimeSpan(bclZone.BaseUtcOffset);
            Offset minSavings     = Offset.Zero; // Just in case we have negative savings!
            Offset maxSavings     = Offset.Zero;

            var rules = bclZone.GetAdjustmentRules();

            if (!bclZone.SupportsDaylightSavingTime || rules.Length == 0)
            {
                var fixedInterval = new ZoneInterval(bclZone.StandardName, Instant.MinValue, Instant.MaxValue, standardOffset, Offset.Zero);
                return(new BclDateTimeZone(bclZone, standardOffset, standardOffset, new FixedZoneIntervalMap(fixedInterval)));
            }
            var     adjustmentIntervals = new List <AdjustmentInterval>();
            var     headInterval        = ComputeHeadInterval(bclZone, rules[0]);
            Instant previousEnd         = headInterval != null ? headInterval.End : Instant.MinValue;

            // TODO(Post-V1): Tidy this up. All of this is horrible.
            for (int i = 0; i < rules.Length; i++)
            {
                var            rule = rules[i];
                ZoneRecurrence standard, daylight;
                GetRecurrences(bclZone, rule, out standard, out daylight);

                minSavings = Offset.Min(minSavings, daylight.Savings);
                maxSavings = Offset.Max(maxSavings, daylight.Savings);

                // Find the last valid transition by working back from the end of time. It's safe to unconditionally
                // take the value here, as there must *be* some recurrences.
                var        lastStandard    = standard.PreviousOrFail(LastValidTick, standardOffset, daylight.Savings);
                var        lastDaylight    = daylight.PreviousOrFail(LastValidTick, standardOffset, Offset.Zero);
                bool       standardIsLater = lastStandard.Instant > lastDaylight.Instant;
                Transition lastTransition  = standardIsLater ? lastStandard : lastDaylight;
                Offset     seamSavings     = lastTransition.NewOffset - standardOffset;
                string     seamName        = standardIsLater ? bclZone.StandardName : bclZone.DaylightName;

                Instant nextStart;

                Instant ruleEnd = GetRuleEnd(rule, lastTransition);

                // Handle the final rule, which may or may not to extend to the end of time. If it doesn't,
                // we transition to standard time at the end of the rule.
                if (i == rules.Length - 1)
                {
                    // If the final transition was to standard time, we can just treat the seam as going on forever.
                    nextStart = standardIsLater ? Instant.MaxValue : ruleEnd;
                    var seam           = new ZoneInterval(seamName, lastTransition.Instant, nextStart, lastTransition.NewOffset, seamSavings);
                    var adjustmentZone = new DaylightSavingsDateTimeZone("ignored", standardOffset, standard.ToInfinity(), daylight.ToInfinity());
                    adjustmentIntervals.Add(new AdjustmentInterval(previousEnd, adjustmentZone, seam));
                    previousEnd = nextStart;
                }
                else
                {
                    // Handle one rule going into another. This is the cause of much pain, as there are several options:
                    // 1) Going into a "normal" rule with two transitions per year.
                    // 2) Going into a "single transition" rule, signified by a transition "into" the current offset
                    //    right at the start of the year. This is treated as if the on-the-edge transition doesn't exist.
                    //
                    // Additionally, there's the possibility that the offset at the start of the next rule (i.e. the
                    // one before the first transition) isn't the same as the offset at the end of end of the current rule
                    // (i.e. lastTransition.NewOffset). This only occurs for Namibia time as far as we've seen, but in that
                    // case we create a seam which only goes until the end of this rule, then an extra zone interval for
                    // the time between the start of the rule and the first transition, then we're as normal, starting at
                    // the first transition. See bug 115 for a bit more information.
                    var            nextRule = rules[i + 1];
                    ZoneRecurrence nextStandard, nextDaylight;
                    GetRecurrences(bclZone, nextRule, out nextStandard, out nextDaylight);

                    // By using the seam's savings for *both* checks, we can detect "daylight to daylight" and
                    // "standard to standard" transitions as happening at the very start of the rule.
                    var firstStandard = nextStandard.NextOrFail(lastTransition.Instant, standardOffset, seamSavings);
                    var firstDaylight = nextDaylight.NextOrFail(lastTransition.Instant, standardOffset, seamSavings);

                    // Ignore any "right at the start of the rule"  transitions.
                    var  firstStandardInstant   = firstStandard.Instant == ruleEnd ? Instant.MaxValue : firstStandard.Instant;
                    var  firstDaylightInstant   = firstDaylight.Instant == ruleEnd ? Instant.MaxValue : firstDaylight.Instant;
                    bool firstStandardIsEarlier = firstStandardInstant < firstDaylightInstant;
                    var  firstTransition        = firstStandardIsEarlier ? firstStandard : firstDaylight;
                    nextStart = firstTransition.Instant;
                    var seamEnd = nextStart;

                    AdjustmentInterval startOfRuleExtraSeam = null;

                    Offset previousOffset = firstStandardIsEarlier ? firstDaylight.NewOffset : firstStandard.NewOffset;
                    if (previousOffset != lastTransition.NewOffset)
                    {
                        seamEnd = ruleEnd;
                        // Recalculate the *real* transition, as we're now going from a different wall offset...
                        var firstRule = firstStandardIsEarlier ? nextStandard : nextDaylight;
                        nextStart = firstRule.NextOrFail(ruleEnd, standardOffset, previousOffset - standardOffset).Instant;
                        var extraSeam = new ZoneInterval(firstStandardIsEarlier ? bclZone.DaylightName : bclZone.StandardName,
                                                         ruleEnd, nextStart, previousOffset, previousOffset - standardOffset);
                        // The extra adjustment interval is really just a single zone interval; we'll never need the DaylightSavingsDateTimeZone part.
                        startOfRuleExtraSeam = new AdjustmentInterval(extraSeam.Start, null, extraSeam);
                    }

                    var seam           = new ZoneInterval(seamName, lastTransition.Instant, seamEnd, lastTransition.NewOffset, seamSavings);
                    var adjustmentZone = new DaylightSavingsDateTimeZone("ignored", standardOffset, standard.ToInfinity(), daylight.ToInfinity());
                    adjustmentIntervals.Add(new AdjustmentInterval(previousEnd, adjustmentZone, seam));

                    if (startOfRuleExtraSeam != null)
                    {
                        adjustmentIntervals.Add(startOfRuleExtraSeam);
                    }
                    previousEnd = nextStart;
                }
            }
            ZoneInterval tailInterval = previousEnd == Instant.MaxValue ? null
                : new ZoneInterval(bclZone.StandardName, previousEnd, Instant.MaxValue, standardOffset, Offset.Zero);

            IZoneIntervalMap uncachedMap = new BclZoneIntervalMap(adjustmentIntervals, headInterval, tailInterval);
            IZoneIntervalMap cachedMap   = CachingZoneIntervalMap.CacheMap(uncachedMap, CachingZoneIntervalMap.CacheType.Hashtable);

            return(new BclDateTimeZone(bclZone, standardOffset + minSavings, standardOffset + maxSavings, cachedMap));
        }