"use strict"; function round2(x) { return Math.round(x * 100) / 100; } function parseUtcDate(s) { const parts = String(s).split("-").map(Number); const y = parts[0], m = parts[1], d = parts[2]; return Date.UTC(y, m - 1, d, 0, 0, 0); } const SYNODIC_MONTH_DAYS = 29.530588853; // Meeus, Astronomical Algorithms, 2nd ed., ch. 49, p. 349 const REF_NEW_MOON_UTC_MS = Date.UTC(2000, 0, 6, 18, 14, 0); // Meeus ch. 49, k = 0 reference new moon const PHASE_NAMES = [ "New Moon", "Waxing Crescent", "First Quarter", "Waxing Gibbous", "Full Moon", "Waning Gibbous", "Last Quarter", "Waning Crescent" ]; function phaseFromAge(ageDays, synodic) { const b = synodic / 16; const edges = [b, b * 3, b * 5, b * 7, b * 9, b * 11, b * 13, b * 15]; for (let i = 0; i < edges.length; i++) { if (ageDays < edges[i]) return PHASE_NAMES[i]; } return PHASE_NAMES[0]; } function compute(dateStr) { const targetMs = parseUtcDate(dateStr); const diffDays = (targetMs - REF_NEW_MOON_UTC_MS) / 86400000; const age = ((diffDays % SYNODIC_MONTH_DAYS) + SYNODIC_MONTH_DAYS) % SYNODIC_MONTH_DAYS; const phase = phaseFromAge(age, SYNODIC_MONTH_DAYS); return { phase, age: round2(age) }; } module.exports = { slug: "moon-phase-by-synodic-month", title: "Moon phase from a mean synodic month", deck_category: "astronomy", description: "Estimates the moon phase name and age in days for a given date using the mean synodic month from a cited reference new moon.", inputs: [ { name: "date", type: "string", unit: "ISO date (YYYY-MM-DD, taken as 00:00 UTC)", required: true, description: "Calendar date to evaluate, interpreted at 00:00 UTC." } ], outputs: [ { name: "phase", unit: "phase name", description: "One of the eight named moon phases." }, { name: "age", unit: "days", description: "Days since the most recent mean new moon, 0 to about 29.53." } ], steps: [ "Take a cited reference new moon: 2000-01-06 18:14 UTC (Meeus, Astronomical Algorithms, 2nd ed., chapter 49).", "Take the mean synodic month: 29.530588853 days (Meeus, same chapter).", "Compute the number of days between the reference new moon and the target date at 00:00 UTC.", "Reduce that count modulo the synodic month, wrapping negative results into 0..synodic month, to get the moon's age in days.", "Divide the synodic month into 8 named phases at boundaries of 1, 3, 5, 7, 9, 11, 13, and 15 sixteenths of the synodic month, and report the phase whose range contains the age.", "Round the reported age to 2 decimal places.", "This is a mean-motion approximation, not a full ephemeris; the true phase time can differ from this estimate by up to about half a day because the Moon's actual orbital speed varies." ], constants: [ { name: "synodic_month_days", value: 29.530588853, unit: "days", source: 0 }, { name: "reference_new_moon_utc", value: "2000-01-06T18:14:00Z", unit: "UTC datetime", source: 0 } ], sources: [ { kind: "edition_page", value: "Meeus, Jean. Astronomical Algorithms, 2nd ed. Willmann-Bell, 1998.", title: "Astronomical Algorithms, 2nd ed., Chapter 49 (Phases of the Moon), p. 349", publisher: "Willmann-Bell", retrieved_utc: "2026-09-30T19:40:00Z", note: "Source of the mean synodic month constant and the k=0 reference new moon epoch only. Chapter 49 itself computes the full phase time from about 25 periodic correction terms for roughly 2-minute accuracy against real ephemeris data; that full algorithm is NOT implemented here. This method is the simpler mean-motion approximation (constant period from a single reference epoch), which can be off by up to roughly half a day at points in the synodic cycle." } ], disclaimer: null, questionText(inputs) { return `What is the approximate moon phase on ${inputs.date} (00:00 UTC), using the mean synodic month from the 2000-01-06 18:14 UTC reference new moon?`; }, run(inputs) { if (!inputs || typeof inputs.date !== "string" || !/^\d{4}-\d{2}-\d{2}$/.test(inputs.date)) { throw new Error("date must be a string in YYYY-MM-DD format"); } const { phase, age } = compute(inputs.date); return { answer: `${phase}, age ${age.toFixed(2)} days`, values: { phase, age_days: age } }; }, examples: [ { name: "worked example", inputs: { date: "2000-01-07" }, expected: { answer: "New Moon, age 0.24 days" }, check: "By hand: 2000-01-07 00:00 UTC is 5h46m (0.2403 days) after the cited 2000-01-06 18:14 UTC reference new moon, so the age is that same small value and the phase is New Moon by definition of the reference epoch." }, { name: "test vector: nine days after reference", inputs: { date: "2000-01-16" }, expected: { answer: "Waxing Gibbous, age 9.24 days" }, check: "By hand: 2000-01-16 00:00 UTC is 9 days 5h46m (9.2403 days) after the reference new moon. The First Quarter band is 5*29.530588853/16=9.228 up to 7*29.530588853/16=12.920 days, but the boundary comparison in steps uses a strict less-than at each edge, so 9.2403 (just past the 9.228 edge) falls in the Waxing Gibbous band, which spans from that same 9.228-day edge up to the next one." } ] };