protected DateTime? CalculateEnd(DateTime start, TimeSpan offset, SeekDirection seekDirection, SeekBoundaryMode seekBoundaryMode,
out TimeSpan? remaining) {
if(IsDebugEnabled)
log.Debug("기준시각으로부터 오프셋만큼 떨어진 시각을 구합니다... " +
@"start=[{0}], offset=[{1}], seekDirection=[{2}], seekBoundaryMode=[{3}]",
start, offset, seekDirection, seekBoundaryMode);
Guard.Assert(offset >= TimeSpan.Zero, "offset 값은 항상 0 이상이어야 합니다. offset=[{0}]", offset);
remaining = offset;
// search periods
ITimePeriodCollection searchPeriods = new TimePeriodCollection(IncludePeriods);
if(searchPeriods.Count == 0)
searchPeriods.Add(TimeRange.Anytime);
// available periods
ITimePeriodCollection availablePeriods = new TimePeriodCollection();
if(ExcludePeriods.Count == 0) {
availablePeriods.AddAll(searchPeriods);
}
else {
// 예외 기간을 제외합니다.
//
var gapCalculator = new TimeGapCalculator<TimeRange>();
var query
= searchPeriods
#if !SILVERLIGHT
.AsParallel()
.AsOrdered()
#endif
.SelectMany(searchPeriod =>
ExcludePeriods.HasOverlapPeriods(searchPeriod)
? gapCalculator.GetGaps(ExcludePeriods, searchPeriod) // 예외 기간과 검색 기간에서 겹치지 않는 기간을 계산하여, 추려냅니다.
: new TimePeriodCollection { searchPeriod } // 예외 기간과 겹쳐진 부분이 없다면, 기간 전체를 추가합니다.
);
availablePeriods.AddAll(query);
}
// 유효한 Period가 없다면 중단합니다.
if(availablePeriods.Count == 0)
return null;
// 기간중에 중복되는 부분의 없도록 유효한 기간을 결합합니다.
if(availablePeriods.Count > 1) {
var periodCombiner = new TimePeriodCombiner<TimeRange>();
availablePeriods = periodCombiner.CombinePeriods(availablePeriods);
}
// 첫 시작 기간을 찾습니다.
//
DateTime seekMoment;
var startPeriod = (seekDirection == SeekDirection.Forward)
? FindNextPeriod(start, availablePeriods, out seekMoment)
: FindPreviousPeriod(start, availablePeriods, out seekMoment);
// 첫 시작 기간이 없다면 중단합니다.
if(startPeriod == null)
return null;
// 오프셋 값이 0 이라면, 바로 다음 값이므로 seekMoment를 반환합니다.
if(offset == TimeSpan.Zero)
return seekMoment;
if(seekDirection == SeekDirection.Forward) {
for(var i = availablePeriods.IndexOf(startPeriod); i < availablePeriods.Count; i++) {
var gap = availablePeriods[i];
var gapRemaining = gap.End - seekMoment;
if(IsDebugEnabled)
log.Debug("Seek Forward... gap=[{0}], gapRemaining=[{1}], remaining=[{2}], seekMoment=[{3}]", gap, gapRemaining,
remaining, seekMoment);
var isTargetPeriod = (seekBoundaryMode == SeekBoundaryMode.Fill)
? gapRemaining >= remaining
: gapRemaining > remaining;
if(isTargetPeriod) {
var end = seekMoment + remaining.Value;
remaining = null;
return end;
}
remaining = remaining - gapRemaining;
if(i == availablePeriods.Count - 1)
return null;
seekMoment = availablePeriods[i + 1].Start; // next period
}
}
else {
for(var i = availablePeriods.IndexOf(startPeriod); i >= 0; i--) {
var gap = availablePeriods[i];
var gapRemaining = seekMoment - gap.Start;
if(IsDebugEnabled)
log.Debug("Seek Backward... gap=[{0}], gapRemaining=[{1}], remaining=[{2}], seekMoment=[{3}]", gap, gapRemaining,
remaining, seekMoment);
var isTargetPeriod = (seekBoundaryMode == SeekBoundaryMode.Fill)
? gapRemaining >= remaining
: gapRemaining > remaining;
if(isTargetPeriod) {
var end = seekMoment - remaining.Value;
remaining = null;
return end;
}
remaining = remaining - gapRemaining;
if(i == 0)
return null;
seekMoment = availablePeriods[i - 1].End; // previous period
}
}
return null;
}