{"version":3,"file":"holidays.cjs","names":[],"sources":["../../src/br/holidays.ts"],"sourcesContent":["/**\n * How binding a non-working day is.\n *\n * - `\"national\"` — a feriado nacional in federal law. Nobody works, and a\n *   deadline that lands here moves.\n * - `\"banking\"` — not a statutory holiday, but a day the national financial\n *   system does not operate: Carnaval (Monday and Tuesday), Sexta-feira da\n *   Paixão and Corpus Christi. Bank branches are shut and compensation does not\n *   run, so a boleto or a TED dated here settles later — but an employer may\n *   legally require work, which is why the two kinds are separate.\n */\nexport type HolidayKind = \"national\" | \"banking\";\n\n/** One non-working day in a given year. */\nexport interface Holiday {\n    /** `YYYY-MM-DD` in the local calendar. */\n    date: string;\n    /** Portuguese name, as the law or the Bacen calendar spells it. */\n    name: string;\n    kind: HolidayKind;\n    /** `true` when the date is derived from Easter rather than fixed. */\n    movable: boolean;\n}\n\n/** Anything these helpers accept as a day. */\nexport type DateInput = Date | string;\n\n/** Options shared by the calendar helpers. */\nexport interface BusinessDayOptions {\n    /**\n     * Which kinds count as non-working. Default `[\"national\", \"banking\"]`, i.e.\n     * the Bacen calendar — the right default for anything money moves through.\n     * Pass `[\"national\"]` for a labour-law calendar.\n     */\n    kinds?: readonly HolidayKind[];\n    /**\n     * Extra non-working days, as `YYYY-MM-DD` or `Date`. This is where state and\n     * municipal holidays go: they are **not** in the built-in table and never will\n     * be — there are 5 570 municipalities, each free to declare its own.\n     */\n    extra?: readonly DateInput[];\n    /**\n     * Days of the week that are not worked, `0` = Sunday. Default `[0, 6]`.\n     */\n    weekend?: readonly number[];\n}\n\n/** Fixed-date national holidays, with the year each became one. */\nconst FIXED_HOLIDAYS: readonly {\n    month: number;\n    day: number;\n    name: string;\n    kind: HolidayKind;\n    since?: number;\n}[] = [\n    { month: 1, day: 1, name: \"Confraternização Universal\", kind: \"national\" },\n    { month: 4, day: 21, name: \"Tiradentes\", kind: \"national\" },\n    { month: 5, day: 1, name: \"Dia do Trabalho\", kind: \"national\" },\n    { month: 9, day: 7, name: \"Independência do Brasil\", kind: \"national\" },\n    { month: 10, day: 12, name: \"Nossa Senhora Aparecida\", kind: \"national\" },\n    { month: 11, day: 2, name: \"Finados\", kind: \"national\" },\n    { month: 11, day: 15, name: \"Proclamação da República\", kind: \"national\" },\n    {\n        month: 11,\n        day: 20,\n        name: \"Dia Nacional de Zumbi e da Consciência Negra\",\n        kind: \"national\",\n        since: 2024,\n    },\n    { month: 12, day: 25, name: \"Natal\", kind: \"national\" },\n];\n\n/** Easter-relative holidays, as a day offset from Easter Sunday. */\nconst MOVABLE_HOLIDAYS: readonly { offset: number; name: string; kind: HolidayKind }[] = [\n    { offset: -48, name: \"Carnaval (segunda-feira)\", kind: \"banking\" },\n    { offset: -47, name: \"Carnaval (terça-feira)\", kind: \"banking\" },\n    { offset: -2, name: \"Sexta-feira da Paixão\", kind: \"banking\" },\n    { offset: 60, name: \"Corpus Christi\", kind: \"banking\" },\n];\n\nconst DEFAULT_KINDS: readonly HolidayKind[] = [\"national\", \"banking\"];\nconst DEFAULT_WEEKEND: readonly number[] = [0, 6];\nconst MS_PER_DAY = 86_400_000;\n\n/**\n * How far a walk may go before it is treated as a bug rather than a long holiday.\n *\n * A real calendar never has more than a handful of consecutive non-working days,\n * so hitting this means `extra` or `weekend` marked every day off, and looping\n * forever is worse than an error.\n */\nconst MAX_WALK_DAYS = 400;\n\n/** `YYYY-MM-DD` from a local-calendar date. */\nfunction toIso(year: number, month: number, day: number): string {\n    return `${String(year).padStart(4, \"0\")}-${String(month).padStart(2, \"0\")}-${String(day).padStart(2, \"0\")}`;\n}\n\n/**\n * Local midnight for any accepted input.\n *\n * Everything here works on local calendar components, never on UTC: `new Date(y,\n * m, d)` and `getFullYear()/getMonth()/getDate()`. Going through `toISOString()`\n * would shift the day for every viewer east of Greenwich, and \"is today a\n * holiday\" is a question about the viewer's calendar.\n *\n * @throws {RangeError} When a string is not `YYYY-MM-DD` or a `Date` is invalid.\n */\nfunction toLocalDate(input: DateInput): Date {\n    if (typeof input === \"string\") {\n        const match = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(input.trim());\n        if (match === null) {\n            throw new RangeError(`Expected a YYYY-MM-DD date, got ${JSON.stringify(input)}.`);\n        }\n        return new Date(Number(match[1]), Number(match[2]) - 1, Number(match[3]));\n    }\n    if (Number.isNaN(input.getTime())) throw new RangeError(\"Received an Invalid Date.\");\n    return new Date(input.getFullYear(), input.getMonth(), input.getDate());\n}\n\n/** `YYYY-MM-DD` for any accepted input. */\nfunction isoOf(input: DateInput): string {\n    const date = toLocalDate(input);\n    return toIso(date.getFullYear(), date.getMonth() + 1, date.getDate());\n}\n\n/**\n * Easter Sunday in the Gregorian calendar.\n *\n * The anonymous Gregorian computus (Meeus/Jones/Butcher): pure integer\n * arithmetic over the year, no tables and no dependency. It is exact for every\n * Gregorian year, which is why the four movable Brazilian holidays are derived\n * from it rather than listed.\n *\n * @param year - Gregorian year.\n * @returns Local midnight of Easter Sunday.\n *\n * @example\n * easterSunday(2026); // 2026-04-05\n */\nexport function easterSunday(year: number): Date {\n    const a = year % 19;\n    const b = Math.floor(year / 100);\n    const c = year % 100;\n    const d = Math.floor(b / 4);\n    const e = b % 4;\n    const f = Math.floor((b + 8) / 25);\n    const g = Math.floor((b - f + 1) / 3);\n    const h = (19 * a + b - d - g + 15) % 30;\n    const i = Math.floor(c / 4);\n    const k = c % 4;\n    const l = (32 + 2 * e + 2 * i - h - k) % 7;\n    const m = Math.floor((a + 11 * h + 22 * l) / 451);\n    const month = Math.floor((h + l - 7 * m + 114) / 31);\n    const day = ((h + l - 7 * m + 114) % 31) + 1;\n    return new Date(year, month - 1, day);\n}\n\n/**\n * Every national non-working day in a year, ordered by date.\n *\n * **What is here:** the nine feriados nacionais in federal law (Lei 662/1949 as\n * amended by Lei 10.607/2002, Lei 6.802/1980 for 12 October, and Lei 14.759/2023\n * for 20 November, which is why that one only appears from 2024), plus the four\n * movable days the national financial system observes.\n *\n * **What is deliberately not here**, and will not be:\n *\n * - **State and municipal holidays.** A data magna varies by state and each of the\n *   5 570 municipalities may declare its own, including up to four religious\n *   days. No table can be both complete and current; pass them through\n *   `options.extra`.\n * - **Ponto facultativo.** A federal decree that lets public servants off is not a\n *   holiday and binds nobody else, so it changes no deadline.\n * - **Pre-2002 history.** The table encodes the law as it stands today. Asking for\n *   1998 returns today's set shifted to 1998, not what was in force then.\n *\n * Carnaval, Sexta-feira da Paixão and Corpus Christi are the interesting case:\n * none of them is a feriado nacional in federal law, yet CMN Resolução 4.880/2020\n * closes the banks on all four days, so a payment cannot settle. They are returned\n * with `kind: \"banking\"` — counted by default, and excluded by passing\n * `kinds: [\"national\"]`.\n *\n * @param year - Gregorian year.\n * @returns The holidays of that year, ascending by date.\n *\n * @example\n * holidaysFor(2026).filter((holiday) => holiday.kind === \"national\").length; // 9\n */\nexport function holidaysFor(year: number): Holiday[] {\n    const holidays: Holiday[] = FIXED_HOLIDAYS.filter(\n        (entry) => entry.since === undefined || year >= entry.since,\n    ).map((entry) => ({\n        date: toIso(year, entry.month, entry.day),\n        name: entry.name,\n        kind: entry.kind,\n        movable: false,\n    }));\n\n    const easter = easterSunday(year);\n    for (const entry of MOVABLE_HOLIDAYS) {\n        const date = new Date(easter.getTime() + entry.offset * MS_PER_DAY);\n        holidays.push({\n            date: toIso(date.getFullYear(), date.getMonth() + 1, date.getDate()),\n            name: entry.name,\n            kind: entry.kind,\n            movable: true,\n        });\n    }\n\n    return holidays.sort((left, right) => left.date.localeCompare(right.date));\n}\n\n/** The set of `YYYY-MM-DD` strings that count as off for a given year. */\nfunction offDays(year: number, options: BusinessDayOptions): Set<string> {\n    const kinds = options.kinds ?? DEFAULT_KINDS;\n    const days = new Set(\n        holidaysFor(year)\n            .filter((holiday) => kinds.includes(holiday.kind))\n            .map((holiday) => holiday.date),\n    );\n    for (const entry of options.extra ?? []) days.add(isoOf(entry));\n    return days;\n}\n\n/**\n * Whether a date is a national holiday.\n *\n * @param date - `YYYY-MM-DD` or a `Date`. Only the local calendar day matters.\n * @param options - See {@link BusinessDayOptions}. `weekend` is ignored here — a\n * Sunday is not a holiday, it is a Sunday.\n * @returns `true` when the day is in the table (or in `extra`).\n * @throws {RangeError} On a malformed string or an Invalid Date.\n *\n * @example\n * isHoliday(\"2026-11-20\"); // true\n * isHoliday(\"2026-02-17\"); // true — Carnaval\n * isHoliday(\"2026-02-17\", { kinds: [\"national\"] }); // false\n */\nexport function isHoliday(date: DateInput, options: BusinessDayOptions = {}): boolean {\n    const iso = isoOf(date);\n    return offDays(Number(iso.slice(0, 4)), options).has(iso);\n}\n\n/**\n * Whether a date is a working day: not a weekend, not a holiday.\n *\n * @param date - `YYYY-MM-DD` or a `Date`.\n * @param options - See {@link BusinessDayOptions}.\n * @returns `true` when work happens on that day.\n * @throws {RangeError} On a malformed string or an Invalid Date.\n *\n * @example\n * isBusinessDay(\"2026-04-03\"); // false — Sexta-feira da Paixão\n */\nexport function isBusinessDay(date: DateInput, options: BusinessDayOptions = {}): boolean {\n    const local = toLocalDate(date);\n    const weekend = options.weekend ?? DEFAULT_WEEKEND;\n    if (weekend.includes(local.getDay())) return false;\n    return !isHoliday(local, options);\n}\n\n/**\n * The first working day strictly after a date.\n *\n * Strictly after: calling it on a Wednesday returns Thursday, never the same\n * Wednesday. That is what a \"prazo de D+1\" means, and it makes the function safe\n * to call in a loop.\n *\n * @param date - `YYYY-MM-DD` or a `Date`.\n * @param options - See {@link BusinessDayOptions}.\n * @returns Local midnight of the next working day.\n * @throws {RangeError} On a malformed input, or when `options` marked so many days\n * off that no working day exists within {@link MAX_WALK_DAYS}.\n *\n * @example\n * nextBusinessDay(\"2026-12-24\"); // 2026-12-28 — the 25th is Natal, then a weekend\n */\nexport function nextBusinessDay(date: DateInput, options: BusinessDayOptions = {}): Date {\n    const cursor = toLocalDate(date);\n    for (let step = 0; step < MAX_WALK_DAYS; step += 1) {\n        cursor.setDate(cursor.getDate() + 1);\n        if (isBusinessDay(cursor, options)) return cursor;\n    }\n    throw new RangeError(\n        `No business day within ${MAX_WALK_DAYS} days of ${isoOf(date)} — check \\`weekend\\` and \\`extra\\`.`,\n    );\n}\n\n/**\n * Move a date by a number of working days.\n *\n * `n` days forward means `n` calls to {@link nextBusinessDay}; a negative `n`\n * walks backwards the same way. `n === 0` returns the day unchanged **even when it\n * is not a working day** — snapping silently would hide the case a caller most\n * needs to see.\n *\n * @param date - `YYYY-MM-DD` or a `Date`.\n * @param days - Working days to add. May be negative.\n * @param options - See {@link BusinessDayOptions}.\n * @returns Local midnight of the resulting day.\n * @throws {RangeError} On a malformed input, or on a calendar with no working days.\n *\n * @example\n * addBusinessDays(\"2026-04-01\", 2); // 2026-04-06 — skips Good Friday and the weekend\n */\nexport function addBusinessDays(\n    date: DateInput,\n    days: number,\n    options: BusinessDayOptions = {},\n): Date {\n    const cursor = toLocalDate(date);\n    const direction = days < 0 ? -1 : 1;\n    for (let moved = 0; moved < Math.abs(days); moved += 1) {\n        let landed = false;\n        for (let step = 0; step < MAX_WALK_DAYS && !landed; step += 1) {\n            cursor.setDate(cursor.getDate() + direction);\n            landed = isBusinessDay(cursor, options);\n        }\n        if (!landed) {\n            throw new RangeError(\n                `No business day within ${MAX_WALK_DAYS} days while walking from ${isoOf(date)} — ` +\n                    \"check `weekend` and `extra`.\",\n            );\n        }\n    }\n    return cursor;\n}\n"],"mappings":"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"}