private DateTime GetHolidayDate(int year, HolidayDefinition holidayDefinition) { return(Enum.Parse <HolidayPlacementStrategy>(holidayDefinition.PlacementStrategy) switch { HolidayPlacementStrategy.FixedDate => DateUtils.CalculateFixedDatePlacement(year, holidayDefinition.Month, holidayDefinition.Day), HolidayPlacementStrategy.FixedDay => DateUtils.CalculateFixedDayPlacement(year, holidayDefinition.Month, holidayDefinition.DayOfWeek, holidayDefinition.WeekOfMonth), HolidayPlacementStrategy.FixedDateNonWeekend => DateUtils.CalculateFixedDateNonWeekendPlacement(year, holidayDefinition.Month, holidayDefinition.Day), _ => throw new InvalidOperationException($"'{holidayDefinition.PlacementStrategy}' is not handled") });
/// <inheritdoc /> public async Task <long> CalculateWorkingDays(DateTime from, DateTime to) { var totalDays = (long)(to - from).TotalDays + 1; if (totalDays <= 0) { return(0); } // Retrieve all relevant holidays definitions from data source, pull all if the total days elapses a year var holidays = await _holidayDefinitionRepository.Query .Where(totalDays < 365?HolidayDefinition.BetweenMonths(from, to) : h => true) .ToListAsync(); // Efficiently calculate whole years, removing in batch. Skipping the last remainder of a year // Additional performance could be achieved with asynchronous batching var removalDays = 0L; var fullYears = totalDays / 365; for (var y = 0; y < fullYears; y++) { // Determine the count of all holidays that rest on a weekday removalDays = holidays .Select(h => GetHolidayDate(from.Year + y, h).DayOfWeek) .Aggregate(removalDays, (days, holidayDay) => days + (holidayDay != DayOfWeek.Saturday && holidayDay != DayOfWeek.Sunday ? 1 : 0)); } // Calculate the overflow of days var excessDays = totalDays % (DateTime.IsLeapYear(to.Year) ? 366 : 365); if (excessDays > 0) { // Sum all holidays that exist within the partial range that are not on a weekend removalDays += holidays .Where(h => h.Month >= to.AddDays(-excessDays).Month) .Count(h => { // Conditional as to what year the holiday falls on, i.e. 1/06/2020 - 1/05/2021 var holidayDate = GetHolidayDate(to.Year, h); // Assume that if it isn't within the last year, it is in the previous year if (holidayDate > to) { holidayDate = GetHolidayDate(to.Year - 1, h); } return(holidayDate >= from && holidayDate <= to && holidayDate.DayOfWeek != DayOfWeek.Saturday && holidayDate.DayOfWeek != DayOfWeek.Sunday); }); } // Account for weekends in bulk var weekends = totalDays / 7 * 2; // Cycle through remaining days (Maximum 6) to determine how many additional weekends var remainingWeekOverflow = totalDays % 7; if (remainingWeekOverflow > 0) { var lastProcessedDate = to.AddDays(-remainingWeekOverflow); // TODO : There is a more optimal way, but in the interest of time the performance increase is negligible for (var d = 1; d <= remainingWeekOverflow; d++) { var day = lastProcessedDate.AddDays(d).DayOfWeek; if (day == DayOfWeek.Saturday || day == DayOfWeek.Sunday) { weekends++; } } } removalDays += weekends; return(totalDays - removalDays); }
static HolidayUtils() { HolidayDefinitions = new HolidayDefinition[] { new HolidayDefinition() { Title = "元日", Month = 1, Day = 1 }, new HolidayDefinition() { Title = "成人の日", Month = 1, Day = 15, EndYear = 1999 }, new HolidayDefinition() { Title = "成人の日", Month = 1, HappyMondayWeek = 2, StartYear = 2000 }, new HolidayDefinition() { Title = "建国記念の日", Month = 2, Day = 11, StartYear = 1967 }, new HolidayDefinition() { Title = "天皇誕生日", Month = 4, Day = 29, EndYear = 1988 }, new HolidayDefinition() { Title = "みどりの日", Month = 4, Day = 29, StartYear = 1989, EndYear = 2006 }, new HolidayDefinition() { Title = "昭和の日", Month = 4, Day = 29, StartYear = 2007 }, new HolidayDefinition() { Title = "憲法記念日", Month = 5, Day = 3 }, new HolidayDefinition() { Title = "みどりの日", Month = 5, Day = 4, StartYear = 2007 }, new HolidayDefinition() { Title = "こどもの日", Month = 5, Day = 5 }, new HolidayDefinition() { Title = "海の日", Month = 7, Day = 20, StartYear = 1996, EndYear = 2002 }, new HolidayDefinition() { Title = "海の日", Month = 7, HappyMondayWeek = 3, StartYear = 2003 }, new HolidayDefinition() { Title = "山の日", Month = 8, Day = 11, StartYear = 2016 }, new HolidayDefinition() { Title = "敬老の日", Month = 9, Day = 15, StartYear = 1966, EndYear = 2002 }, new HolidayDefinition() { Title = "敬老の日", Month = 9, HappyMondayWeek = 3, StartYear = 2003 }, new HolidayDefinition() { Title = "体育の日", Month = 10, Day = 10, EndYear = 1999 }, new HolidayDefinition() { Title = "体育の日", Month = 10, HappyMondayWeek = 2, StartYear = 2000 }, new HolidayDefinition() { Title = "文化の日", Month = 11, Day = 3 }, new HolidayDefinition() { Title = "勤労感謝の日", Month = 11, Day = 23 }, new HolidayDefinition() { Title = "天皇誕生日", Month = 12, Day = 23, StartYear = 1989 }, }; }