/// <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)); }
/// <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)); }
/// <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))); }
/// <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> /// 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)); }