Esempio n. 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));
        }
Esempio n. 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));
        }
 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.
     Preconditions.DebugCheckArgument(start <= end, nameof(end),
                                      "Invalid start/end combination: {0} - {1}", start, end);
     this.Start = start;
     this.End   = end;
     this.map   = map;
 }
Esempio n. 5
0
 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");
 }
 /// <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;
            }
        }
Esempio n. 11
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);
                }
Esempio n. 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>
                /// 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);
                }
Esempio n. 14
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));
        }
Esempio n. 15
0
 private BclDateTimeZone(TimeZoneInfo bclZone, Offset minOffset, Offset maxOffset, IZoneIntervalMap map)
     : base(bclZone.Id, bclZone.SupportsDaylightSavingTime, minOffset, maxOffset)
 {
     this.OriginalZone = bclZone;
     this.map          = map;
 }
Esempio n. 16
0
 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));
        }
 private BclDateTimeZone(TimeZoneInfo bclZone, Offset minOffset, Offset maxOffset, IZoneIntervalMap map)
     : base(bclZone.Id, bclZone.SupportsDaylightSavingTime, minOffset, maxOffset)
 {
     this.OriginalZone = bclZone;
     this.map = map;
 }
Esempio n. 20
0
 /// <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;
 }
Esempio n. 21
0
 /// <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;
 }
 internal HashArrayCache([NotNull] IZoneIntervalMap map)
 {
     this.map = Preconditions.CheckNotNull(map, nameof(map));
     instantCache = new HashCacheNode[CacheSize];
 }
                /// <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;
                }