protected DateTime?CalculateEnd(DateTime start, TimeSpan offset, SeekDirection seekDirection, SeekBoundaryMode seekBoundaryMode, out TimeSpan?remaining) { if (offset < TimeSpan.Zero) { throw new InvalidOperationException("time span must be positive"); } remaining = offset; // search periods TimePeriodCollection searchPeriods = new TimePeriodCollection(_includePeriods); // no search periods specified: search anytime if (searchPeriods.Count == 0) { searchPeriods.Add(TimeRange.Anytime); } // available periods ITimePeriodCollection availablePeriods = new TimePeriodCollection(); // no exclude periods specified: use all search periods if (_excludePeriods.Count == 0) { availablePeriods.AddAll(searchPeriods); } else // remove exclude periods { TimeGapCalculator <TimeRange> gapCalculator = new TimeGapCalculator <TimeRange>(); foreach (ITimePeriod searchPeriod in searchPeriods) { // no overlaps: use the entire search range if (!_excludePeriods.HasOverlapPeriods(searchPeriod)) { availablePeriods.Add(searchPeriod); } else // add gaps of search period using the exclude periods { availablePeriods.AddAll(gapCalculator.GetGaps(_excludePeriods, searchPeriod)); } } } // no periods available if (availablePeriods.Count == 0) { return(null); } // combine the available periods, ensure no overlapping // used for FindNextPeriod/FindPreviousPeriod if (availablePeriods.Count > 1) { TimePeriodCombiner <TimeRange> periodCombiner = new TimePeriodCombiner <TimeRange>(); availablePeriods = periodCombiner.CombinePeriods(availablePeriods); } // find the starting search period ITimePeriod startPeriod = null; DateTime seekMoment = start; switch (seekDirection) { case SeekDirection.Forward: startPeriod = FindNextPeriod(start, availablePeriods, out seekMoment); break; case SeekDirection.Backward: startPeriod = FindPreviousPeriod(start, availablePeriods, out seekMoment); break; } // no starting period available if (startPeriod == null) { return(null); } // no offset: use the search staring position // maybe moved to the next available search period if (offset == TimeSpan.Zero) { return(seekMoment); } // setup destination search switch (seekDirection) { case SeekDirection.Forward: for (int i = availablePeriods.IndexOf(startPeriod); i < availablePeriods.Count; i++) { ITimePeriod gap = availablePeriods[i]; TimeSpan gapRemining = gap.End - seekMoment; bool isTargetPeriod = false; switch (seekBoundaryMode) { case SeekBoundaryMode.Fill: isTargetPeriod = gapRemining >= remaining; break; case SeekBoundaryMode.Next: isTargetPeriod = gapRemining > remaining; break; } if (isTargetPeriod) { DateTime end = seekMoment + remaining.Value; remaining = null; return(end); } remaining = remaining - gapRemining; if (i == availablePeriods.Count - 1) { return(null); } seekMoment = availablePeriods[i + 1].Start; // next period } break; case SeekDirection.Backward: for (int i = availablePeriods.IndexOf(startPeriod); i >= 0; i--) { ITimePeriod gap = availablePeriods[i]; TimeSpan gapRemining = seekMoment - gap.Start; bool isTargetPeriod = false; switch (seekBoundaryMode) { case SeekBoundaryMode.Fill: isTargetPeriod = gapRemining >= remaining; break; case SeekBoundaryMode.Next: isTargetPeriod = gapRemining > remaining; break; } if (isTargetPeriod) { DateTime end = seekMoment - remaining.Value; remaining = null; return(end); } remaining = remaining - gapRemining; if (i == 0) { return(null); } seekMoment = availablePeriods[i - 1].End; // previous period } break; } return(null); }