"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 uses the day-number modulo 7, calibrated against 1970-01-01 (day-number 0) // being a Thursday. DST rule dates are computed, never looked up from a time zone database, // per the Energy Policy Act of 2005 rule (15 U.S.C. 260a), cited below. No host clock, no // Date.now(). Input is a DATE ONLY; the Act's transitions occur at 2:00 a.m. LOCAL time, so // on the two transition dates themselves this method reports the majority-of-day state // (spring-forward day counted as DST already in effect; fall-back day counted as DST already // ended), which it documents explicitly rather than requiring a time-of-day input. 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) { return String(y).padStart(4, "0") + "-" + pad2(m) + "-" + pad2(d); } const WEEKDAY_NAMES = ["Thursday", "Friday", "Saturday", "Sunday", "Monday", "Tuesday", "Wednesday"]; const ISO_ORDER = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]; function isoWeekday(y, m, d) { const z = daysFromCivil(y, m, d); return ISO_ORDER.indexOf(WEEKDAY_NAMES[((z % 7) + 7) % 7]) + 1; } // nth (n>0) or last (n=-1) occurrence of an ISO weekday (1=Mon..7=Sun) in month y-m function nthWeekdayOfMonth(y, m, n, weekdayTarget) { const first = isoWeekday(y, m, 1); let delta = weekdayTarget - first; if (delta < 0) delta += 7; const day = 1 + delta + (n - 1) * 7; return { year: y, month: m, day }; } module.exports = { slug: "us-dst-in-effect", title: "US daylight saving time in effect?", deck_category: "timezones", description: "Determines whether a given date falls within US daylight saving time under the federal rule, and reports that year's transition dates.", inputs: [ { name: "date", type: "string", format: "date", required: true, description: "ISO 8601 date to check, YYYY-MM-DD (year must be 2007 or later, when the current US rule took effect)" } ], outputs: [ { name: "dst_in_effect", type: "boolean", description: "True if the date falls within US daylight saving time" }, { name: "dst_start", format: "date", description: "Second Sunday in March of that year, when DST begins" }, { name: "dst_end", format: "date", description: "First Sunday in November of that year, when DST ends" } ], steps: [ "Parse the date as a proleptic Gregorian civil date.", "Find the second Sunday of March and the first Sunday of November of the same year, using ISO weekday parity from the day-number kernel (Hinnant days_from_civil, cited above).", "DST is in effect for dates on or after the second Sunday of March and before the first Sunday of November.", "This method takes a date only (no time of day): the Act's transitions occur at 2:00 a.m. local time, so this method treats the start date itself as already in DST and the end date itself as already back to standard time." ], constants: [ { name: "dst_start_rule", value: "second Sunday in March", unit: "rule", source: 0 }, { name: "dst_end_rule", value: "first Sunday in November", unit: "rule", source: 0 }, { name: "rule_effective_from_year", value: 2007, unit: "year", source: 0 } ], sources: [ { kind: "statute", value: "15 U.S.C. 260a", title: "Energy Policy Act of 2005, Section 110 (daylight saving time)", publisher: "United States Code", retrieved_utc: "2026-09-30T19:41:35Z", note: "sets US DST from the second Sunday in March to the first Sunday in November, transitions at 2:00 a.m. local time, effective 2007 onward" } ], disclaimer: null, questionText(inputs) { return "Is US daylight saving time in effect on " + inputs.date + "?"; }, run(inputs) { const d = parseISODate(inputs.date, "date"); if (d.year < 2007) throw new Error("this rule only applies from 2007 onward"); const start = nthWeekdayOfMonth(d.year, 3, 2, 7); const end = nthWeekdayOfMonth(d.year, 11, 1, 7); const z = daysFromCivil(d.year, d.month, d.day); const zStart = daysFromCivil(start.year, start.month, start.day); const zEnd = daysFromCivil(end.year, end.month, end.day); const inEffect = z >= zStart && z < zEnd; const dstStart = toISODate(start.year, start.month, start.day); const dstEnd = toISODate(end.year, end.month, end.day); const answer = (inEffect ? "yes" : "no") + " (DST " + d.year + ": " + dstStart + " to " + dstEnd + ")"; return { answer, values: { dst_in_effect: inEffect, dst_start: dstStart, dst_end: dstEnd } }; }, examples: [ { name: "worked example", inputs: { date: "2026-07-04" }, expected: { answer: "yes (DST 2026: 2026-03-08 to 2026-11-01)" }, check: "July 4 clearly falls between the second Sunday of March 2026 (March 8) and the first Sunday of November 2026 (Nov 1)" }, { name: "test vector: before DST starts", inputs: { date: "2026-01-15" }, expected: { answer: "no (DST 2026: 2026-03-08 to 2026-11-01)" }, check: "January is always before the second Sunday of March" }, { name: "test vector: on the end transition date", inputs: { date: "2026-11-01" }, expected: { answer: "no (DST 2026: 2026-03-08 to 2026-11-01)" }, check: "2026-11-01 is the first Sunday of November 2026, the documented fall-back date, so it is reported as already outside DST" } ] };