"use strict"; // Day-number kernel: Howard Hinnant, "chrono-Compatible Low-Level Date Algorithms", // https://howardhinnant.github.io/date_algorithms.html (days_from_civil). Fixed-offset // arithmetic only: no time zone database, no host clock, no Date.now(). 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 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 parseTime(s) { const m = /^(\d{2}):(\d{2})(?::(\d{2}))?$/.exec(String(s).trim()); if (!m) throw new Error("time must be HH:MM or HH:MM:SS, got: " + s); const hh = parseInt(m[1], 10), mm = parseInt(m[2], 10), ss = m[3] ? parseInt(m[3], 10) : 0; if (hh > 23 || mm > 59 || ss > 59) throw new Error("time fields out of range: " + s); return hh * 3600 + mm * 60 + ss; } function parseOffset(s) { const m = /^([+-])(\d{2}):(\d{2})$/.exec(String(s).trim()); if (!m) throw new Error("offset must be like -05:00 or +01:00, got: " + s); const sign = m[1] === "-" ? -1 : 1; const hh = parseInt(m[2], 10), mm = parseInt(m[3], 10); if (hh > 23 || mm > 59) throw new Error("offset fields out of range: " + s); return sign * (hh * 3600 + mm * 60); } function instantSeconds(dateStr, timeStr, offsetStr, label) { const d = parseISODate(dateStr, label + "_date"); const local = parseTime(timeStr); const off = parseOffset(offsetStr); return daysFromCivil(d.year, d.month, d.day) * 86400 + local - off; } module.exports = { slug: "flight-duration-across-zones", title: "Flight duration across time zones", deck_category: "timezones", description: "Computes elapsed travel time from a departure local date/time/offset to an arrival local date/time/offset.", inputs: [ { name: "departure_date", type: "string", format: "date", required: true, description: "ISO 8601 departure date, YYYY-MM-DD, local to departure_offset" }, { name: "departure_time", type: "string", required: true, description: "Departure local wall-clock time, HH:MM or HH:MM:SS" }, { name: "departure_offset", type: "string", required: true, description: "Fixed UTC offset at departure, e.g. -05:00" }, { name: "arrival_date", type: "string", format: "date", required: true, description: "ISO 8601 arrival date, YYYY-MM-DD, local to arrival_offset" }, { name: "arrival_time", type: "string", required: true, description: "Arrival local wall-clock time, HH:MM or HH:MM:SS" }, { name: "arrival_offset", type: "string", required: true, description: "Fixed UTC offset at arrival, e.g. +01:00" } ], outputs: [ { name: "hours", description: "Whole hours of elapsed time" }, { name: "minutes", description: "Remaining minutes of elapsed time" } ], steps: [ "Parse the departure date, time and offset into a single UTC instant, in seconds: day-number (Hinnant days_from_civil, cited above) times 86400, plus local seconds-since-midnight, minus the offset in seconds.", "Do the same for the arrival date, time and offset to get the arrival UTC instant.", "elapsed_seconds = arrival_utc_instant - departure_utc_instant; require this to be positive.", "hours = floor(elapsed_seconds / 3600); minutes = floor((elapsed_seconds mod 3600) / 60)." ], 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 to place both wall-clock times on a single UTC instant line; no time zone database is used" } ], disclaimer: null, questionText(inputs) { return "Departing " + inputs.departure_date + " " + inputs.departure_time + " " + inputs.departure_offset + " and arriving " + inputs.arrival_date + " " + inputs.arrival_time + " " + inputs.arrival_offset + ", how long was the flight?"; }, run(inputs) { const dep = instantSeconds(inputs.departure_date, inputs.departure_time, inputs.departure_offset, "departure"); const arr = instantSeconds(inputs.arrival_date, inputs.arrival_time, inputs.arrival_offset, "arrival"); const elapsed = arr - dep; if (elapsed <= 0) throw new Error("arrival instant must be after departure instant"); const hours = Math.floor(elapsed / 3600); const minutes = Math.floor((elapsed % 3600) / 60); const answer = hours + "h " + minutes + "m"; return { answer, values: { hours, minutes } }; }, examples: [ { name: "worked example", inputs: { departure_date: "2026-09-30", departure_time: "08:00:00", departure_offset: "-05:00", arrival_date: "2026-09-30", arrival_time: "21:35:00", arrival_offset: "+01:00" }, expected: { answer: "7h 35m" }, check: "08:00 -05:00 is 13:00 UTC; 21:35 +01:00 is 20:35 UTC; 20:35 minus 13:00 is 7 hours 35 minutes" }, { name: "test vector: overnight, crosses a calendar date", inputs: { departure_date: "2026-03-01", departure_time: "22:00:00", departure_offset: "-05:00", arrival_date: "2026-03-02", arrival_time: "11:30:00", arrival_offset: "+01:00" }, expected: { answer: "7h 30m" }, check: "22:00 -05:00 on Mar 1 is 03:00 UTC Mar 2; 11:30 +01:00 on Mar 2 is 10:30 UTC Mar 2; 10:30 minus 03:00 is 7 hours 30 minutes" }, { name: "test vector: both local times identical but different offsets", inputs: { departure_date: "2026-05-01", departure_time: "09:00:00", departure_offset: "+09:00", arrival_date: "2026-05-01", arrival_time: "09:00:00", arrival_offset: "-08:00" }, expected: { answer: "17h 0m" }, check: "09:00 +09:00 is 00:00 UTC; 09:00 -08:00 is 17:00 UTC on the same UTC date; 17:00 minus 00:00 is 17 hours" } ] };