public void Extremes()
        {
            ZoneRecurrence winter = new ZoneRecurrence("Winter", Offset.Zero,
                                                       new ZoneYearOffset(TransitionMode.Wall, 10, 5, 0, false, new LocalTime(2, 0)), int.MinValue, int.MaxValue);

            ZoneRecurrence summer = new ZoneRecurrence("Summer", Offset.FromHours(1),
                                                       new ZoneYearOffset(TransitionMode.Wall, 3, 10, 0, false, new LocalTime(1, 0)), int.MinValue, int.MaxValue);

            var zone = new StandardDaylightAlternatingMap(Offset.Zero, winter, summer);

            var firstSpring = Instant.FromUtc(-9998, 3, 10, 1, 0);
            var firstAutumn = Instant.FromUtc(-9998, 10, 5, 1, 0); // 1am UTC = 2am wall

            var lastSpring = Instant.FromUtc(9999, 3, 10, 1, 0);
            var lastAutumn = Instant.FromUtc(9999, 10, 5, 1, 0); // 1am UTC = 2am wall

            var dstOffset = Offset.FromHours(1);

            // Check both year -9998 and 9999, both the infinite interval and the next one in
            var firstWinter = new ZoneInterval("Winter", Instant.BeforeMinValue, firstSpring, Offset.Zero, Offset.Zero);
            var firstSummer = new ZoneInterval("Summer", firstSpring, firstAutumn, dstOffset, dstOffset);
            var lastSummer  = new ZoneInterval("Summer", lastSpring, lastAutumn, dstOffset, dstOffset);
            var lastWinter  = new ZoneInterval("Winter", lastAutumn, Instant.AfterMaxValue, Offset.Zero, Offset.Zero);

            Assert.AreEqual(firstWinter, zone.GetZoneInterval(Instant.MinValue));
            Assert.AreEqual(firstWinter, zone.GetZoneInterval(Instant.FromUtc(-9998, 2, 1, 0, 0)));
            Assert.AreEqual(firstSummer, zone.GetZoneInterval(firstSpring));
            Assert.AreEqual(firstSummer, zone.GetZoneInterval(Instant.FromUtc(-9998, 5, 1, 0, 0)));

            Assert.AreEqual(lastSummer, zone.GetZoneInterval(lastSpring));
            Assert.AreEqual(lastSummer, zone.GetZoneInterval(Instant.FromUtc(9999, 5, 1, 0, 0)));
            Assert.AreEqual(lastWinter, zone.GetZoneInterval(lastAutumn));
            Assert.AreEqual(lastWinter, zone.GetZoneInterval(Instant.FromUtc(9999, 11, 1, 0, 0)));
            Assert.AreEqual(lastWinter, zone.GetZoneInterval(Instant.MaxValue));
        }
 static StandardDaylightAlternatingMapBenchmarks()
 {
     // Build a daylight savings zone which basically models modern America/Los_Angeles.
     SampleZone = new StandardDaylightAlternatingMap(Offset.FromHours(-8),
         new ZoneRecurrence("PDT", Offset.FromHours(1),
             new ZoneYearOffset(TransitionMode.Wall, monthOfYear: 3, dayOfMonth: 8, dayOfWeek: 7,
                                advance: true, timeOfDay: new LocalTime(2, 0)),
             int.MinValue, int.MaxValue),
         new ZoneRecurrence("PST", Offset.FromHours(0),
             new ZoneYearOffset(TransitionMode.Standard, monthOfYear: 11, dayOfMonth: 1, dayOfWeek: 7,
                                advance: true, timeOfDay: new LocalTime(2, 0)),
             int.MinValue, int.MaxValue));
 }
Exemplo n.º 3
0
 static StandardDaylightAlternatingMapBenchmarks()
 {
     // Build a daylight savings zone which basically models modern America/Los_Angeles.
     SampleZone = new StandardDaylightAlternatingMap(Offset.FromHours(-8),
                                                     new ZoneRecurrence("PDT", Offset.FromHours(1),
                                                                        new ZoneYearOffset(TransitionMode.Wall, monthOfYear: 3, dayOfMonth: 8, dayOfWeek: 7,
                                                                                           advance: true, timeOfDay: new LocalTime(2, 0)),
                                                                        int.MinValue, int.MaxValue),
                                                     new ZoneRecurrence("PST", Offset.FromHours(0),
                                                                        new ZoneYearOffset(TransitionMode.Standard, monthOfYear: 11, dayOfMonth: 1, dayOfWeek: 7,
                                                                                           advance: true, timeOfDay: new LocalTime(2, 0)),
                                                                        int.MinValue, int.MaxValue));
 }
        public void InvalidMap_SimultaneousTransition()
        {
            // Two recurrences with different savings, but which occur at the same instant in time every year.
            ZoneRecurrence r1 = new ZoneRecurrence("Recurrence1", Offset.Zero,
                                                   new ZoneYearOffset(TransitionMode.Utc, 10, 5, 0, false, new LocalTime(2, 0)), int.MinValue, int.MaxValue);

            ZoneRecurrence r2 = new ZoneRecurrence("Recurrence2", Offset.FromHours(1),
                                                   new ZoneYearOffset(TransitionMode.Utc, 10, 5, 0, false, new LocalTime(2, 0)), int.MinValue, int.MaxValue);

            var map = new StandardDaylightAlternatingMap(Offset.Zero, r1, r2);

            Assert.Throws <InvalidOperationException>(() => map.GetZoneInterval(Instant.FromUtc(2017, 8, 25, 0, 0, 0)));
        }
        public void ReadWrite()
        {
            var map1   = new StandardDaylightAlternatingMap(Offset.FromHours(1), Summer, Winter);
            var stream = new MemoryStream();
            var writer = new DateTimeZoneWriter(stream, null);

            map1.Write(writer);
            stream.Position = 0;

            var reader = new DateTimeZoneReader(stream, null);
            var map2   = StandardDaylightAlternatingMap.Read(reader);

            Assert.AreEqual(map1, map2);
        }
        public void Equality()
        {
            // Order of recurrences doesn't matter
            var map1 = new StandardDaylightAlternatingMap(Offset.FromHours(1), Summer, Winter);
            var map2 = new StandardDaylightAlternatingMap(Offset.FromHours(1), Winter, Summer);
            var map3 = new StandardDaylightAlternatingMap(Offset.FromHours(1), Winter,
                                                          // Summer, but starting from 1900
                                                          new ZoneRecurrence("Summer", Offset.FromHours(1),
                                                                             new ZoneYearOffset(TransitionMode.Wall, 3, 10, 0, false, new LocalTime(1, 0)), 1900, int.MaxValue));
            // Standard offset does matter
            var map4 = new StandardDaylightAlternatingMap(Offset.FromHours(0), Summer, Winter);

            TestHelper.TestEqualsClass(map1, map2, map4);
            TestHelper.TestEqualsClass(map1, map3, map4);

            // Recurrences like Summer, but different in one aspect each, *except*
            var unequalMaps = new[]
            {
                new ZoneRecurrence("Different name", Offset.FromHours(1),
                                   new ZoneYearOffset(TransitionMode.Wall, 3, 10, 0, false, new LocalTime(1, 0)), 2000, int.MaxValue),
                new ZoneRecurrence("Summer", Offset.FromHours(2),
                                   new ZoneYearOffset(TransitionMode.Wall, 3, 10, 0, false, new LocalTime(1, 0)), 2000, int.MaxValue),
                new ZoneRecurrence("Summer", Offset.FromHours(1),
                                   new ZoneYearOffset(TransitionMode.Standard, 3, 10, 0, false, new LocalTime(1, 0)), 2000, int.MaxValue),
                new ZoneRecurrence("Summer", Offset.FromHours(1),
                                   new ZoneYearOffset(TransitionMode.Wall, 4, 10, 0, false, new LocalTime(1, 0)), 2000, int.MaxValue),
                new ZoneRecurrence("Summer", Offset.FromHours(1),
                                   new ZoneYearOffset(TransitionMode.Wall, 3, 9, 0, false, new LocalTime(1, 0)), 2000, int.MaxValue),
                new ZoneRecurrence("Summer", Offset.FromHours(1),
                                   new ZoneYearOffset(TransitionMode.Wall, 3, 10, 1, false, new LocalTime(1, 0)), 2000, int.MaxValue),
                // Advance with day-of-week 0 doesn't make any real difference, but they compare non-equal...
                new ZoneRecurrence("Summer", Offset.FromHours(1),
                                   new ZoneYearOffset(TransitionMode.Wall, 3, 10, 0, true, new LocalTime(1, 0)), 2000, int.MaxValue),
                new ZoneRecurrence("Summer", Offset.FromHours(1),
                                   new ZoneYearOffset(TransitionMode.Wall, 3, 10, 0, false, new LocalTime(2, 0)), 2000, int.MaxValue)
            }.Select(recurrence => new StandardDaylightAlternatingMap(Offset.FromHours(1), Winter, recurrence)).ToArray();

            TestHelper.TestEqualsClass(map1, map2, unequalMaps);
        }
        public void Extremes()
        {
            ZoneRecurrence winter = new ZoneRecurrence("Winter", Offset.Zero,
                new ZoneYearOffset(TransitionMode.Wall, 10, 5, 0, false, new LocalTime(2, 0)), int.MinValue, int.MaxValue);

            ZoneRecurrence summer = new ZoneRecurrence("Summer", Offset.FromHours(1),
                new ZoneYearOffset(TransitionMode.Wall, 3, 10, 0, false, new LocalTime(1, 0)), int.MinValue, int.MaxValue);

            var zone = new StandardDaylightAlternatingMap(Offset.Zero, winter, summer);

            var firstSpring = Instant.FromUtc(-9998, 3, 10, 1, 0);
            var firstAutumn = Instant.FromUtc(-9998, 10, 5, 1, 0); // 1am UTC = 2am wall

            var lastSpring = Instant.FromUtc(9999, 3, 10, 1, 0);
            var lastAutumn = Instant.FromUtc(9999, 10, 5, 1, 0); // 1am UTC = 2am wall

            var dstOffset = Offset.FromHours(1);

            // Check both year -9998 and 9999, both the infinite interval and the next one in
            var firstWinter = new ZoneInterval("Winter", Instant.BeforeMinValue, firstSpring, Offset.Zero, Offset.Zero);
            var firstSummer = new ZoneInterval("Summer", firstSpring, firstAutumn, dstOffset, dstOffset);
            var lastSummer = new ZoneInterval("Summer", lastSpring, lastAutumn, dstOffset, dstOffset);
            var lastWinter = new ZoneInterval("Winter", lastAutumn, Instant.AfterMaxValue, Offset.Zero, Offset.Zero);

            Assert.AreEqual(firstWinter, zone.GetZoneInterval(Instant.MinValue));
            Assert.AreEqual(firstWinter, zone.GetZoneInterval(Instant.FromUtc(-9998, 2, 1, 0, 0)));
            Assert.AreEqual(firstSummer, zone.GetZoneInterval(firstSpring));
            Assert.AreEqual(firstSummer, zone.GetZoneInterval(Instant.FromUtc(-9998, 5, 1, 0, 0)));

            Assert.AreEqual(lastSummer, zone.GetZoneInterval(lastSpring));
            Assert.AreEqual(lastSummer, zone.GetZoneInterval(Instant.FromUtc(9999, 5, 1, 0, 0)));
            Assert.AreEqual(lastWinter, zone.GetZoneInterval(lastAutumn));
            Assert.AreEqual(lastWinter, zone.GetZoneInterval(Instant.FromUtc(9999, 11, 1, 0, 0)));
            Assert.AreEqual(lastWinter, zone.GetZoneInterval(Instant.MaxValue));
        }
Exemplo n.º 8
0
        /// <summary>
        /// Adds the intervals from the given rule set to the end of the zone
        /// being built. The rule is deemed to take effect from the end of the previous
        /// zone interval, or the start of time if this is the first rule set (which must
        /// be a fixed one). Intervals are added until the rule set expires, or
        /// until we determine that the rule set continues to the end of time,
        /// possibly with a tail zone - a pair of standard/daylight rules which repeat
        /// forever.
        /// </summary>
        private void AddIntervals(ZoneRuleSet ruleSet)
        {
            // We use the last zone interval computed so far (if there is one) to work out where to start.
            var lastZoneInterval = zoneIntervals.LastOrDefault();
            var start = lastZoneInterval?.End ?? Instant.BeforeMinValue;

            // Simple case: a zone line with fixed savings (or - for 0)
            // instead of a rule name. Just a single interval.
            if (ruleSet.IsFixed)
            {
                zoneIntervals.Add(ruleSet.CreateFixedInterval(start));
                return;
            }

            // Work on a copy of the rule set. We eliminate rules from it as they expire,
            // so that we can tell when we're down to an infinite pair which can be represented
            // as a tail zone.
            var activeRules = new List<ZoneRecurrence>(ruleSet.Rules);

            // Surprisingly tricky bit to work out: how to handle the transition from
            // one rule set to another. We know the instant at which the new rule set
            // come in, but not what offsets/name to use from that point onwards: which
            // of the new rules is in force. We find out which rule would have taken
            // effect most recently before or on the transition instant - but using
            // the offsets from the final interval before the transition, instead
            // of the offsets which would have been in force if the new rule set were
            // actually extended backwards forever.
            //
            // It's possible that the most recent transition we find would actually
            // have started before that final interval anyway - but this appears to
            // match what zic produces.
            //
            // If we don't have a zone interval at all, we're starting at the start of
            // time, so there definitely aren't any preceding rules.
            var firstRule = lastZoneInterval == null ? null :
                activeRules
                    .Select(rule => new { rule, prev = rule.PreviousOrSame(start, lastZoneInterval.StandardOffset, lastZoneInterval.Savings) })
                    .Where(pair => pair.prev != null)
                    .OrderBy(pair => pair.prev.Value.Instant)
                    .Select(pair => pair.rule)
                    .LastOrDefault();

            // Every transition in this rule set will use the same standard offset.
            var standardOffset = ruleSet.StandardOffset;

            // previousTransition here is ongoing as we loop through the transitions. It's not like
            // lastZoneInterval, lastStandard and lastSavings, which refer to the last aspects of the
            // previous rule set. When we set it up, this is effectively the *first* transition leading
            // into the period in which the new rule set is 
            ZoneTransition previousTransition;
            if (firstRule != null)
            {
                previousTransition = new ZoneTransition(start, firstRule.Name, standardOffset, firstRule.Savings);
            }
            else
            {
                // None of the rules in the current set have *any* transitions in the past, apparently.
                // For an example of this, see Europe/Prague (in 2015e, anyway). A zone line with the
                // Czech rule takes effect in 1944, but all the rules are from 1945 onwards.
                // Use standard time until the first transition, regardless of the previous savings,
                // and take the name for this first interval from the first standard time rule.
                var name = activeRules.First(rule => rule.Savings == Offset.Zero).Name;
                previousTransition = new ZoneTransition(start, name, standardOffset, Offset.Zero);
            }

            // Main loop - we keep going round until we run out of rules or hit infinity, each of which
            // corresponds with a return statement in the loop.
            while (true)
            {
                ZoneTransition bestTransition = null;
                for (int i = 0; i < activeRules.Count; i++)
                {
                    var rule = activeRules[i];
                    var nextTransition = rule.Next(previousTransition.Instant, standardOffset, previousTransition.Savings);
                    // Once a rule is no longer active, remove it from the list. That way we can tell
                    // when we can create a tail zone.
                    if (nextTransition == null)
                    {
                        activeRules.RemoveAt(i);
                        i--;
                        continue;
                    }
                    var zoneTransition = new ZoneTransition(nextTransition.Value.Instant, rule.Name, standardOffset, rule.Savings);
                    if (!zoneTransition.IsTransitionFrom(previousTransition))
                    {
                        continue;
                    }
                    if (bestTransition == null || zoneTransition.Instant <= bestTransition.Instant)
                    {
                        bestTransition = zoneTransition;
                    }
                }
                Instant currentUpperBound = ruleSet.GetUpperLimit(previousTransition.Savings);
                if (bestTransition == null || bestTransition.Instant >= currentUpperBound)
                {
                    // No more transitions to find. (We may have run out of rules, or they may be beyond where this rule set expires.)
                    // Add a final interval leading up to the upper bound of the rule set, unless the previous transition took us up to
                    // this current bound anyway.
                    // (This is very rare, but can happen if changing rule brings the upper bound down to the time
                    // that the transition occurred. Example: 2008d, Europe/Sofia, April 1945.)
                    if (currentUpperBound > previousTransition.Instant)
                    {
                        zoneIntervals.Add(previousTransition.ToZoneInterval(currentUpperBound));
                    }
                    return;
                }

                // We have a non-final transition. so add an interval from the previous transition to
                // this one.
                zoneIntervals.Add(previousTransition.ToZoneInterval(bestTransition.Instant));
                previousTransition = bestTransition;

                // Tail zone handling.
                // The final rule set must extend to infinity. There are potentially three ways
                // this can happen:
                // - All rules expire, leaving us with the final real transition, and an upper
                //   bound of infinity. This is handled above.
                // - 1 rule is left, but it cannot create more than one transition in a row,
                //   so again we end up with no transitions to record, and we bail out with
                //   a final infinite interval.
                // - 2 rules are left which would alternate infinitely. This is represented
                //   using a DaylightSavingZone as the tail zone.
                // 
                // The code here caters for that last option, but needs to do it in stages.
                // When we first realize we will have a tail zone (an infinite rule set,
                // two rules left, both of which are themselves infinite) we can create the
                // tail zone, but we don't yet know that we're into its regular tick/tock.
                // It's possible that one rule only starts years after our current transition,
                // so we need to hit the first transition of that rule before we can create a
                // "seam" from the list of precomputed zone intervals to the calculated-on-demand 
                // part of history.
                // For an example of why this is necessary, see Asia/Amman in 2013e: in late 2011
                // we hit "two rules left" but the final rule only starts in 2013 - we don't want
                // to see a bogus transition into that rule in 2012.
                // We could potentially record fewer zone intervals by keeping track of which
                // rules have created at least one transition, but this approach is simpler.
                if (ruleSet.IsInfinite && activeRules.Count == 2)
                {
                    if (tailZone != null)
                    {
                        // Phase two: both rules must now be active, so we're done.
                        return;
                    }
                    ZoneRecurrence startRule = activeRules[0];
                    ZoneRecurrence endRule = activeRules[1];
                    if (startRule.IsInfinite && endRule.IsInfinite)
                    {
                        // Phase one: build the zone, so we can go round once again and then return.
                        tailZone = new StandardDaylightAlternatingMap(standardOffset, startRule, endRule);
                    }
                }
            }
        }
Exemplo n.º 9
0
        /// <summary>
        /// Adds the intervals from the given rule set to the end of the zone
        /// being built. The rule is deemed to take effect from the end of the previous
        /// zone interval, or the start of time if this is the first rule set (which must
        /// be a fixed one). Intervals are added until the rule set expires, or
        /// until we determine that the rule set continues to the end of time,
        /// possibly with a tail zone - a pair of standard/daylight rules which repeat
        /// forever.
        /// </summary>
        private void AddIntervals(ZoneRuleSet ruleSet)
        {
            // We use the last zone interval computed so far (if there is one) to work out where to start.
            var lastZoneInterval = zoneIntervals.LastOrDefault();
            var start            = lastZoneInterval?.End ?? Instant.BeforeMinValue;

            // Simple case: a zone line with fixed savings (or - for 0)
            // instead of a rule name. Just a single interval.
            if (ruleSet.IsFixed)
            {
                zoneIntervals.Add(ruleSet.CreateFixedInterval(start));
                return;
            }

            // Work on a copy of the rule set. We eliminate rules from it as they expire,
            // so that we can tell when we're down to an infinite pair which can be represented
            // as a tail zone.
            var activeRules = new List <ZoneRecurrence>(ruleSet.Rules);

            // Surprisingly tricky bit to work out: how to handle the transition from
            // one rule set to another. We know the instant at which the new rule set
            // come in, but not what offsets/name to use from that point onwards: which
            // of the new rules is in force. We find out which rule would have taken
            // effect most recently before or on the transition instant - but using
            // the offsets from the final interval before the transition, instead
            // of the offsets which would have been in force if the new rule set were
            // actually extended backwards forever.
            //
            // It's possible that the most recent transition we find would actually
            // have started before that final interval anyway - but this appears to
            // match what zic produces.
            //
            // If we don't have a zone interval at all, we're starting at the start of
            // time, so there definitely aren't any preceding rules.
            var firstRule = lastZoneInterval == null ? null :
                            activeRules
                            .Select(rule => new { rule, prev = rule.PreviousOrSame(start, lastZoneInterval.StandardOffset, lastZoneInterval.Savings) })
                            .Where(pair => pair.prev != null)
                            .OrderBy(pair => pair.prev.Value.Instant)
                            .Select(pair => pair.rule)
                            .LastOrDefault();

            // Every transition in this rule set will use the same standard offset.
            var standardOffset = ruleSet.StandardOffset;

            // previousTransition here is ongoing as we loop through the transitions. It's not like
            // lastZoneInterval, lastStandard and lastSavings, which refer to the last aspects of the
            // previous rule set. When we set it up, this is effectively the *first* transition leading
            // into the period in which the new rule set is
            ZoneTransition previousTransition;

            if (firstRule != null)
            {
                previousTransition = new ZoneTransition(start, firstRule.Name, standardOffset, firstRule.Savings);
            }
            else
            {
                // None of the rules in the current set have *any* transitions in the past, apparently.
                // For an example of this, see Europe/Prague (in 2015e, anyway). A zone line with the
                // Czech rule takes effect in 1944, but all the rules are from 1945 onwards.
                // Use standard time until the first transition, regardless of the previous savings,
                // and take the name for this first interval from the first standard time rule.
                var name = activeRules.First(rule => rule.Savings == Offset.Zero).Name;
                previousTransition = new ZoneTransition(start, name, standardOffset, Offset.Zero);
            }

            // Main loop - we keep going round until we run out of rules or hit infinity, each of which
            // corresponds with a return statement in the loop.
            while (true)
            {
                ZoneTransition bestTransition = null;
                for (int i = 0; i < activeRules.Count; i++)
                {
                    var rule           = activeRules[i];
                    var nextTransition = rule.Next(previousTransition.Instant, standardOffset, previousTransition.Savings);
                    // Once a rule is no longer active, remove it from the list. That way we can tell
                    // when we can create a tail zone.
                    if (nextTransition == null)
                    {
                        activeRules.RemoveAt(i);
                        i--;
                        continue;
                    }
                    var zoneTransition = new ZoneTransition(nextTransition.Value.Instant, rule.Name, standardOffset, rule.Savings);
                    if (!zoneTransition.IsTransitionFrom(previousTransition))
                    {
                        continue;
                    }
                    if (bestTransition == null || zoneTransition.Instant <= bestTransition.Instant)
                    {
                        bestTransition = zoneTransition;
                    }
                }
                Instant currentUpperBound = ruleSet.GetUpperLimit(previousTransition.Savings);
                if (bestTransition == null || bestTransition.Instant >= currentUpperBound)
                {
                    // No more transitions to find. (We may have run out of rules, or they may be beyond where this rule set expires.)
                    // Add a final interval leading up to the upper bound of the rule set, unless the previous transition took us up to
                    // this current bound anyway.
                    // (This is very rare, but can happen if changing rule brings the upper bound down to the time
                    // that the transition occurred. Example: 2008d, Europe/Sofia, April 1945.)
                    if (currentUpperBound > previousTransition.Instant)
                    {
                        zoneIntervals.Add(previousTransition.ToZoneInterval(currentUpperBound));
                    }
                    return;
                }

                // We have a non-final transition. so add an interval from the previous transition to
                // this one.
                zoneIntervals.Add(previousTransition.ToZoneInterval(bestTransition.Instant));
                previousTransition = bestTransition;

                // Tail zone handling.
                // The final rule set must extend to infinity. There are potentially three ways
                // this can happen:
                // - All rules expire, leaving us with the final real transition, and an upper
                //   bound of infinity. This is handled above.
                // - 1 rule is left, but it cannot create more than one transition in a row,
                //   so again we end up with no transitions to record, and we bail out with
                //   a final infinite interval.
                // - 2 rules are left which would alternate infinitely. This is represented
                //   using a DaylightSavingZone as the tail zone.
                //
                // The code here caters for that last option, but needs to do it in stages.
                // When we first realize we will have a tail zone (an infinite rule set,
                // two rules left, both of which are themselves infinite) we can create the
                // tail zone, but we don't yet know that we're into its regular tick/tock.
                // It's possible that one rule only starts years after our current transition,
                // so we need to hit the first transition of that rule before we can create a
                // "seam" from the list of precomputed zone intervals to the calculated-on-demand
                // part of history.
                // For an example of why this is necessary, see Asia/Amman in 2013e: in late 2011
                // we hit "two rules left" but the final rule only starts in 2013 - we don't want
                // to see a bogus transition into that rule in 2012.
                // We could potentially record fewer zone intervals by keeping track of which
                // rules have created at least one transition, but this approach is simpler.
                if (ruleSet.IsInfinite && activeRules.Count == 2)
                {
                    if (tailZone != null)
                    {
                        // Phase two: both rules must now be active, so we're done.
                        return;
                    }
                    ZoneRecurrence startRule = activeRules[0];
                    ZoneRecurrence endRule   = activeRules[1];
                    if (startRule.IsInfinite && endRule.IsInfinite)
                    {
                        // Phase one: build the zone, so we can go round once again and then return.
                        tailZone = new StandardDaylightAlternatingMap(standardOffset, startRule, endRule);
                    }
                }
            }
        }
Exemplo n.º 10
0
            internal BclAdjustmentRule(TimeZoneInfo zone, TimeZoneInfo.AdjustmentRule rule)
            {
                // With .NET 4.6, adjustment rules can have their own standard offsets, allowing
                // a much more reasonable set of time zone data. Unfortunately, this isn't directly
                // exposed, but we can detect it by just finding the UTC offset for an arbitrary
                // time within the rule - the start, in this case - and then take account of the
                // possibility of that being in daylight saving time. Fortunately, we only need
                // to do this during the setup.
                var ruleStandardOffset = zone.GetUtcOffset(rule.DateStart);
                if (zone.IsDaylightSavingTime(rule.DateStart))
                {
                    ruleStandardOffset -= rule.DaylightDelta;
                }
                StandardOffset = ruleStandardOffset.ToOffset();

                // Although the rule may have its own standard offset, the start/end is still determined
                // using the zone's standard offset.
                var zoneStandardOffset = zone.BaseUtcOffset.ToOffset();

                // Note: this extends back from DateTime.MinValue to start of time, even though the BCL can represent
                // as far back as 1AD. This is in the *spirit* of a rule which goes back that far.
                Start = rule.DateStart == DateTime.MinValue ? Instant.BeforeMinValue : rule.DateStart.ToLocalDateTime().WithOffset(zoneStandardOffset).ToInstant();
                // The end instant (exclusive) is the end of the given date, so we need to add a day.
                End = rule.DateEnd == MaxDate ? Instant.AfterMaxValue : rule.DateEnd.ToLocalDateTime().PlusDays(1).WithOffset(zoneStandardOffset).ToInstant();
                Savings = rule.DaylightDelta.ToOffset();

                // Some rules have DST start/end of "January 1st", to indicate that they're just in standard time. This is important
                // for rules which have a standard offset which is different to the standard offset of the zone itself.
                if (IsStandardOffsetOnlyRule(rule))
                {
                    PartialMap = PartialZoneIntervalMap.ForZoneInterval(zone.StandardName, Start, End, StandardOffset, Offset.Zero);
                }
                else
                {
                    var daylightRecurrence = new ZoneRecurrence(zone.DaylightName, Savings, ConvertTransition(rule.DaylightTransitionStart), int.MinValue, int.MaxValue);
                    var standardRecurrence = new ZoneRecurrence(zone.StandardName, Offset.Zero, ConvertTransition(rule.DaylightTransitionEnd), int.MinValue, int.MaxValue);
                    var recurringMap = new StandardDaylightAlternatingMap(StandardOffset, standardRecurrence, daylightRecurrence);
                    PartialMap = new PartialZoneIntervalMap(Start, End, recurringMap);
                }
            }