///------------------------------------------------------------------------------------------------- /// <summary> Adds a weekly in month event. </summary> /// /// <remarks> /// Adds weekly day-of-week events, can occur on multiple days of the week and at some /// interval by week. If the interval is null, all weeks [1...5.] are added, the 5th week /// does not occur every month. Its possible to set an odd pattern such as 1rst, 2nd week of /// every month. /// </remarks> /// /// <exception cref="ArgumentNullException"> /// Thrown when eventName is null or empty. /// </exception> /// <exception cref="ArgumentException"> /// Thrown when eventName is not unique or startDate > endDate. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when interval is less than 1 or greater than 5. /// </exception> /// /// <param name="eventName"> Name of the event, must be unique. </param> /// <param name="dayOff"> True if is a day off (is a workday). </param> /// <param name="dayOfWeek"> The day of week. </param> /// <param name="weekIntervals"> (Optional) The weekly intervals, If null every week, 1: 1 first week, 2: second week, 3: third week... Not every month will have a fifth week. </param> /// <param name="startDate"> (Optional) The date start, DateTime.MinValue if null. </param> /// <param name="endDate"> (Optional) The date end, DateTime.MaxValue if null. </param> /// /// <example> /// <code> /// // Mondays, first and third week. /// var weekIntervals = new int[] {1, 3}; /// CalendarDateTime.AddWeeklyInMonthEvent("Backups", false, DayOfWeek.Monday, weekIntervals); /// </code> /// </example> ///------------------------------------------------------------------------------------------------- public static void AddWeeklyInMonthEvent([CanBeNull] string eventName, bool dayOff, DayOfWeek dayOfWeek, [CanBeNull] IEnumerable <int> weekIntervals = null, DateTime?startDate = null, DateTime?endDate = null) { if (string.IsNullOrEmpty(eventName)) { throw new ArgumentNullException(nameof(eventName)); } if (ContainsEventKey(eventName)) { throw new ArgumentException(ErrorMessageKeyAlreadyAdded(nameof(eventName), eventName), nameof(eventName)); } const int minWeek = 1; const int maxWeek = 5; weekIntervals = weekIntervals?.Distinct().OrderBy(s => s).ToList() ?? Enumerable.Range(minWeek, maxWeek).ToList(); if (minWeek > weekIntervals.Min()) { throw new ArgumentOutOfRangeException(nameof(weekIntervals), ErrorMessageMinMaxOutOfRange(weekIntervals.Min(), nameof(weekIntervals), minWeek, maxWeek)); } if (weekIntervals.Max() > maxWeek) { throw new ArgumentOutOfRangeException(nameof(weekIntervals), ErrorMessageMinMaxOutOfRange(weekIntervals.Max(), nameof(weekIntervals), minWeek, maxWeek)); } var dateRange = new MinMaxSwapDate(startDate, endDate); WeeklyEventsDictionary.Add(eventName, new WeeklyInMonthEvent(dayOff, dayOfWeek, weekIntervals, dateRange.Min, dateRange.Max)); }
/// ------------------------------------------------------------------------------------------------- /// <summary> Adds a weekly event. </summary> /// /// <remarks> /// Add a weekly day-of-week events, can occur on multiple days of the week and at some skip /// interval. The baseDate is the date of the interval. Paydays typically follow this /// pattern. The number of occurrences may vary from month to month because some months are 4 /// weeks, some are 5 weeks. Another example; a meeting every two weeks on Tuesdays and /// Thursday within a date range. /// </remarks> /// /// <exception cref="ArgumentNullException"> /// Thrown when eventName is null or empty. /// </exception> /// <exception cref="ArgumentException"> /// Thrown when eventName is not unique or startDate > endDate. /// </exception> /// <exception cref="ArgumentOutOfRangeException"> /// Thrown when interval is less than one. /// </exception> /// <param name="eventName"> Name of the event, must be unique. </param> /// <param name="dayOff"> True if is a day off (is a workday). </param> /// <param name="daysOfWeek"> The days of week. This may be null. </param> /// <param name="seedWeek"> /// (Optional) (Optional if interval = 1) Seed date in the week of an occurrence. /// </param> /// <param name="interval"> /// (Optional) The week skip interval, 1=every week, 2 = every 2 weeks, 3 = 3 weeks... /// </param> /// <param name="startDate"> (Optional) The date start, DateTime.MinValue if null. </param> /// <param name="endDate"> (Optional) The date end, DateTime.MaxValue if null. </param> /// ------------------------------------------------------------------------------------------------- public static void AddWeeklyEvent([CanBeNull] string eventName, bool dayOff, [CanBeNull] IEnumerable <DayOfWeek> daysOfWeek, DateTime?seedWeek = null, int interval = 1, DateTime?startDate = null, DateTime?endDate = null) { if (string.IsNullOrEmpty(eventName)) { throw new ArgumentNullException(nameof(eventName)); } if (ContainsEventKey(eventName)) { throw new ArgumentException(ErrorMessageKeyAlreadyAdded(nameof(eventName), eventName), nameof(eventName)); } daysOfWeek = daysOfWeek?.Distinct().OrderBy(s => s).ToList(); if (daysOfWeek == null || !daysOfWeek.Any()) { throw new ArgumentNullException(nameof(daysOfWeek), ErrorMessageCannotBeNullOrEmpty(nameof(daysOfWeek))); } if (interval < 1) { throw new ArgumentOutOfRangeException(ErrorMessageZeroOrNegative(nameof(interval), interval)); } if (interval > 1 && seedWeek == null) { throw new ArgumentNullException(nameof(seedWeek), "If interval > 1, then " + nameof(seedWeek) + " cannot be null."); } var seed = seedWeek ?? DateTime.MinValue; var dateRange = new MinMaxSwapDate(startDate, endDate); WeeklyEventsDictionary.Add(eventName, new WeeklyEvent(dayOff, interval, daysOfWeek, seed, dateRange.Min, dateRange.Max)); }
///------------------------------------------------------------------------------------------------- /// <summary> A DateTime extension method that returns list of events on date. </summary> /// /// <param name="date"> The date. </param> /// <param name="includeWorkdays"> True to include, false to include workday events. </param> /// <param name="includeDaysOff"> True to disable, false to include days off events. </param> /// /// <returns> An ImmutableArray<string> </returns> /// /// <example> /// <code> /// CalendarDateTime.AddYearlyDateEvent("myBirthday", true, 5, 20, false, false); /// var aDate = new DateTime(2018, 5, 20, 5, 30, 0); /// var events = aDate.EventsOnDate(true, true); /// // events = [0] = "myBirthday" /// </code> /// </example> ///------------------------------------------------------------------------------------------------- public static ImmutableArray <string> EventsOnDate(this DateTime date, bool includeWorkdays, bool includeDaysOff) { date = date.Date; var events = new List <string>(); var yearlyEvents = YearlyEventsDictionary .Where(v => date.IsBetweenEqual(v.Value.DateStart, v.Value.DateEnd) && (v.Value.DayOff && includeDaysOff || v.Value.WorkDay && includeWorkdays) && v.Value.Date(date.Year) == date) .Select(k => k.Key); var monthlyEvents = MonthlyEventsDictionary .Where(v => date.IsBetweenEqual(v.Value.DateStart, v.Value.DateEnd) && (v.Value.DayOff && includeDaysOff || v.Value.WorkDay && includeWorkdays) && v.Value.Date(date.Year, date.Month) == date) .Select(k => k.Key); var weeklyEvents = WeeklyEventsDictionary .Where(v => date.IsBetweenEqual(v.Value.DateStart, v.Value.DateEnd) && (v.Value.DayOff && includeDaysOff || v.Value.WorkDay && includeWorkdays) && v.Value.IsEventDay(date)) .Select(k => k.Key); var dateEvents = DateEventsDictionary .Where(v => (v.Value.DayOff && includeDaysOff || v.Value.WorkDay && includeWorkdays) && v.Value.Date == date) .Select(k => k.Key); events.AddRange(yearlyEvents); events.AddRange(monthlyEvents); events.AddRange(weeklyEvents); events.AddRange(dateEvents); return(events.ToImmutableArray()); }
///------------------------------------------------------------------------------------------------- /// <summary> /// A DateTime extension method that query if 'date' is day off. The days off is configured /// using the Add-calendar event methods. /// </summary> /// /// <param name="date"> The date. </param> /// /// <returns> True if day off, false if not. </returns> /// /// <example> /// <code> /// // LINQ example. /// var starting = new DateTime(2018, 1, 1); /// var ending = new DateTime(2018, 12, 31); /// var allDates = Enumerable.Range(0, 1 + ending.Subtract(starting).Days).Select(i=> starting.AddDays(i)); /// /// CalendarDateTime.AddYearlyDateEvent("theDate", true, 6, 10, false, false); /// var result = allDates.Where(d => d.IsDayOff()); /// // result = [0] = {6/10/2018 12:00:00 AM} /// </code> /// </example> ///------------------------------------------------------------------------------------------------- public static bool IsDayOff(this DateTime date) { date = date.Date; var p = YearlyEventsDictionary.Any(v => v.Value.DayOff && v.Value.Date(date.Year) == date); var m = MonthlyEventsDictionary.Any(v => v.Value.DayOff && v.Value.Date(date.Year, date.Month) == date); var w = WeeklyEventsDictionary.Any(v => v.Value.DayOff && v.Value.IsEventDay(date)); var a = DateEventsDictionary.Any(v => v.Value.DayOff && v.Value.Date == date); return(p || m || w || a); }