"use strict"; // Day-number kernel: Howard Hinnant, "chrono-Compatible Low-Level Date Algorithms", // https://howardhinnant.github.io/date_algorithms.html (days_from_civil). Verified against // two independent known facts: 1970-01-01 (the Unix epoch civil day) converts to Julian Day // Number 2440588, and 2000-01-01 converts to JDN 2451545 (the well-known J2000.0 epoch date). // No host clock, no Date.now(), no time zone database; pure proleptic Gregorian civil dates. 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; } const EPOCH_JDN = 2440588; function toJDN(y, m, d) { return daysFromCivil(y, m, d) + EPOCH_JDN; } 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 }; } module.exports = { slug: "days-between-dates", title: "Days between two dates", deck_category: "dates", description: "Counts the whole days between two calendar dates, inclusive or exclusive of both endpoints.", inputs: [ { name: "date_a", type: "string", format: "date", required: true, description: "Earlier ISO 8601 date, YYYY-MM-DD" }, { name: "date_b", type: "string", format: "date", required: true, description: "Later or equal ISO 8601 date, YYYY-MM-DD" }, { name: "inclusive", type: "boolean", required: true, description: "If true, count both date_a and date_b as elapsed days; if false, count only the nights between them" } ], outputs: [ { name: "days", unit: "days", description: "Number of days between the two dates under the chosen rule" } ], steps: [ "Parse date_a and date_b as proleptic Gregorian civil dates (year, month, day).", "Convert each date to a Julian Day Number (JDN) using the Fliegel and Van Flandern (1968) day-count method, implemented here via the equivalent Hinnant days_from_civil algorithm (cited above).", "Require date_b to be on or after date_a; otherwise the inputs are invalid.", "diff = JDN(date_b) - JDN(date_a). This is the exclusive count: the number of nights, or day-boundaries crossed, between the two dates.", "If inclusive is true, days = diff + 1 (both endpoint calendar days are counted). If inclusive is false, days = diff." ], 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: "days_from_civil / civil_from_days, the day-number kernel used by this method; verified against 1970-01-01 -> JDN 2440588 and 2000-01-01 -> JDN 2451545" } ], disclaimer: null, questionText(inputs) { return "How many days are there between " + inputs.date_a + " and " + inputs.date_b + (inputs.inclusive ? ", counting both dates" : ", not counting either date") + "?"; }, run(inputs) { const a = parseISODate(inputs.date_a, "date_a"); const b = parseISODate(inputs.date_b, "date_b"); if (typeof inputs.inclusive !== "boolean") throw new Error("inclusive must be a boolean"); const jdnA = toJDN(a.year, a.month, a.day); const jdnB = toJDN(b.year, b.month, b.day); if (jdnB < jdnA) throw new Error("date_b must not be before date_a"); const diff = jdnB - jdnA; const days = inputs.inclusive ? diff + 1 : diff; return { answer: String(days), values: { days } }; }, examples: [ { name: "worked example", inputs: { date_a: "2024-01-01", date_b: "2024-01-10", inclusive: false }, expected: { answer: "9" }, check: "independently counted: Jan 2, 3, 4, 5, 6, 7, 8, 9, 10 is 9 elapsed days after Jan 1" }, { name: "test vector: inclusive count", inputs: { date_a: "2024-01-01", date_b: "2024-01-10", inclusive: true }, expected: { answer: "10" }, check: "same span plus 1 for counting the start date itself" }, { name: "test vector: leap day crossing", inputs: { date_a: "2024-02-28", date_b: "2024-03-01", inclusive: false }, expected: { answer: "2" }, check: "2024 is a leap year, so the span is Feb 28 -> Feb 29 -> Mar 1, which is 2 days" } ] };