namespace TaxBaik.Web.Components.Admin.Shared; public static class BusinessDayCalculator { private sealed record HolidayWindow(DateOnly Start, DateOnly End) { public IEnumerable Dates() { for (var date = Start; date <= End; date = date.AddDays(1)) { yield return date; } } } private static readonly HolidayWindow[] HolidayWindows = { new(new DateOnly(2026, 1, 1), new DateOnly(2026, 1, 1)), new(new DateOnly(2026, 2, 16), new DateOnly(2026, 2, 18)), new(new DateOnly(2026, 3, 1), new DateOnly(2026, 3, 2)), new(new DateOnly(2026, 5, 5), new DateOnly(2026, 5, 5)), new(new DateOnly(2026, 6, 6), new DateOnly(2026, 6, 6)), new(new DateOnly(2026, 8, 15), new DateOnly(2026, 8, 17)), new(new DateOnly(2026, 9, 24), new DateOnly(2026, 9, 26)), new(new DateOnly(2026, 10, 3), new DateOnly(2026, 10, 5)), new(new DateOnly(2026, 10, 9), new DateOnly(2026, 10, 9)), new(new DateOnly(2026, 12, 25), new DateOnly(2026, 12, 25)), new(new DateOnly(2027, 1, 1), new DateOnly(2027, 1, 1)), new(new DateOnly(2027, 2, 6), new DateOnly(2027, 2, 9)), new(new DateOnly(2027, 3, 1), new DateOnly(2027, 3, 2)), new(new DateOnly(2027, 5, 5), new DateOnly(2027, 5, 5)), new(new DateOnly(2027, 5, 13), new DateOnly(2027, 5, 13)), new(new DateOnly(2027, 6, 6), new DateOnly(2027, 6, 6)), new(new DateOnly(2027, 8, 15), new DateOnly(2027, 8, 16)), new(new DateOnly(2027, 9, 14), new DateOnly(2027, 9, 16)), new(new DateOnly(2027, 10, 3), new DateOnly(2027, 10, 4)), new(new DateOnly(2027, 10, 9), new DateOnly(2027, 10, 11)), new(new DateOnly(2027, 12, 25), new DateOnly(2027, 12, 26)) }; private static readonly HashSet HolidayDates = BuildHolidayDates(); public static DateOnly GetEffectiveDueDate(DateOnly dueDate) { var effectiveDate = dueDate; while (!IsBusinessDay(effectiveDate)) { effectiveDate = effectiveDate.AddDays(1); } return effectiveDate; } public static int GetDday(DateOnly dueDate, DateOnly? referenceDate = null) { var today = referenceDate ?? DateOnly.FromDateTime(DateTime.Today); var effectiveDueDate = GetEffectiveDueDate(dueDate); return effectiveDueDate.DayNumber - today.DayNumber; } public static bool IsBusinessDay(DateOnly date) => date.DayOfWeek is not DayOfWeek.Saturday and not DayOfWeek.Sunday && !HolidayDates.Contains(date); private static HashSet BuildHolidayDates() { var holidays = new HashSet(); foreach (var window in HolidayWindows) { foreach (var date in window.Dates()) { holidays.Add(date); } } // 주말과 연속 공휴일 뒤에 붙는 대체휴일을 다음 영업일로 자동 확장한다. foreach (var window in HolidayWindows) { foreach (var date in window.Dates()) { if (date.DayOfWeek is not DayOfWeek.Saturday and not DayOfWeek.Sunday) { continue; } var substitute = date.AddDays(1); while (substitute.DayOfWeek is DayOfWeek.Saturday or DayOfWeek.Sunday || holidays.Contains(substitute)) { substitute = substitute.AddDays(1); } holidays.Add(substitute); } } return holidays; } }