Example #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, 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));
        }
Example #2
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));
        }
Example #3
0
 /// <summary>
 /// Returns a cached time zone for the given time zone.
 /// </summary>
 /// <remarks>
 /// If the time zone is already cached or it is fixed then it is returned unchanged.
 /// </remarks>
 /// <param name="timeZone">The time zone to cache.</param>
 /// <returns>The cached time zone.</returns>
 internal static DateTimeZone ForZone(DateTimeZone timeZone)
 {
     Preconditions.CheckNotNull(timeZone, nameof(timeZone));
     if (timeZone is CachedDateTimeZone || timeZone.IsFixed)
     {
         return(timeZone);
     }
     return(new CachedDateTimeZone(timeZone, CachingZoneIntervalMap.CacheMap(timeZone)));
 }
Example #4
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));
        }
Example #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([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));
        }