Compute and chart arbitrary microphase divisions of the lunar synodic cycle.
Project description
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.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file moonphase-1.2.0.tar.gz.
File metadata
- Download URL: moonphase-1.2.0.tar.gz
- Upload date:
- Size: 48.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2e6a9ac0089e416a97564cb8dc94330bfdcbb07b9ec9f34f69318d8b6da4e0f8
|
|
| MD5 |
44877f5e4f6d9825920023e4859ca67a
|
|
| BLAKE2b-256 |
4f7d3dbf61c75d3de9d03272829bd6e686549ec3b4df8e14e61a090ea3d4ddaa
|
File details
Details for the file moonphase-1.2.0-py3-none-any.whl.
File metadata
- Download URL: moonphase-1.2.0-py3-none-any.whl
- Upload date:
- Size: 38.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.3
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b60113089b7b4d4e1269dc7698d62cd046ad5a421f20e438a383a420629eac5f
|
|
| MD5 |
0baccff50bc5ffdf34f3481b9e215b41
|
|
| BLAKE2b-256 |
0ea16420cfe96473ebc937ce4b00097eea536fba6cef9f63155bbe857a86b489
|