/** * Returns a list of possible dates generated from the applicable BY* rules, using the specified date as a seed. * @param date the seed date * @param value the type of date list to return * @return a DateList */ static private List <DateTime> GetCandidates(DateTime date, RecurrencePattern pattern, bool?[] expandBehaviors) { List <DateTime> dates = new List <DateTime>(); dates.Add(date); dates = GetMonthVariants(dates, pattern, expandBehaviors[0]); dates = GetWeekNoVariants(dates, pattern, expandBehaviors[1]); dates = GetYearDayVariants(dates, pattern, expandBehaviors[2]); dates = GetMonthDayVariants(dates, pattern, expandBehaviors[3]); dates = GetDayVariants(dates, pattern, expandBehaviors[4]); dates = GetHourVariants(dates, pattern, expandBehaviors[5]); dates = GetMinuteVariants(dates, pattern, expandBehaviors[6]); dates = GetSecondVariants(dates, pattern, expandBehaviors[7]); dates = ApplySetPosRules(dates, pattern); return(dates); }
static bool?[] GetExpandBehaviorList(RecurrencePattern p) { // See the table in RFC 5545 Section 3.3.10 (Page 43). switch (p.Frequency) { case FrequencyType.Minutely: return(new bool?[] { false, null, false, false, false, false, false, true, false }); case FrequencyType.Hourly: return(new bool?[] { false, null, false, false, false, false, true, true, false }); case FrequencyType.Daily: return(new bool?[] { false, null, null, false, false, true, true, true, false }); case FrequencyType.Weekly: return(new bool?[] { false, null, null, null, true, true, true, true, false }); case FrequencyType.Monthly: { bool?[] row = new bool?[] { false, null, null, true, true, true, true, true, false }; // Limit if BYMONTHDAY is present; otherwise, special expand for MONTHLY. if (p.ByMonthDay.Count > 0) { row[4] = false; } return(row); } case FrequencyType.Yearly: { bool?[] row = new bool?[] { true, true, true, true, true, true, true, true, false }; // Limit if BYYEARDAY or BYMONTHDAY is present; otherwise, // special expand for WEEKLY if BYWEEKNO present; otherwise, // special expand for MONTHLY if BYMONTH present; otherwise, // special expand for YEARLY. if (p.ByYearDay.Count > 0 || p.ByMonthDay.Count > 0) { row[4] = false; } return(row); } default: return(new bool?[] { false, null, false, false, false, false, false, false, false }); } }
/** * Applies BYSECOND rules specified in this Recur instance to the specified date list. If no BYSECOND rules are * specified the date list is returned unmodified. * @param dates * @return */ static private List <DateTime> GetSecondVariants(List <DateTime> dates, RecurrencePattern pattern, bool?expand) { if (expand == null || pattern.BySecond.Count == 0) { return(dates); } if (expand.HasValue && expand.Value) { // Expand behavior List <DateTime> secondlyDates = new List <DateTime>(); for (int i = 0; i < dates.Count; i++) { DateTime date = dates[i]; for (int j = 0; j < pattern.BySecond.Count; j++) { int second = pattern.BySecond[j]; date = date.AddSeconds(-date.Second + second); secondlyDates.Add(date); } } return(secondlyDates); } else { // Limit behavior for (int i = dates.Count - 1; i >= 0; i--) { DateTime date = dates[i]; for (int j = 0; j < pattern.BySecond.Count; j++) { int second = pattern.BySecond[j]; if (date.Second == second) { goto Next; } } // Remove unmatched dates dates.RemoveAt(i); Next :; } return(dates); } }
/** * Applies BYHOUR rules specified in this Recur instance to the specified date list. If no BYHOUR rules are * specified the date list is returned unmodified. * @param dates * @return */ static private List <DateTime> GetHourVariants(List <DateTime> dates, RecurrencePattern pattern, bool?expand) { if (expand == null || pattern.ByHour.Count == 0) { return(dates); } if (expand.HasValue && expand.Value) { // Expand behavior List <DateTime> hourlyDates = new List <DateTime>(); for (int i = 0; i < dates.Count; i++) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByHour.Count; j++) { int hour = pattern.ByHour[j]; date = date.AddHours(-date.Hour + hour); hourlyDates.Add(date); } } return(hourlyDates); } else { // Limit behavior for (int i = dates.Count - 1; i >= 0; i--) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByHour.Count; j++) { int hour = pattern.ByHour[j]; if (date.Hour == hour) { goto Next; } } // Remove unmatched dates dates.RemoveAt(i); Next :; } return(dates); } }
/** * Applies BYMONTH rules specified in this Recur instance to the specified date list. If no BYMONTH rules are * specified the date list is returned unmodified. * @param dates * @return */ static private List <DateTime> GetMonthVariants(List <DateTime> dates, RecurrencePattern pattern, bool?expand) { if (expand == null || pattern.ByMonth.Count == 0) { return(dates); } if (expand.HasValue && expand.Value) { // Expand behavior List <DateTime> monthlyDates = new List <DateTime>(); for (int i = 0; i < dates.Count; i++) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByMonth.Count; j++) { int month = pattern.ByMonth[j]; date = date.AddMonths(month - date.Month); monthlyDates.Add(date); } } return(monthlyDates); } else { // Limit behavior for (int i = dates.Count - 1; i >= 0; i--) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByMonth.Count; j++) { if (date.Month == pattern.ByMonth[j]) { goto Next; } } dates.RemoveAt(i); Next :; } return(dates); } }
public void CopyFrom(RecurrencePattern r) { if (r != null) { Frequency = r.Frequency; Until = r.Until; Count = r.Count; Interval = r.Interval; BySecond = new List <int>(r.BySecond); ByMinute = new List <int>(r.ByMinute); ByHour = new List <int>(r.ByHour); ByDay = new List <WeekDay>(r.ByDay); ByMonthDay = new List <int>(r.ByMonthDay); ByYearDay = new List <int>(r.ByYearDay); ByWeekNo = new List <int>(r.ByWeekNo); ByMonth = new List <int>(r.ByMonth); BySetPosition = new List <int>(r.BySetPosition); FirstDayOfWeek = r.FirstDayOfWeek; RestrictionType = r.RestrictionType; EvaluationMode = r.EvaluationMode; } }
public static List <DateTime> GetDates(DateTime seed, DateTime periodStart, DateTime periodEnd, int maxCount, RecurrencePattern pattern, bool includeReferenceDateInResults) { List <DateTime> dates = new List <DateTime>(); DateTime originalDate = new DateTime(seed.Ticks, seed.Kind); DateTime seedCopy = new DateTime(seed.Ticks, seed.Kind); if (includeReferenceDateInResults) { dates.Add(seedCopy); } // If the interval is set to zero, or our count prevents us // from getting additional items, then return with the reference // date only. if (pattern.Interval == 0 || (pattern.Count != int.MinValue && pattern.Count <= dates.Count)) { return(dates); } // optimize the start time for selecting candidates // (only applicable where a COUNT is not specified) if (pattern.Count == int.MinValue) { DateTime incremented = seedCopy; // FIXME: we can more aggresively increment here when // the difference between dates is greater. IncrementDate(ref incremented, pattern, pattern.Interval); while (incremented < periodStart) { seedCopy = incremented; IncrementDate(ref incremented, pattern, pattern.Interval); } } bool?[] expandBehavior = GetExpandBehaviorList(pattern); int invalidCandidateCount = 0; int noCandidateIncrementCount = 0; DateTime candidate = DateTime.MinValue; while ((maxCount < 0) || (dates.Count < maxCount)) { if (pattern.Until != DateTime.MinValue && candidate != DateTime.MinValue && candidate > pattern.Until) { break; } if (periodEnd != null && candidate != DateTime.MinValue && candidate > periodEnd) { break; } if (pattern.Count >= 1 && (dates.Count + invalidCandidateCount) >= pattern.Count) { break; } List <DateTime> candidates = GetCandidates(seedCopy, pattern, expandBehavior); if (candidates.Count > 0) { noCandidateIncrementCount = 0; // sort candidates for identifying when UNTIL date is exceeded.. candidates.Sort(); for (int i = 0; i < candidates.Count; i++) { candidate = candidates[i]; // don't count candidates that occur before the original date.. if (candidate >= originalDate) { // candidates MAY occur before periodStart // For example, FREQ=YEARLY;BYWEEKNO=1 could return dates // from the previous year. // // candidates exclusive of periodEnd.. if (candidate >= periodEnd) { invalidCandidateCount++; } else if (pattern.Count >= 1 && (dates.Count + invalidCandidateCount) >= pattern.Count) { break; } else if (pattern.Until == DateTime.MinValue || candidate <= pattern.Until) { if (!dates.Contains(candidate)) { dates.Add(candidate); } } } } } else { noCandidateIncrementCount++; if ((maxIncrementCount > 0) && (noCandidateIncrementCount > maxIncrementCount)) { break; } } IncrementDate(ref seedCopy, pattern, pattern.Interval); } // sort final list.. dates.Sort(); return(dates); }
/** * Returns a list of applicable dates corresponding to the specified week day in accordance with the frequency * specified by this recurrence rule. * @param date * @param weekDay * @return */ static private List <DateTime> GetAbsWeekDays(DateTime date, WeekDay weekDay, RecurrencePattern pattern, bool?expand) { List <DateTime> days = new List <DateTime>(); DayOfWeek dayOfWeek = weekDay.DayOfWeek; if (pattern.Frequency == FrequencyType.Daily) { if (date.DayOfWeek == dayOfWeek) { days.Add(date); } } else if (pattern.Frequency == FrequencyType.Weekly || pattern.ByWeekNo.Count > 0) { // Rewind to the first day of the week while (date.DayOfWeek != pattern.FirstDayOfWeek) { date = date.AddDays(-1); } // Step forward until we're on the day of week we're interested in while (date.DayOfWeek != dayOfWeek) { date = date.AddDays(1); } days.Add(date); } else if (pattern.Frequency == FrequencyType.Monthly || pattern.ByMonth.Count > 0) { int month = date.Month; // construct a list of possible month days.. date = date.AddDays(-date.Day + 1); while (date.DayOfWeek != dayOfWeek) { date = date.AddDays(1); } while (date.Month == month) { days.Add(date); date = date.AddDays(7); } } else if (pattern.Frequency == FrequencyType.Yearly) { int year = date.Year; // construct a list of possible year days.. date = date.AddDays(-date.DayOfYear + 1); while (date.DayOfWeek != dayOfWeek) { date = date.AddDays(1); } while (date.Year == year) { days.Add(date); date = date.AddDays(7); } } return(GetOffsetDates(days, weekDay.Offset)); }
/** * Applies BYMONTHDAY rules specified in this Recur instance to the specified date list. If no BYMONTHDAY rules are * specified the date list is returned unmodified. * @param dates * @return */ static private List <DateTime> GetMonthDayVariants(List <DateTime> dates, RecurrencePattern pattern, bool?expand) { if (expand == null || pattern.ByMonthDay.Count == 0) { return(dates); } if (expand.HasValue && expand.Value) { // Expand behavior List <DateTime> monthDayDates = new List <DateTime>(); for (int i = 0; i < dates.Count; i++) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByMonthDay.Count; j++) { int monthDay = pattern.ByMonthDay[j]; int daysInMonth = Calendar.GetDaysInMonth(date.Year, date.Month); if (Math.Abs(monthDay) <= daysInMonth) { // Account for positive or negative numbers DateTime newDate; if (monthDay > 0) { newDate = date.AddDays(-date.Day + monthDay); } else { newDate = date.AddDays(-date.Day + 1).AddMonths(1).AddDays(monthDay); } monthDayDates.Add(newDate); } } } return(monthDayDates); } else { // Limit behavior for (int i = dates.Count - 1; i >= 0; i--) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByMonthDay.Count; j++) { int monthDay = pattern.ByMonthDay[j]; int daysInMonth = Calendar.GetDaysInMonth(date.Year, date.Month); if (Math.Abs(monthDay) > daysInMonth) { throw new ArgumentException("Invalid day of month: " + date + " (day " + monthDay + ")"); } // Account for positive or negative numbers DateTime newDate; if (monthDay > 0) { newDate = date.AddDays(-date.Day + monthDay); } else { newDate = date.AddDays(-date.Day + 1).AddMonths(1).AddDays(monthDay); } if (newDate.Day.Equals(date.Day)) { goto Next; } } Next :; dates.RemoveAt(i); } return(dates); } }
/** * Applies BYYEARDAY rules specified in this Recur instance to the specified date list. If no BYYEARDAY rules are * specified the date list is returned unmodified. * @param dates * @return */ static private List <DateTime> GetYearDayVariants(List <DateTime> dates, RecurrencePattern pattern, bool?expand) { if (expand == null || pattern.ByYearDay.Count == 0) { return(dates); } if (expand.HasValue && expand.Value) { // Expand behavior List <DateTime> yearDayDates = new List <DateTime>(); for (int i = 0; i < dates.Count; i++) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByYearDay.Count; j++) { int yearDay = pattern.ByYearDay[j]; DateTime newDate; if (yearDay > 0) { newDate = date.AddDays(-date.DayOfYear + yearDay); } else { newDate = date.AddDays(-date.DayOfYear + 1).AddYears(1).AddDays(yearDay); } yearDayDates.Add(newDate); } } return(yearDayDates); } else { // Limit behavior for (int i = dates.Count - 1; i >= 0; i--) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByYearDay.Count; j++) { int yearDay = pattern.ByYearDay[j]; DateTime newDate; if (yearDay > 0) { newDate = date.AddDays(-date.DayOfYear + yearDay); } else { newDate = date.AddDays(-date.DayOfYear + 1).AddYears(1).AddDays(yearDay); } if (newDate.DayOfYear == date.DayOfYear) { goto Next; } } dates.RemoveAt(i); Next :; } return(dates); } }
/** * Applies BYWEEKNO rules specified in this Recur instance to the specified date list. If no BYWEEKNO rules are * specified the date list is returned unmodified. * @param dates * @return */ static private List <DateTime> GetWeekNoVariants(List <DateTime> dates, RecurrencePattern pattern, bool?expand) { if (expand == null || pattern.ByWeekNo.Count == 0) { return(dates); } if (expand.HasValue && expand.Value) { // Expand behavior List <DateTime> weekNoDates = new List <DateTime>(); for (int i = 0; i < dates.Count; i++) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByWeekNo.Count; j++) { // Determine our target week number int weekNo = pattern.ByWeekNo[j]; // Determine our current week number int currWeekNo = Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); while (currWeekNo > weekNo) { // If currWeekNo > weekNo, then we're likely at the start of a year // where currWeekNo could be 52 or 53. If we simply step ahead 7 days // we should be back to week 1, where we can easily make the calculation // to move to weekNo. date = date.AddDays(7); currWeekNo = Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); } // Move ahead to the correct week of the year date = date.AddDays((weekNo - currWeekNo) * 7); // Step backward single days until we're at the correct DayOfWeek while (date.DayOfWeek != pattern.FirstDayOfWeek) { date = date.AddDays(-1); } for (int k = 0; k < 7; k++) { weekNoDates.Add(date); date = date.AddDays(1); } } } return(weekNoDates); } else { // Limit behavior for (int i = dates.Count - 1; i >= 0; i--) { DateTime date = dates[i]; for (int j = 0; j < pattern.ByWeekNo.Count; j++) { // Determine our target week number int weekNo = pattern.ByWeekNo[j]; // Determine our current week number int currWeekNo = Calendar.GetWeekOfYear(date, CalendarWeekRule.FirstFourDayWeek, pattern.FirstDayOfWeek); if (weekNo == currWeekNo) { goto Next; } } dates.RemoveAt(i); Next :; } return(dates); } }
public object Deserialize(TextReader tr) { string value = tr.ReadToEnd(); // Instantiate the data type RecurrencePattern r = new RecurrencePattern(); if (r != null) { Match match = Regex.Match(value, @"FREQ=(SECONDLY|MINUTELY|HOURLY|DAILY|WEEKLY|MONTHLY|YEARLY);?(.*)", RegexOptions.IgnoreCase); if (match.Success) { // Parse the frequency type r.Frequency = (FrequencyType)Enum.Parse(typeof(FrequencyType), match.Groups[1].Value, true); // NOTE: fixed a bug where the group 2 match // resulted in an empty string, which caused // an error. if (match.Groups[2].Success && match.Groups[2].Length > 0) { string[] keywordPairs = match.Groups[2].Value.Split(';'); foreach (string keywordPair in keywordPairs) { string[] keyValues = keywordPair.Split('='); string keyword = keyValues[0]; string keyValue = keyValues[1]; switch (keyword.ToUpper()) { case "UNTIL": { try { r.Until = DateTime.Parse(keyValue, System.Globalization.CultureInfo.InvariantCulture); } catch (Exception ex) { Log.e(ex); } } break; case "COUNT": r.Count = Convert.ToInt32(keyValue); break; case "INTERVAL": r.Interval = Convert.ToInt32(keyValue); break; case "BYSECOND": AddInt32Values(r.BySecond, keyValue); break; case "BYMINUTE": AddInt32Values(r.ByMinute, keyValue); break; case "BYHOUR": AddInt32Values(r.ByHour, keyValue); break; case "BYDAY": { string[] days = keyValue.Split(','); foreach (string day in days) { WeekDay wd = new WeekDay(); Match match2 = Regex.Match(value, @"(\+|-)?(\d{1,2})?(\w{2})"); if (match2.Success) { if (match2.Groups[2].Success) { wd.Offset = Convert.ToInt32(match2.Groups[2].Value); if (match2.Groups[1].Success && match2.Groups[1].Value.Contains("-")) { wd.Offset *= -1; } } wd.DayOfWeek = GetDayOfWeek(match2.Groups[3].Value); r.ByDay.Add(wd); } } } break; case "BYMONTHDAY": AddInt32Values(r.ByMonthDay, keyValue); break; case "BYYEARDAY": AddInt32Values(r.ByYearDay, keyValue); break; case "BYWEEKNO": AddInt32Values(r.ByWeekNo, keyValue); break; case "BYMONTH": AddInt32Values(r.ByMonth, keyValue); break; case "BYSETPOS": AddInt32Values(r.BySetPosition, keyValue); break; case "WKST": r.FirstDayOfWeek = GetDayOfWeek(keyValue); break; } } } } // // This matches strings such as: // // "Every 6 minutes" // "Every 3 days" // else if ((match = Regex.Match(value, @"every\s+(?<Interval>other|\d+)?\w{0,2}\s*(?<Freq>second|minute|hour|day|week|month|year)s?,?\s*(?<More>.+)", RegexOptions.IgnoreCase)).Success) { if (match.Groups["Interval"].Success) { int interval; if (!int.TryParse(match.Groups["Interval"].Value, out interval)) { r.Interval = 2; // "other" } else { r.Interval = interval; } } else { r.Interval = 1; } switch (match.Groups["Freq"].Value.ToLower()) { case "second": r.Frequency = FrequencyType.Secondly; break; case "minute": r.Frequency = FrequencyType.Minutely; break; case "hour": r.Frequency = FrequencyType.Hourly; break; case "day": r.Frequency = FrequencyType.Daily; break; case "week": r.Frequency = FrequencyType.Weekly; break; case "month": r.Frequency = FrequencyType.Monthly; break; case "year": r.Frequency = FrequencyType.Yearly; break; } string[] values = match.Groups["More"].Value.Split(','); foreach (string item in values) { if ((match = Regex.Match(item, @"(?<Num>\d+)\w\w\s+(?<Type>second|minute|hour|day|week|month)", RegexOptions.IgnoreCase)).Success || (match = Regex.Match(item, @"(?<Type>second|minute|hour|day|week|month)\s+(?<Num>\d+)", RegexOptions.IgnoreCase)).Success) { int num; if (int.TryParse(match.Groups["Num"].Value, out num)) { switch (match.Groups["Type"].Value.ToLower()) { case "second": r.BySecond.Add(num); break; case "minute": r.ByMinute.Add(num); break; case "hour": r.ByHour.Add(num); break; case "day": switch (r.Frequency) { case FrequencyType.Yearly: r.ByYearDay.Add(num); break; case FrequencyType.Monthly: r.ByMonthDay.Add(num); break; } break; case "week": r.ByWeekNo.Add(num); break; case "month": r.ByMonth.Add(num); break; } } } else if ((match = Regex.Match(item, @"(?<Num>\d+\w{0,2})?(\w|\s)+?(?<First>first)?(?<Last>last)?\s*((?<Day>sunday|monday|tuesday|wednesday|thursday|friday|saturday)\s*(and|or)?\s*)+", RegexOptions.IgnoreCase)).Success) { int num = int.MinValue; if (match.Groups["Num"].Success) { if (int.TryParse(match.Groups["Num"].Value, out num)) { if (match.Groups["Last"].Success) { // Make number negative num *= -1; } } } else if (match.Groups["Last"].Success) { num = -1; } else if (match.Groups["First"].Success) { num = 1; } foreach (Capture capture in match.Groups["Day"].Captures) { WeekDay wd = new WeekDay(); wd.Offset = num; wd.DayOfWeek = (DayOfWeek)Enum.Parse(typeof(DayOfWeek), capture.Value, true); r.ByDay.Add(wd); } } else if ((match = Regex.Match(item, @"at\s+(?<Hour>\d{1,2})(:(?<Minute>\d{2})((:|\.)(?<Second>\d{2}))?)?\s*(?<Meridian>(a|p)m?)?", RegexOptions.IgnoreCase)).Success) { int hour, minute, second; if (int.TryParse(match.Groups["Hour"].Value, out hour)) { // Adjust for PM if (match.Groups["Meridian"].Success && match.Groups["Meridian"].Value.ToUpper().StartsWith("P")) { hour += 12; } r.ByHour.Add(hour); if (match.Groups["Minute"].Success && int.TryParse(match.Groups["Minute"].Value, out minute)) { r.ByMinute.Add(minute); if (match.Groups["Second"].Success && int.TryParse(match.Groups["Second"].Value, out second)) { r.BySecond.Add(second); } } } } else if ((match = Regex.Match(item, @"^\s*until\s+(?<DateTime>.+)$", RegexOptions.IgnoreCase)).Success) { DateTime dt = DateTime.Parse(match.Groups["DateTime"].Value); DateTime.SpecifyKind(dt, DateTimeKind.Utc); r.Until = dt; } else if ((match = Regex.Match(item, @"^\s*for\s+(?<Count>\d+)\s+occurrences\s*$", RegexOptions.IgnoreCase)).Success) { int count; if (!int.TryParse(match.Groups["Count"].Value, out count)) { return(false); } else { r.Count = count; } } } } else { // Couldn't parse the object, return null! r = null; } if (r != null) { CheckMutuallyExclusive("COUNT", "UNTIL", r.Count, r.Until); CheckRange("INTERVAL", r.Interval, 0, int.MaxValue); CheckRange("COUNT", r.Count, 0, int.MaxValue); CheckRange("BYSECOND", r.BySecond, 0, 59); CheckRange("BYMINUTE", r.ByMinute, 0, 59); CheckRange("BYHOUR", r.ByHour, 0, 23); CheckRange("BYMONTHDAY", r.ByMonthDay, -31, 31); CheckRange("BYYEARDAY", r.ByYearDay, -366, 366); CheckRange("BYWEEKNO", r.ByWeekNo, -53, 53); CheckRange("BYMONTH", r.ByMonth, 1, 12); CheckRange("BYSETPOS", r.BySetPosition, -366, 366); } } return(r); }