"use strict"; // Day-number kernel: Howard Hinnant, "chrono-Compatible Low-Level Date Algorithms", // https://howardhinnant.github.io/date_algorithms.html (days_from_civil). No host clock, no // Date.now(), no time zone database. 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: "day-of-year-and-days-remaining", title: "Day of year and days remaining", deck_category: "dates", description: "Computes a date's ordinal position within its calendar year and how many days are left in that year.", inputs: [ { name: "date", type: "string", format: "date", required: true, description: "ISO 8601 date, YYYY-MM-DD" } ], outputs: [ { name: "day_of_year", unit: "days", description: "Ordinal day within the year, 1 to 365 or 366" }, { name: "days_remaining", unit: "days", description: "Days left in the calendar year after this date" } ], steps: [ "Parse the date as a proleptic Gregorian civil date.", "Convert the date and January 1 of the same year to Julian Day Numbers (Hinnant days_from_civil, cited above).", "day_of_year = JDN(date) - JDN(year, 1, 1) + 1.", "days_in_year = 366 if the year is a Gregorian leap year, else 365.", "days_remaining = days_in_year - day_of_year." ], 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 the ordinal-day subtraction" } ], disclaimer: null, questionText(inputs) { return "What day of the year is " + inputs.date + ", and how many days remain in that year?"; }, run(inputs) { const d = parseISODate(inputs.date, "date"); const dayOfYear = toJDN(d.year, d.month, d.day) - toJDN(d.year, 1, 1) + 1; const daysInYear = isLeapGregorian(d.year) ? 366 : 365; const daysRemaining = daysInYear - dayOfYear; const answer = "Day " + dayOfYear + " of " + daysInYear + "; " + daysRemaining + " days remaining in the year"; return { answer, values: { day_of_year: dayOfYear, days_remaining: daysRemaining } }; }, examples: [ { name: "worked example", inputs: { date: "2024-03-01" }, expected: { answer: "Day 61 of 366; 305 days remaining in the year" }, check: "2024 is a leap year: January has 31 days and February has 29, so March 1 is day 31+29+1 = 61 of 366" }, { name: "test vector: last day of a non-leap year", inputs: { date: "2023-12-31" }, expected: { answer: "Day 365 of 365; 0 days remaining in the year" }, check: "2023 is not a leap year, so it has 365 days and Dec 31 is the last one" }, { name: "test vector: first day of year", inputs: { date: "2026-01-01" }, expected: { answer: "Day 1 of 365; 364 days remaining in the year" }, check: "2026 is not a leap year (not divisible by 4); January 1 is always day 1" } ] };