@verifyhash/moon-phase
v0.1.0
Published
Zero-dependency lunar phase & illumination math (simplified Meeus method): phase angle, illuminated fraction, synodic age, canonical phase name, and Brown lunation number for any UTC instant.
Maintainers
Readme
moon-phase
TODO (owner): pick the final npm name/scope before publishing. The npm package name is not finalized by this project. Convention used here:
package.jsonkeeps the working slugmoon-phaseas a placeholder — replace it (and thenpm installline below) with the real published name/scope before runningnpm publish.
A zero-dependency, pure-function Node library that turns any instant into the Moon's phase: its phase angle, the fraction of the disk that is lit, the synodic age in days, the canonical phase name, and the Brown lunation number.
No network, no external dependencies, no servers, no I/O. Every export is a pure
function of a single Date and never mutates its argument. require() it and go.
const moon = require('./index.js');
const d = new Date('2024-01-25T17:54:00Z'); // a full moon
moon.phaseName(d); // 'Full'
moon.illuminatedFraction(d); // ~1.0
moon.moonAge(d); // ~14.75 (days since the last new moon)
moon.moonPhaseAngle(d); // ~179.85 (Sun-Moon elongation, degrees)
moon.lunation(d); // 1250 (Brown Lunation Number)Who it's for
Developers who need the Moon's phase without pulling in a heavyweight ephemeris:
- Weather / almanac apps — show tonight's phase and % illumination next to the forecast.
- Astronomy & astrophotography planners — a dark-sky (new moon) or full-moon window finder, or a "how washed-out will the sky be" estimate.
- Tide and fishing tools — spring/neap tides track the new/full syzygy, and the phase angle is a cheap proxy for it.
- Calendar / UI widgets — a correct moon glyph for any date.
It is a geocentric phase model: it answers "what phase is the Moon in?", not "when does the Moon rise here?" or "where is it in my sky?". For observer-local rise/set/altitude you want a different tool (e.g. a full ephemeris).
API
All functions take a JS Date interpreted as a UTC instant (date and
time). The time of day matters: the Moon moves ~12°/day, so one hour shifts the
elongation by ~0.5°. A date-only value like new Date('2024-01-11') is treated
as 00:00 UTC that day.
| Function | Returns |
| --- | --- |
| moonPhaseAngle(date) | Sun-Moon elongation in degrees, [0, 360). 0° = New, 90° = First Quarter, 180° = Full, 270° = Last Quarter. |
| illuminatedFraction(date) | Fraction of the disk lit, [0, 1]. 0 at new, 1 at full, ~0.5 at the quarters. |
| moonAge(date) | Days since the last new moon (synodic age), [0, ~29.53). |
| phaseName(date) | One of the 8 canonical phases: New, Waxing Crescent, First Quarter, Waxing Gibbous, Full, Waning Gibbous, Last Quarter, Waning Crescent. |
| lunation(date) | Brown Lunation Number (integer) of the lunation in progress. |
phaseName bins the elongation into eight 45°-wide sectors, each centred on its
defining angle — so New covers 337.5°–360°–22.5°, First Quarter covers
67.5°–112.5°, and so on. The narrow "instant" phases (New, the two quarters,
Full) are the sectors straddling their exact angles.
Algorithm & citation
The geometry follows Jean Meeus, Astronomical Algorithms (2nd ed., 1998):
- Evaluate the Moon's mean elongation
D, the Sun's mean anomalyM, and the Moon's mean anomalyM′from the standard polynomials in Julian centuriesTfrom J2000.0 (Meeus ch. 47). - Form the true Sun-Moon elongation by adding the leading periodic terms of the
phase-angle series (Meeus ch. 48, eq. 48.4 — the published "low accuracy"
method), giving the phase angle
iand elongation180° − i. - Illuminated fraction
k = (1 + cos i) / 2(Meeus eq. 48.1).
The synodic month is taken as 29.530588853 days (Meeus ch. 47). The Brown
lunation number is anchored to the new moon of 2000-01-06 18:14 UTC
(Meeus lunation index k = 0), whose Brown Lunation Number is 953; Brown
lunation 1 was the new moon of 1923-01-17.
Accuracy — honest limits
This is a simplified model. It keeps only the largest periodic terms and no planetary perturbations, so:
- Phase angle / elongation: within about 0.5° of the full Meeus solution for dates within a couple of centuries of J2000.
- Illuminated fraction: within about 1% — a few percent near a thin crescent, where a 0.5° angle error has the most leverage.
- Synodic age (
moonAge): derived from the true elongation via the mean synodic month, so it can drift up to roughly 0.5 day from a true almanac age, because the Moon's angular speed varies over its ~27.5-day anomalistic (perigee/apogee) cycle. Treat it as good to a fraction of a day, not to the minute. - No topocentric / parallax correction: this is the geocentric phase, which is what "the phase of the Moon" almost always means.
If you need to-the-minute event times (exact new/full moon instants) or a sky-position ephemeris, use a full-precision library instead. This one is for "what phase, how lit, how old, what's it called" — fast, dependency-free, and accurate enough for UI, planning, and forecasting overlays.
Tests
Run the full suite (a tiny built-in assert harness — no framework, no deps):
node test/moon.test.jsIt exits non-zero on any failure. Fixtures use published new/full/quarter-moon instants (NASA GSFC phase tables / timeanddate.com), and the suite also checks that illumination increases monotonically across the waxing half-lunation, that the phase-name bins are ordered and complete over a lunation, that inputs are not mutated, and that invalid input throws. Stated tolerances: 1.5° on the phase angle at a documented event, 0.02 on the illuminated fraction at the quarters, and 0.15 day on the synodic age.
License
MIT.
Install
Placeholder name: the
moon-phasebelow is the working slug, not a finalized npm name — see the owner TODO near the top of this README.
npm install moon-phaseconst moon = require('moon-phase');
const d = new Date('2024-01-25T17:54:00Z'); // a full moon
moon.phaseName(d); // 'Full'
moon.illuminatedFraction(d); // ~1.0
moon.lunation(d); // 1250 (Brown Lunation Number)