private DateTime InternalGetNextOccurence(DateTime baseValue, DateTime endValue)
{
var newValue = baseValue;
var overflow = true;
var isSecondFormat = Format == CronStringFormat.WithSeconds || Format == CronStringFormat.WithSecondsAndYears;
var isYearFormat = Format == CronStringFormat.WithYears || Format == CronStringFormat.WithSecondsAndYears;
// First things first - trim off any time components we don't need
newValue = newValue.AddMilliseconds(-newValue.Millisecond);
if (!isSecondFormat) newValue = newValue.AddSeconds(-newValue.Second);
var minuteFilters = Filters[CrontabFieldKind.Minute].Where(x => x is ITimeFilter).Cast<ITimeFilter>().ToList();
var hourFilters = Filters[CrontabFieldKind.Hour].Where(x => x is ITimeFilter).Cast<ITimeFilter>().ToList();
var firstSecondValue = newValue.Second;
var firstMinuteValue = minuteFilters.Select(x => x.First()).Min();
var firstHourValue = hourFilters.Select(x => x.First()).Min();
var newSeconds = newValue.Second;
if (isSecondFormat)
{
var secondFilters = Filters[CrontabFieldKind.Second].Where(x => x is ITimeFilter).Cast<ITimeFilter>().ToList();
firstSecondValue = secondFilters.Select(x => x.First()).Min();
newSeconds = Increment(secondFilters, newValue.Second, firstSecondValue, out overflow);
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds);
if (!overflow && !IsMatch(newValue))
{
newSeconds = firstSecondValue;
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newValue.Minute, newSeconds);
overflow = true;
}
if (!overflow) return MinDate(newValue, endValue);
}
var newMinutes = Increment(minuteFilters, newValue.Minute + (overflow ? 0 : -1), firstMinuteValue, out overflow);
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, overflow ? firstSecondValue : newSeconds);
if (!overflow && !IsMatch(newValue))
{
newSeconds = firstSecondValue;
newMinutes = firstMinuteValue;
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newValue.Hour, newMinutes, firstSecondValue);
overflow = true;
}
if (!overflow) return MinDate(newValue, endValue);
var newHours = Increment(hourFilters, newValue.Hour + (overflow ? 0 : -1), firstHourValue, out overflow);
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, newHours,
overflow ? firstMinuteValue : newMinutes,
overflow ? firstSecondValue : newSeconds);
if (!overflow && !IsMatch(newValue))
{
newValue = new DateTime(newValue.Year, newValue.Month, newValue.Day, firstHourValue, firstMinuteValue, firstSecondValue);
overflow = true;
}
if (!overflow) return MinDate(newValue, endValue);
List<ITimeFilter> yearFilters = null;
if (isYearFormat) yearFilters = Filters[CrontabFieldKind.Year].Where(x => x is ITimeFilter).Cast<ITimeFilter>().ToList();
// Sooo, this is where things get more complicated.
// Since the filtering of days relies on what month/year you're in
// (for weekday/nth day filters), we'll only increment the day, and
// check all day/month/year filters. Might be a litle slow, but we
// won't miss any days that way.
// Also, if we increment to the next day, we need to set the hour, minute and second
// fields to their "first" values, since that would be the earliest they'd run. We
// only have to do this after the initial AddDays call. FYI - they're already at their
// first values if overflowHour = True. :-)
// This feels so dirty. This is to catch the odd case where you specify
// 12/31/9999 23:59:59.999 as your end date, and you don't have any matches,
// so it reaches the max value of DateTime and throws an exception.
try { newValue = newValue.AddDays(1); } catch { return endValue; }
while (!(IsMatch(newValue, CrontabFieldKind.Day) && IsMatch(newValue, CrontabFieldKind.DayOfWeek) && IsMatch(newValue, CrontabFieldKind.Month) && (!isYearFormat || IsMatch(newValue, CrontabFieldKind.Year))))
{
if (newValue >= endValue) return MinDate(newValue, endValue);
// In instances where the year is filtered, this will speed up the path to get to endValue
// (without having to actually go to endValue)
if (isYearFormat && yearFilters.Select(x => x.Next(newValue.Year - 1)).All(x => x == null)) return endValue;
// Ugh...have to do the try/catch again...
try { newValue = newValue.AddDays(1); } catch { return endValue; }
}
return MinDate(newValue, endValue);
}