///------------------------------------------------------------------------------------------------- /// <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); }
///------------------------------------------------------------------------------------------------- /// <summary> Adds a monthly last day event. </summary> /// /// <remarks> /// Add a monthly last day of the month. Typically the 30, or 31 of the month 28 or 29 in /// February. /// </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> /// /// <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="startDate"> (Optional) The date start, DateTime.MinValue if null. </param> /// <param name="endDate"> (Optional) The date end, DateTime.MaxValue if null. </param> /// /// <example> /// <code> /// CalendarDateTime.AddMonthlyLastDayEvent("Last date of every month", false); /// </code> /// </example> ///------------------------------------------------------------------------------------------------- public static void AddMonthlyLastDayEvent([CanBeNull] string eventName, bool dayOff, 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), eventName); } var dateRange = new MinMaxSwapDate(startDate, endDate); MonthlyEventsDictionary.Add(eventName, new MonthlyLastDayEvent(dayOff, dateRange.Min, dateRange.Max)); }
///------------------------------------------------------------------------------------------------- /// <summary> Adds a monthly day of week reverse (from end of month) event. </summary> /// /// <remarks> /// Add a monthly Day-of-Week, n-weeks reverse from the end of the month. Example, The last /// Monday of each month. Memorial day (US) follows this pattern (it's not always the 4th /// Monday of May). /// </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 weeksForward is less han 1. /// </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="weeksReverse"> Weeks reverse from the end of the month. </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> /// CalendarDateTime.AddMonthlyDayOfWeekReverseEvent("Last Monday Every Month", false, DateTime.Tuesday, 1) /// </code> /// </example> ///------------------------------------------------------------------------------------------------- public static void AddMonthlyDayOfWeekReverseEvent([CanBeNull] string eventName, bool dayOff, DayOfWeek dayOfWeek, int weeksReverse, DateTime?startDate = null, DateTime?endDate = null) { if (string.IsNullOrEmpty(eventName)) { throw new ArgumentNullException(nameof(eventName), ErrorMessageCannotBeNullOrEmpty(nameof(eventName))); } if (ContainsEventKey(eventName)) { throw new ArgumentException(ErrorMessageKeyAlreadyAdded(nameof(eventName), eventName), eventName); } if (weeksReverse < 1) { throw new ArgumentOutOfRangeException(nameof(weeksReverse), ErrorMessageZeroOrNegative(nameof(weeksReverse), weeksReverse)); } var dateRange = new MinMaxSwapDate(startDate, endDate); MonthlyEventsDictionary.Add(eventName, new MonthlyDayOfWeekReverseEvent(dayOff, dayOfWeek, weeksReverse, dateRange.Min, dateRange.Max)); }
///------------------------------------------------------------------------------------------------- /// <summary> Adds a monthly date event. </summary> /// /// <remarks> /// Adds a recurring monthly day of each month. Example: The 8th of each 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 month day is out of range, assumes [1...31] range for unknown month. /// </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="monthDay"> /// The month day, assumes [1...31] range. Caution, 29, 30, and 31 days may be problematic. /// </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> /// var start = new DateTime(2018, 2, 1); /// // Electric bill due, starts Feb 1, 2018 and never ends. /// CalendarDateTime.AddMonthlyDateEvent("Electric Bill Due", false, 8, start); /// </code> /// </example> ///------------------------------------------------------------------------------------------------- public static void AddMonthlyDateEvent([CanBeNull] string eventName, bool dayOff, int monthDay, DateTime?startDate = null, DateTime?endDate = null) { if (string.IsNullOrEmpty(eventName)) { throw new ArgumentNullException(nameof(eventName), ErrorMessageCannotBeNullOrEmpty(nameof(eventName))); } if (ContainsEventKey(eventName)) { throw new ArgumentException(ErrorMessageKeyAlreadyAdded(nameof(eventName), eventName), eventName); } if (1 > monthDay || monthDay > 31) { // Although this depends of the month and leap year, this is the best we can do. throw new ArgumentOutOfRangeException(ErrorMessageMinMaxOutOfRange(monthDay, nameof(monthDay), 1, 31)); } var dateRange = new MinMaxSwapDate(startDate, endDate); MonthlyEventsDictionary.Add(eventName, new MonthlyDayEvent(dayOff, monthDay, dateRange.Min, dateRange.Max)); }