"use strict"; // Day-number kernel: Howard Hinnant, "chrono-Compatible Low-Level Date Algorithms", // https://howardhinnant.github.io/date_algorithms.html (days_from_civil / civil_from_days). // Weekday parity (to find Saturday/Sunday) uses the day-number modulo 7, calibrated against // 1970-01-01 (day-number 0) being a Thursday. No host clock, no Date.now(), no time zone // database, and NO holiday calendar: this method excludes weekends only. function daysFromCivil(y, m, d) { y = m <= 2 ? y - 1 : y; const era = Math.floor((y >= 0 ? y : y - 399) / 400); const yoe = y - era * 400; const doy = Math.floor((153 * (m + (m > 2 ? -3 : 9)) + 2) / 5) + d - 1; const doe = yoe * 365 + Math.floor(yoe / 4) - Math.floor(yoe / 100) + doy; return era * 146097 + doe - 719468; } function civilFromDays(z) { z += 719468; const era = Math.floor((z >= 0 ? z : z - 146096) / 146097); const doe = z - era * 146097; const yoe = Math.floor((doe - Math.floor(doe / 1460) + Math.floor(doe / 36524) - Math.floor(doe / 146096)) / 365); const y = yoe + era * 400; const doy = doe - (365 * yoe + Math.floor(yoe / 4) - Math.floor(yoe / 100)); const mp = Math.floor((5 * doy + 2) / 153); const d = doy - Math.floor((153 * mp + 2) / 5) + 1; const m = mp + (mp < 10 ? 3 : -9); const yy = y + (m <= 2 ? 1 : 0); return { year: yy, month: m, day: d }; } function isLeapGregorian(y) { return (y % 4 === 0 && y % 100 !== 0) || (y % 400 === 0); } function daysInMonth(y, m) { const dim = [31, isLeapGregorian(y) ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]; return dim[m - 1]; } function parseISODate(s, label) { const m = /^(-?\d{4})-(\d{2})-(\d{2})$/.exec(String(s).trim()); if (!m) throw new Error((label || "date") + " must be an ISO 8601 date YYYY-MM-DD, got: " + s); const year = parseInt(m[1], 10), month = parseInt(m[2], 10), day = parseInt(m[3], 10); if (month < 1 || month > 12) throw new Error((label || "date") + " month out of range 1-12"); if (day < 1 || day > daysInMonth(year, month)) throw new Error((label || "date") + " day out of range for that month"); return { year, month, day }; } function pad2(n) { return String(n).padStart(2, "0"); } function toISODate(y, m, d) { const ys = y < 0 ? "-" + String(-y).padStart(4, "0") : String(y).padStart(4, "0"); return ys + "-" + pad2(m) + "-" + pad2(d); } // ISO weekday number, Monday=1 .. Sunday=7 (ISO 8601), via day-number mod 7 calibrated // against 1970-01-01 (day-number 0) being a Thursday (ISO weekday 4). function isoWeekdayFromZ(z) { const idx = ((z % 7) + 7) % 7; // 0=Thursday return ((idx + 3) % 7) + 1; // shift so Thursday(idx0) -> 4, Friday(1)->5, ... Wed(6)->3 } function isWeekend(z) { const iso = isoWeekdayFromZ(z); return iso === 6 || iso === 7; } module.exports = { slug: "add-business-days", title: "Add business days to a date", deck_category: "dates", description: "Adds (or subtracts) a count of Monday-to-Friday business days to a date, treating Saturday and Sunday as non-business days and excluding NO holidays.", inputs: [ { name: "date", type: "string", format: "date", required: true, description: "ISO 8601 starting date, YYYY-MM-DD" }, { name: "business_days", type: "integer", required: true, description: "Number of business days to add; negative subtracts business days" } ], outputs: [ { name: "result_date", format: "date", description: "The resulting ISO 8601 date" } ], steps: [ "Parse the date as a proleptic Gregorian civil date and convert it to a day-number (Hinnant days_from_civil, cited above).", "Business-day convention used here: Monday through Friday are business days; Saturday and Sunday are not. This method does NOT exclude any holidays, only weekends.", "Step the day-number one day at a time in the sign of business_days (forward for positive, backward for negative).", "Each step, determine the ISO 8601 weekday (Monday=1 .. Sunday=7) from the day-number modulo 7; if it is not Saturday (6) or Sunday (7), count it against the remaining business-day total.", "Stop once the requested number of business days has been counted and convert the final day-number back to a civil date." ], constants: [], sources: [ { kind: "url", value: "https://howardhinnant.github.io/date_algorithms.html", title: "chrono-Compatible Low-Level Date Algorithms", publisher: "Howard Hinnant", retrieved_utc: "2026-09-30T19:41:35Z", note: "day-number kernel used for stepping and weekday parity" }, { kind: "standard", value: "ISO 8601:2004", title: "Data elements and interchange formats -- Information interchange -- Representation of dates and times", publisher: "International Organization for Standardization", retrieved_utc: "2026-09-30T19:41:35Z", note: "defines weekday numbering Monday=1 through Sunday=7, used to identify the Saturday/Sunday weekend excluded by this method" } ], disclaimer: null, questionText(inputs) { const n = inputs.business_days; return "What date is " + Math.abs(n) + (n >= 0 ? " business days after " : " business days before ") + inputs.date + " (Monday-Friday only, no holidays excluded)?"; }, run(inputs) { const d = parseISODate(inputs.date, "date"); if (!Number.isInteger(inputs.business_days)) throw new Error("business_days must be an integer"); let z = daysFromCivil(d.year, d.month, d.day); const step = inputs.business_days >= 0 ? 1 : -1; let remaining = Math.abs(inputs.business_days); while (remaining > 0) { z += step; if (!isWeekend(z)) remaining -= 1; } const r = civilFromDays(z); const answer = toISODate(r.year, r.month, r.day); return { answer, values: { result_date: answer } }; }, examples: [ { name: "worked example", inputs: { date: "2026-10-01", business_days: 5 }, expected: { answer: "2026-10-08" }, check: "2026-10-01 is a Thursday; counting forward Fri 10-02(1), skip Sat/Sun, Mon 10-05(2), Tue 10-06(3), Wed 10-07(4), Thu 10-08(5)" }, { name: "test vector: starting on a Saturday", inputs: { date: "2026-10-03", business_days: 1 }, expected: { answer: "2026-10-05" }, check: "2026-10-03 is a Saturday; the next business day, skipping Sunday 10-04, is Monday 2026-10-05" }, { name: "test vector: subtracting across a weekend", inputs: { date: "2026-10-06", business_days: -3 }, expected: { answer: "2026-10-01" }, check: "2026-10-06 is a Tuesday; counting back Mon 10-05(1), skip Sun/Sat, Fri 10-02(2), Thu 10-01(3)" } ] };