/// <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)); }
internal PartialZoneIntervalMap(Instant start, Instant end, IZoneIntervalMap map) { // Allowing empty maps makes life simpler. Preconditions.DebugCheckArgument(start <= end, nameof(end), "Invalid start/end combination: {0} - {1}", start, end); this.Start = start; this.End = end; this.map = map; }
internal PartialZoneIntervalMap(Instant start, Instant end, IZoneIntervalMap map) { // Allowing empty maps makes life simpler. // TODO(misc): Does it really? It's a pain in some places... Preconditions.DebugCheckArgument(start <= end, nameof(end), "Invalid start/end combination: {0} - {1}", start, end); this.Start = start; this.End = end; this.map = map; }
/// <summary> /// Returns a caching map for the given input map. /// </summary> internal static IZoneIntervalMap CacheMap([NotNull] IZoneIntervalMap map, CacheType type) { switch (type) { case CacheType.Hashtable: return(new HashArrayCache(map)); default: throw new ArgumentException("The type parameter is invalid", "type"); } }
// TODO: Consider making this part of the NodaTime assembly. // It's just a copy from DateTimeZone, with the interval taken out. // It could be an extension method on IZoneIntervalMap, with optional interval. // On the other hand, IZoneIntervalMap is internal, so it would only be used by us. private static IEnumerable<ZoneInterval> GetZoneIntervals(IZoneIntervalMap map) { var current = Instant.MinValue; while (current < Instant.AfterMaxValue) { var zoneInterval = map.GetZoneInterval(current); yield return zoneInterval; // If this is the end of time, this will just fail on the next comparison. current = zoneInterval.RawEnd; } }
/// <summary> /// Validates that all the periods before the tail zone make sense. We have to start at the beginning of time, /// and then have adjoining periods. This is only called in the constructors. /// </summary> /// <remarks>This is only called from the constructors, but is internal to make it easier to test.</remarks> /// <exception cref="ArgumentException">The periods specified are invalid.</exception> internal static void ValidatePeriods(ZoneInterval[] periods, IZoneIntervalMap tailZone) { Preconditions.CheckArgument(periods.Length > 0, nameof(periods), "No periods specified in precalculated time zone"); Preconditions.CheckArgument(!periods[0].HasStart, nameof(periods), "Periods in precalculated time zone must start with the beginning of time"); for (int i = 0; i < periods.Length - 1; i++) { // Safe to use End here: there can't be a period *after* an endless one. Likewise it's safe to use Start on the next // period, as there can't be a period *before* one which goes back to the start of time. Preconditions.CheckArgument(periods[i].End == periods[i + 1].Start, nameof(periods), "Non-adjoining ZoneIntervals for precalculated time zone"); } Preconditions.CheckArgument(tailZone != null || periods[periods.Length - 1].RawEnd == Instant.AfterMaxValue, nameof(tailZone), "Null tail zone given but periods don't cover all of time"); }
// TODO: Consider making this part of the NodaTime assembly. // It's just a copy from DateTimeZone, with the interval taken out. // It could be an extension method on IZoneIntervalMap, with optional interval. // On the other hand, IZoneIntervalMap is internal, so it would only be used by us. private static IEnumerable <ZoneInterval> GetZoneIntervals(IZoneIntervalMap map) { var current = Instant.MinValue; while (current < Instant.AfterMaxValue) { var zoneInterval = map.GetZoneInterval(current); yield return(zoneInterval); // If this is the end of time, this will just fail on the next comparison. current = zoneInterval.RawEnd; } }
/// <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> /// 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 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 days = period << PeriodShift; var periodStart = new Instant(new Duration(Math.Max(days, Instant.MinDays), 0L)); var nextPeriodStartDays = days + (1 << PeriodShift); var interval = map.GetZoneInterval(periodStart); var node = new HashCacheNode(interval, period, null); // Keep going while the current interval ends before the period. // (We only need to check the days, as every period lands on a // day boundary.) // If the raw end is the end of time, the condition will definitely // evaluate to false. while (interval.RawEnd.DaysSinceEpoch < nextPeriodStartDays) { interval = map.GetZoneInterval(interval.End); node = new HashCacheNode(interval, period, node); } return(node); }
/// <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)); }
private BclDateTimeZone(TimeZoneInfo bclZone, Offset minOffset, Offset maxOffset, IZoneIntervalMap map) : base(bclZone.Id, bclZone.SupportsDaylightSavingTime, minOffset, maxOffset) { this.OriginalZone = bclZone; this.map = map; }
internal DaylightFakingZoneIntervalMap(IZoneIntervalMap originalMap, string daylightName) { this.originalMap = originalMap; this.daylightName = daylightName; }
internal HashArrayCache([NotNull] IZoneIntervalMap map) { this.map = Preconditions.CheckNotNull(map, nameof(map)); instantCache = new HashCacheNode[CacheSize]; }
// Currently the only implementation is HashArrayCache. This container class is mostly for historical // reasons; it's not really necessary but it does no harm. /// <summary> /// Returns a caching map for the given input map. /// </summary> internal static IZoneIntervalMap CacheMap(IZoneIntervalMap map) { return(new HashArrayCache(map)); }
/// <summary> /// Initializes a new instance of the <see cref="CachedDateTimeZone"/> class. /// </summary> /// <param name="timeZone">The time zone to cache.</param> /// <param name="map">The caching map</param> private CachedDateTimeZone(DateTimeZone timeZone, IZoneIntervalMap map) : base(timeZone.Id, false, timeZone.MinOffset, timeZone.MaxOffset) { this.TimeZone = timeZone; this.map = map; }
/// <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 days = period << PeriodShift; var periodStart = new Instant(new Duration(Math.Max(days, Instant.MinDays), 0L)); var nextPeriodStartDays = days + (1 << PeriodShift); var interval = map.GetZoneInterval(periodStart); var node = new HashCacheNode(interval, period, null); // Keep going while the current interval ends before the period. // (We only need to check the days, as every period lands on a // day boundary.) // If the raw end is the end of time, the condition will definitely // evaluate to false. while (interval.RawEnd.DaysSinceEpoch < nextPeriodStartDays) { interval = map.GetZoneInterval(interval.End); node = new HashCacheNode(interval, period, node); } return node; }