Exemplo n.º 1
0
        /// <summary>
        /// This is used to handle the expansion of the yearly frequency
        /// </summary>
        /// <param name="dates">The collection in which to put the dates</param>
        /// <remarks>The spec is rather vague about how all the rules should interact so I'm making some best
        /// guesses here based on the examples in the spec itself although not all combinations are shown.
        /// </remarks>
        private void ExpandYearly(RecurDateTimeCollection dates)
        {
            RecurDateTimeCollection rdtcMonth = null, rdtcMoDay = null, rdtcWeek = null, rdtcYrDay = null,
                rdtcDay = null;
            bool isExpanded = false;

            // We'll expand each rule individually and combine the results before applying the time expansions.
            // The application of the BYMONTHDAY and BYDAY rules varies based on whatever other rule parts are
            // present as well.
            if(byMonth.Count != 0)
            {
                // Expand by month
                isExpanded = true;
                rdtcMonth = new RecurDateTimeCollection(dates);
                freqRules.ByMonth(this, rdtcMonth);

                // If BYMONTHDAY and BYDAY are both specified, we need to expand by month day and then filter by
                // day.  If we expand by day alone, note that we do so only in the months specified in the
                // BYMONTH rule.
                if(byMonthDay.Count != 0 && byDay.Count != 0)
                {
                    Expand.ByMonthDay(this, rdtcMonth);
                    Filter.ByDay(this, rdtcMonth);
                }
                else
                    if(Expand.ByMonthDay(this, rdtcMonth) != 0)
                        Expand.ByDayInMonths(this, rdtcMonth);
            }
            else
            {
                if(byMonthDay.Count != 0)
                {
                    // Expand by month day if specified without any by month rule part
                    isExpanded = true;
                    rdtcMoDay = new RecurDateTimeCollection(dates);
                    freqRules.ByMonthDay(this, rdtcMoDay);
                }

                // As long as by week number isn't specified either, we'll expand the by day rule here too
                if(byWeekNo.Count == 0)
                {
                    isExpanded = true;
                    rdtcDay = new RecurDateTimeCollection(dates);
                    freqRules.ByDay(this, rdtcDay);
                }
            }

            if(byWeekNo.Count != 0)
            {
                // Expand by week number
                isExpanded = true;
                rdtcWeek = new RecurDateTimeCollection(dates);
                freqRules.ByWeekNo(this, rdtcWeek);

                // Expand by days of the week in those weeks
                Expand.ByDayInWeeks(this, rdtcWeek);
            }

            if(byYearDay.Count != 0)
            {
                // Expand by year day
                isExpanded = true;
                rdtcYrDay = new RecurDateTimeCollection(dates);
                freqRules.ByYearDay(this, rdtcYrDay);
            }

            // Combine the various expansions.  If nothing was done, leave the original date in the collection.
            if(isExpanded)
            {
                dates.Clear();

                if(rdtcMonth != null && rdtcMonth.Count != 0)
                    dates.AddRange(rdtcMonth);

                if(rdtcMoDay != null && rdtcMoDay.Count != 0)
                    dates.AddRange(rdtcMoDay);

                if(rdtcWeek != null && rdtcWeek.Count != 0)
                    dates.AddRange(rdtcWeek);

                if(rdtcYrDay != null && rdtcYrDay.Count != 0)
                    dates.AddRange(rdtcYrDay);

                if(rdtcDay != null && rdtcDay.Count != 0)
                    dates.AddRange(rdtcDay);
            }

            // In any case, the time parts are easy.  They always expand the instances if there's anything there.
            if(dates.Count != 0)
            {
                Expand.ByHour(this, dates);
                Expand.ByMinute(this, dates);
                Expand.BySecond(this, dates);
            }
        }
Exemplo n.º 2
0
        /// <summary>
        /// This method is used to return all recurring instances between the two specified date/times based on
        /// the current settings.
        /// </summary>
        /// <param name="fromDate">The minimum date/time on or after which instances should occur.</param>
        /// <param name="toDate">The maximum date/time on or before which instances should occur.</param>
        /// <returns>Returns a <see cref="DateTimeCollection"/> of <see cref="DateTime" /> objects that represent
        /// the instances found between the two specified date/times.</returns>
        private DateTimeCollection GenerateInstances(DateTime fromDate, DateTime toDate)
        {
            RecurDateTimeCollection rdtc;
            RecurDateTime rdt;
            int idx, count, lastYear = -1;

            DateTimeCollection dcDates = new DateTimeCollection();

            // If undefined or if the requested range is outside that of the recurrence, don't bother.  Just
            // return an empty collection.  Note that for defined recurrences that use a count, we'll always
            // have to expand it.
            if(frequency == RecurFrequency.Undefined || startDate > toDate || untilDate < fromDate)
                return dcDates;

            RecurDateTime start = new RecurDateTime(startDate), end = new RecurDateTime(untilDate),
                from = new RecurDateTime(fromDate), to = new RecurDateTime(toDate);

            RecurDateTime current = freqRules.FindStart(this, start, end, from, to);

            // If there's nothing to generate, stop now
            if(current == null)
                return dcDates;

            rdtc = new RecurDateTimeCollection();

            // Initialize the filtering arrays.  These help speed up the filtering process by letting us do one
            // look up as opposed to comparing all elements in the collection.
            Array.Clear(isSecondUsed, 0, isSecondUsed.Length);
            Array.Clear(isMinuteUsed, 0, isMinuteUsed.Length);
            Array.Clear(isHourUsed, 0, isHourUsed.Length);
            Array.Clear(isDayUsed, 0, isDayUsed.Length);
            Array.Clear(isMonthDayUsed, 0, isMonthDayUsed.Length);
            Array.Clear(isNegMonthDayUsed, 0, isNegMonthDayUsed.Length);
            Array.Clear(isYearDayUsed, 0, isYearDayUsed.Length);
            Array.Clear(isNegYearDayUsed, 0, isNegYearDayUsed.Length);
            Array.Clear(isMonthUsed, 0, isMonthUsed.Length);

            if(bySecond.Count != 0)
                foreach(int second in bySecond)
                    isSecondUsed[second] = true;

            if(byMinute.Count != 0)
                foreach(int minute in byMinute)
                    isMinuteUsed[minute] = true;

            if(byHour.Count != 0)
                foreach(int hour in byHour)
                    isHourUsed[hour] = true;

            if(byMonth.Count != 0)
                foreach(int month in byMonth)
                    isMonthUsed[month - 1] = true;

            // When filtering, the instance is ignored
            if(byDay.Count != 0)
                foreach(DayInstance di in byDay)
                    isDayUsed[(int)di.DayOfWeek] = true;

            // Negative days are from the end of the month
            if(byMonthDay.Count != 0)
                foreach(int monthDay in byMonthDay)
                    if(monthDay > 0)
                        isMonthDayUsed[monthDay] = true;
                    else
                        isNegMonthDayUsed[0 - monthDay] = true;

            // Negative days are from the end of the year
            if(byYearDay.Count != 0)
                foreach(int yearDay in byYearDay)
                    if(yearDay > 0)
                        isYearDayUsed[yearDay] = true;
                    else
                        isNegYearDayUsed[0 - yearDay] = true;

            do
            {
                rdtc.Clear();
                rdtc.Add(current);

                // The spec is rather vague about how some of the rules are used together.  For example, it says
                // that rule parts for a period of time less than the frequency generally expand it.  However,
                // an example for the MONTHLY frequency shows that when BYMONTHDAY and BYDAY are used together,
                // BYDAY acts as a filter for BYMONTHDAY not an expansion of the frequency.  When used by
                // themselves, the rules in question do act as expansions.  There are no examples for the yearly
                // frequency that show how all of the various combinations interact so I'm making some
                // assumptions based on an evaluation of what makes the most sense.
                switch(frequency)
                {
                    case RecurFrequency.Yearly:
                        // This one gets rather messy so it's separate
                        ExpandYearly(rdtc);
                        break;

                    case RecurFrequency.Monthly:
                        if(freqRules.ByMonth(this, rdtc) != 0)
                            if(freqRules.ByYearDay(this, rdtc) != 0)
                            {
                                // If BYMONTHDAY and BYDAY are specified, expand by month day and filter by day.
                                // If one but not the other or neither is specified, handle them in order as
                                // usual.
                                if(byMonthDay.Count != 0 && byDay.Count != 0)
                                {
                                    if(Expand.ByMonthDay(this, rdtc) != 0)
                                        if(Filter.ByDay(this, rdtc) != 0)
                                        {
                                            // These always expand if used
                                            Expand.ByHour(this, rdtc);
                                            Expand.ByMinute(this, rdtc);
                                            Expand.BySecond(this, rdtc);
                                        }
                                }
                                else
                                    if(Expand.ByMonthDay(this, rdtc) != 0)
                                        if(freqRules.ByDay(this, rdtc) != 0)
                                        {
                                            // These always expand if used
                                            Expand.ByHour(this, rdtc);
                                            Expand.ByMinute(this, rdtc);
                                            Expand.BySecond(this, rdtc);
                                        }
                            }
                        break;

                    default:
                        // Everything else is fairly straightforward.  We just expand or filter based on the
                        // frequency type and what rules are specified.
                        if(freqRules.ByMonth(this, rdtc) != 0)
                            if(freqRules.ByYearDay(this, rdtc) != 0)
                                if(freqRules.ByMonthDay(this, rdtc) != 0)
                                    if(freqRules.ByDay(this, rdtc) != 0)
                                        if(freqRules.ByHour(this, rdtc) != 0)
                                            if(freqRules.ByMinute(this, rdtc) != 0)
                                                freqRules.BySecond(this, rdtc);
                        break;
                }

                // Sort the dates and remove invalid and duplicate dates
                rdtc.Sort();

                for(idx = 0, count = rdtc.Count; idx < count; idx++)
                {
                    rdt = rdtc[idx];

                    // If not valid, discard it.
                    if(!rdt.IsValidDate())
                    {
                        rdtc.RemoveAt(idx);
                        idx--;
                        count--;
                        continue;
                    }

                    // Discard it if it falls on a holiday
                    if(!canOccurOnHoliday)
                    {
                        // If this is the first call or the year changes, get the holidays in the date's year
                        // and the next year.
                        if(holDates == null || lastYear != rdt.Year)
                        {
                            holDates = new HashSet<DateTime>(holidays.HolidaysBetween(rdt.Year, rdt.Year + 1));
                            lastYear = rdt.Year;
                        }

                        // Note that we only compare the date part as the holiday's time probably will not match
                        // the recurrence's time.
                        if(holDates.Contains(rdt.ToDateTime().Date))
                        {
                            rdtc.RemoveAt(idx);
                            idx--;
                            count--;
                            continue;
                        }
                    }

                    // Discard it if it's a duplicate
                    if(idx != 0 && rdt == rdtc[idx - 1])
                    {
                        rdtc.RemoveAt(idx);
                        idx--;
                        count--;
                        continue;
                    }
                }

                if(rdtc.Count != 0)
                {
                    // Apply the BYSETPOS rule and remove entries prior to the start or past the end of the
                    // ranges.
                    if(bySetPos.Count != 0)
                    {
                        foreach(int nPos in bySetPos)
                        {
                            // Invert negative values.  They'll select elements indexed from the end of the
                            // array.
                            if(nPos < 0)
                                idx = nPos + rdtc.Count;
                            else
                                idx = nPos - 1;

                            if(idx >= 0 && idx < rdtc.Count)
                                if(rdtc[idx] >= start && rdtc[idx] <= end && rdtc[idx] >= from && rdtc[idx] <= to)
                                    dcDates.Add(rdtc[idx].ToDateTime());
                        }
                    }
                    else
                        for(idx = 0; idx < rdtc.Count; idx++)
                            if(rdtc[idx] >= start && rdtc[idx] <= end && rdtc[idx] >= from && rdtc[idx] <= to)
                                dcDates.Add(rdtc[idx].ToDateTime());

                    // Handle MaxOccurrences property.  Note that if it's used, it is assumed that the limiting
                    // range starts at the recurrence start.  Otherwise, we have no way of knowing how many
                    // occurred between the recurrence start and the limiting range's start date.
                    if(maxOccur != 0 && dcDates.Count > maxOccur)
                        dcDates.RemoveRange(maxOccur, dcDates.Count - maxOccur);
                }

                // Loop until the end of the recurrence or the range
            } while(freqRules.FindNext(this, end, to, current) && (maxOccur == 0 || dcDates.Count < maxOccur));

            // Sort the collection one last time.  There's no guaranteed order of selection if BYSETPOS was used.
            dcDates.Sort(true);

            return dcDates;
        }