moonphase
Compute and chart microphases of the Moon — arbitrary divisions of the synodic cycle — across a date range of any length, and pin down the exact instant of every phase.
The standard four phases (new, first quarter, full, last quarter) are just
--divisions 4. Want 32 phases? --divisions 32. Want one tick per degree of
Sun–Moon elongation? --step 1deg (= 360 microphases per cycle).
Concepts
The synodic cycle is 360° of Sun–Moon elongation: 0° = new, 90° = first quarter, 180° = full, 270° = last quarter.
- Microphase (phase) — one of
Nequal arcs of the cycle, centered on its phase center. For--divisions 4the microphases are the classical phases. "Phase" and "microphase" are synonyms; microphase just emphasizes thatNis arbitrary. - Phase center — the angle
k·stepat the middle of microphasek(the exact New-moon instant, Full-moon instant, …). These are what event mode locates precisely. - Transition point — the angle
(k+½)·step, the boundary between two adjacent microphases. A separate, opt-in category (--transitions), not a finer division.
Two ways to get data out:
--mode series(default) — sample the phase angle at a fixed cadence (--sample, default 1h) and bucket each sample into a microphase. Good for charts and dense time-series.--mode events— root-find the exact UTC instants of each phase center (and, with--transitions, each transition point) to sub-second precision, independent of cadence. Requires a step that divides 360° evenly (every--divisionsscheme qualifies).
--mode auto-resolves from --format when omitted, so you rarely need it.
Installation
Requires Python ≥ 3.10.
# From PyPI
pip install moonphase
# From source
git clone https://github.com/Bezoar/moonphase.git
cd moonphase
python -m venv .venv && source .venv/bin/activate
pip install -e . # use ".[dev]" to also get pytest + ruff
This installs the moonphase console script (also runnable as
python -m moonphase.cli). The JPL DE421 ephemeris (~17 MB) downloads to
./data/ on first use — see Ephemeris.
Usage
Every run needs a date range and a division scheme
(--divisions N or --step Xdeg); everything else has defaults.
Series mode (default) — sampled time-series
# 32 microphases across 2026 as a PNG strip-chart
moonphase --start 2026-01-01 --end 2026-12-31 --divisions 32 --out chart.png
# 1°-resolution phase angle for January as CSV (360 microphases per cycle)
moonphase --start 2026-01-01 --end 2026-01-31 --step 1deg --format csv --out jan.csv
# 8-phase terminal calendar — one row of moon glyphs per day
moonphase --start 2026-01-01 --end 2026-01-31 --divisions 8 --format terminal
# Tighter sampling cadence (default is 1h)
moonphase --start 2026-01-01 --end 2026-01-07 --divisions 8 --sample 10m --format json
Events mode — exact instants
# Exact New / First-Quarter / Full / Last-Quarter instants for 2026, as CSV
moonphase --start 2026-01-01 --end 2026-12-31 --divisions 4 \
--mode events --format csv --out phases-2026.csv
# ...also include the transition points (the midpoints between phases)
moonphase --start 2026-01-01 --end 2026-12-31 --divisions 4 \
--mode events --transitions --format json --out phases-2026.json
# Events to stdout (omit --out)
moonphase --start 2026-03-01 --end 2026-03-31 --divisions 8 --mode events
--mode is optional — it auto-resolves from --format (multi-mode formats
default to series). --sample applies only in series mode. Events mode needs a
step that divides 360° evenly, which every --divisions N satisfies.
Calendar & almanac views
# Year heatmap tinted by microphase index (16 hues)
moonphase --start 2026-01-01 --end 2026-12-31 --divisions 16 \
--format heatmap --tint index --out year.png
# Lunar-month heatmap: one phase-aligned strip per lunation (new-moon boundaries)
moonphase --start 2026-01-01 --end 2026-12-31 --divisions 16 \
--format heatmap --calendar lunar --out lunar.png
# Almanac ribbon of the principal phases (+ transitions) for a quarter
moonphase --start 2026-01-01 --end 2026-03-31 --divisions 4 \
--format almanac --transitions --out almanac.svg
# Custom names: rename the four phases (inline), or name 16 gradations from a file
moonphase --start 2026-01-01 --end 2026-03-31 --divisions 4 --format almanac \
--labels "Dark Moon,Building,Bright Moon,Fading" --out named.svg
moonphase --start 2026-01-01 --end 2026-12-31 --divisions 16 --format chart \
--labels @names16.txt --out year-named.png
heatmap is series-mode; almanac is events-mode — both auto-resolve --mode.
--labels accepts an inline comma list or @file (one name per line, or a JSON
index→name map); unnamed slots fall back to the built-in names (for 4/8 divisions)
or the microphase index.
A 2-column @file CSV (Full Name,AB per line, row order = microphase index) also sets a short abbreviation per phase. In --tint index heatmaps those codes are drawn in each day cell with a labelled swatch legend beneath the grid. See examples/moon-mother-16.csv.
# 16 Moon-Mother phases: 2-letter codes in each cell + a labelled legend,
# a custom title, and a book-citation footer
moonphase --start 2026-01-01 --end 2026-12-31 --divisions 16 --format heatmap \
--tint index --labels @examples/moon-mother-16.csv \
--title "2026 — Faces of the Moon Mother" \
--footer "Names: The Faces of the Moon Mother (ISBN 0-9624716-2-3)" --out mother.png
Timezones: bare dates use your local time; pass an ISO offset (e.g.
2026-01-01T00:00-08:00or…Z) to pin a zone. Output carries the offset, conversions are DST-aware, and every render states its timezone.
CLI
moonphase --start DATE --end DATE
(--divisions N | --step Xdeg)
[--mode {series,events}] # default: auto from --format, else series
[--transitions] # include transition points
[--sample DUR] # series cadence (e.g. 30m, 1h, 2d); series mode only
[--format {chart,heatmap,almanac,csv,json,terminal}]
[--theme {dark,light}] # color theme (default: dark)
[--tint {illumination,index}] # heatmap cell tint
[--calendar {gregorian,lunar}] # heatmap layout
[--lunar-anchor {new,full}] # lunar-month boundary
[--size WxH] # output image size in px (e.g. 5000x3000)
[--cell-times] # heatmap: print transition times in cells (needs --transitions, gregorian)
[--font NAME|PATH] # font family name or .ttf/.otf path for heatmap text
[--labels SPEC] # names: "A,B,C" or @file (lines / JSON / name,abbrev CSV)
[--title TEXT] # custom chart title (chart/heatmap/almanac)
[--footer TEXT] # free-text footer line (chart/heatmap/almanac)
[--out PATH] # stdout / window if omitted, where applicable
[--ephemeris PATH.bsp] # override the bundled-kernel download
--start/--end accept YYYY-MM-DD or full ISO 8601. Exactly one of
--divisions / --step is required.
Renderers
Renderers are a pluggable registry; each declares which modes it supports.
--format choices come from the registry.
| Format | Modes | Output |
|---|---|---|
chart |
series, events | Matplotlib strip-chart of elongation vs time — centered phase bands, named phases on the left axis / degrees on the right, with exact-event overlays (solid = phase centers, dashed = transitions). File type inferred from --out extension (png/svg/pdf/…). |
heatmap |
series | Calendar grid. --calendar gregorian (months × days, cells tinted, principal-phase day markers) or --calendar lunar (one phase-aligned strip per lunation, dated by --lunar-anchor). --tint illumination (grayscale by lit fraction) or --tint index (a hue per microphase). With --cell-times (gregorian + --transitions only), each day cell also prints the time(s) a microphase transition took effect, in low-contrast text; the figure is auto-sized so 9 pt text fits (override or enlarge with --size, restyle with --font). With --tint index and a 2-column --labels CSV, each day cell shows the microphase's short code and a labelled swatch legend (code = name) is drawn beneath the grid. |
almanac |
events | Ribbon of rendered moon disks at each exact phase center (name + date + time), with transition points dashed between. |
csv |
series, events | Sample rows, or exact-event rows (time, target_angle_deg, kind, microphase_index, name, …). |
json |
series, events | {scheme, samples} or {scheme, events}. |
terminal |
series, events | One row per day of Unicode moon glyphs (series), or a list of exact events. |
Adding a renderer is a single file: define render(report, out), decorate with
@register("name", modes={...}), add one import line — no changes to the CLI or
the calendar/events core.
Showcase
Two real renders from the sample gallery (tap to open full-size):
Almanac ribbon — the almanac renderer: exact phase instants across Jan–Feb 2026
at 16 divisions, a moon disk at each phase center with transition points dashed between.
Giant cell-times heatmap — the heatmap renderer with --cell-times: the full 2026
calendar at 16 microphases, index-tinted, every day cell printing its phase-peak times
(and, with --transitions, each transition into a phase as a bare Δ HH:MM).
Sample gallery
Actual rendered output of every chart for 2026 (at 8 and 16 divisions, plus
custom-label examples) lives in samples/ — each image is
embedded above the exact moonphase command that produced it.
Status & roadmap
Implemented:
- Phase 1 — centered-phase model, exact event-finding (phase centers +
transition points), the
Report-based renderer interface with mode declarations, thechart/csv/json/terminalrenderers, and the--mode/--transitionsCLI. - Phase 2 — Time handling — local-timezone input (bare dates → local), ISO offsets in output, DST-aware conversions, and a timezone caption on every time-bearing render.
- Phase 3 — New renderers & layouts — the
heatmaprenderer (--tint illumination|index,--calendar gregorian|lunarwith--lunar-anchor) and thealmanacmoon-disk ribbon. - Phase 4 — Custom names —
--labels(inline or@file, sparse-merged over the built-in names) for naming the finer gradations.
All four roadmap phases are implemented. moonphase is released on PyPI
(moonphase, current v1.2.0) and the
specification is marked production.
Possible future work / the roadmap lives in the spec —
see §9 of docs/specs/primary.md.
See docs/specs/primary.md for the full specification,
docs/superpowers/specs/ for the design write-up, and
docs/superpowers/plans/ for the phased
implementation plans.
Ephemeris
Uses Skyfield with the JPL DE421 kernel.
The kernel (~17 MB) is downloaded to ./data/ on first use (when a phase angle
is actually computed) and is gitignored. Pass --ephemeris path/to/de421.bsp to
use a pre-bundled copy.
DE421 is freely usable under NAIF's terms (redistribution of the unmodified kernel is permitted); it is not bundled in this repository.
Public API
from moonphase import (
MicrophaseScheme, # .from_divisions(N) / .from_step(deg)
PhaseEphemeris, # Skyfield-backed phase-angle lookup
PhaseSample, # (when, angle_deg, microphase)
PhaseEvent, # (when, angle_deg, kind, index, name)
Report, # renderer context: (scheme, mode, samples, events, tz, labels, options)
build_series, # sampled series
build_events, # exact phase-center / transition instants
phase_to_index, # centered microphase bucketing
)
from moonphase import renderers # register / get / modes_for / available
Development
pip install -e ".[dev]"
pytest -q # full suite (offline — uses a synthetic ephemeris, no kernel download)
ruff check src tests
License
MIT. See LICENSE.
Metadata
Release files for moonphase 1.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| moonphase-1.2.0.tar.gz | 48.0 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| moonphase-1.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 86.2 kB
Release files / moonphase-1.2.0.tar.gz
| Download URL | moonphase-1.2.0.tar.gz |
|---|---|
| Size | 48.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
2e6a9ac0089e416a97564cb8dc94330bfdcbb07b9ec9f34f69318d8b6da4e0f8
|
|
BLAKE2b-256 checksum How to use checksums |
4f7d3dbf61c75d3de9d03272829bd6e686549ec3b4df8e14e61a090ea3d4ddaa
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|
Release files / moonphase-1.2.0-py3-none-any.whl
| Download URL | moonphase-1.2.0-py3-none-any.whl |
|---|---|
| Size | 38.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
b60113089b7b4d4e1269dc7698d62cd046ad5a421f20e438a383a420629eac5f
|
|
BLAKE2b-256 checksum How to use checksums |
0ea16420cfe96473ebc937ce4b00097eea536fba6cef9f63155bbe857a86b489
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.14.3
|