Current section
Files
Jump to
Current section
Files
README.md
# GreenCal π±π
[](https://github.com/nseaSeb/green_cal/actions/workflows/ci.yml)
Agricultural sun & moon calendar for Elixir β **pure arithmetic, zero
dependencies, no time zone database required**.
From a geolocation, a date (or a period) and optionally an elevation,
GreenCal computes:
- **Sun** β sunrise, solar noon, sunset, day length, rise/set azimuths,
civil / nautical / astronomical twilights (Meeus ch. 25, ~1 s of time)
- **Moon** β moonrise, moonset, transit, phase, illuminated fraction,
distance (full Meeus ch. 47 series, validated against example 47.a to
0.13β³ in longitude)
- **The three independent lunar cycles** used by agricultural calendars:
waxing/waning (illumination), **ascending/descending** (declination β the
one biodynamic sowing calendars actually use), perigee/apogee (distance)
- **Exact instants** of everything a printed calendar marks with a symbol
(`GreenCal.lunar_events/2`): phases (with an eclipse screening flag),
perigees/apogees, node crossings, and lunar standstills (the exact
ascending β descending flips)
- The constellation the Moon stands in β equal-sector sidereal zodiac
(Lahiri) by default, or the real unequal **IAU boundaries** (13
constellations, Ophiuchus included) with `boundaries: :iau`, the
convention of printed biodynamic calendars β plus its element
(fire/earth/air/water) and the traditionally associated plant organ
(fruit/root/flower/leaf)
Rise/set times are found by numeric search (sampling + bisection) rather
than the closed-form hour-angle formula, so **moonrise is accurate too**
(the closed form can be 15+ minutes off for the Moon) and polar edge cases
(midnight sun, polar night, "no moonrise today") are reported explicitly
instead of producing garbage. ΞT (TT β UT) uses **observed IERS values**
on 2000β2026, the Espenak & Meeus polynomials before that, and a
documented hold-then-bridge extrapolation after β so results don't drift
over the years.
## Usage
```elixir
loc = {48.8566, 2.3522} # Paris; {lat, lon}, East positive
day = GreenCal.day(loc, ~D[2026-06-21], elevation: 35.0)
day.sun.rise #=> ~U[2026-06-21 03:45:21Z]
day.sun.day_length_minutes #=> 974.1
day.twilight.dawn #=> ~U[2026-06-21 03:04:16Z]
day.moon.phase #=> :first_quarter
day.moon.trend #=> :descending (declination β sowing calendars)
day.moon.illumination_trend #=> :waxing (independent cycle!)
day.constellation #=> "Virgo"
day.organ #=> :root
# A whole month, with local times (needs a tz database, e.g. tzdata):
GreenCal.calendar(loc, Date.range(~D[2026-07-01], ~D[2026-07-31]),
time_zone: "Europe/Paris")
# A year in ~160 ms:
GreenCal.calendar(loc, Date.range(~D[2026-01-01], ~D[2026-12-31]), parallel: true)
# Exact instants of phases, apsides, nodes and standstills (geocentric):
GreenCal.lunar_events(Date.range(~D[2026-08-01], ~D[2026-08-31]))
# phases: [%{type: :new_moon, at: ~U[2026-08-12 17:36:40Z], eclipse: :likely}, ...]
```
The low-level astronomy is public too β `GreenCal.Astro` (UT-facing facade),
`GreenCal.Astro.Sun`, `GreenCal.Astro.Moon`, `GreenCal.Astro.RiseSet`,
`GreenCal.Astro.Time` β if you need positions, ephemerides or custom events.
An interactive tour lives in [`livebooks/green_cal.livemd`](livebooks/green_cal.livemd).
## Accuracy
| Quantity | Method | Validated against |
| --- | --- | --- |
| Solar position | Meeus ch. 25 (low accuracy) | Example 25.a |
| Lunar position | Meeus ch. 47, full 60+60 term tables | Example 47.a (0.13β³ / 0.03 km) |
| Nutation | Meeus ch. 22 short series (~0.5β³) | Example 22.a |
| Sidereal time | Meeus ch. 12 | Examples 12.a / 12.b |
| ΞT | Observed IERS 2000β2026, polynomials before, hold-then-bridge after | USNO `deltat.data` |
| Sun rise/set | numeric search | NOAA-consistent, < 1 min for \|lat\| < 66Β° |
| Moon rise/set | numeric search with per-instant parallax hβ | USNO API (Paris, Sydney, Edinburgh β β€ 30 s) |
| Phase instants | elongation crossings, bisection | Published 2026 instants (β€ 3 min) |
| Eclipse screening | \|Ξ²\| at syzygy (Meeus ch. 54 criterion) | All 4 eclipses of 2026, no false positive |
| IAU constellations | boundary table (J2000) + precession | Solar entry dates, all 13 constellations |
**Honesty note.** The astronomy above is science; elements, organs and
"sowing days" are tradition. GreenCal computes the former rigorously and
labels the latter for what it is.
## Known limitations
- **Refraction is the fixed standard 34β².** Real refraction varies with
temperature and pressure; near the horizon at high latitudes this can
shift a rise time by several minutes on unusual days. NOAA and USNO
tables share this convention.
- **`:elevation` models an unobstructed horizon below the observer**
(hilltop, coast, plain). In a valley the visible horizon is higher, not
lower β pass `0` and expect the sun later than computed.
- **Moon times are geocentric** apart from the parallax term absorbed in
hβ; the topocentric transit can differ by a few seconds.
- **Future ΞT is unknowable.** Observed values end in 2026; beyond, the
last value is held (documented in `GreenCal.Astro.Time`). Error should
stay under a second for ~a decade, then grow slowly. Override with
`:delta_t` if you need better.
- **Eclipse flags are screening, not circumstances** β "an eclipse happens
somewhere on Earth", not where or how deep.
## Installation
```elixir
def deps do
[
{:green_cal, "~> 0.1.1"}
]
end
```
Documentation: <https://hexdocs.pm/green_cal>.
## License
MIT