A Zig library for astronomical calculations: the Sun and Moon from anywhere on Earth, the Moon's phases and libration, and time scales.

Files
README.md

zig-astro #

Astronomical calculations for Zig 0.17.

The Earth's Moon is the first target. The Sun, the planets, and other natural and artificial satellites are to follow.

What is here so far is the foundation everything else stands on:

astro.time Instants, the time scales they are counted in (TT and UT), Julian and Gregorian calendar dates, Unix time, and ΔT
astro.Angle Angles, read and written in radians, degrees, arcseconds, hours, and sexagesimal, so that one is never mistaken for another
astro.coord Ecliptic, equatorial and horizontal coordinates, and the conversions between them
astro.earth Nutation, the mean and true obliquity of the ecliptic, and mean and apparent sidereal time at Greenwich
astro.observer An observer on the Earth's surface, by geodetic latitude, east longitude and height on the WGS 84 ellipsoid, and the topocentric position — right ascension and declination, ecliptic coordinates, hour angle, azimuth and altitude, and distance — of any body whose geocentric position is known; and atmospheric refraction, from airless to observed altitude and back
astro.sun The Sun's apparent position and distance, to about 0.01°, from the Earth's center or from an observer
astro.moon The Moon's apparent position and distance, to about 10″ in longitude and 4″ in latitude, from the Earth's center or from an observer, from whom it can be a degree away
astro.moon.phase The Moon's phase at an instant — elongation, phase angle, illuminated fraction, the bright limb, waxing or waning, and its named phase — and the instants of new Moon, first quarter, full Moon and last quarter, to within seconds, with the lunar age
astro.moon.libration The Moon's optical, physical and total libration and the position angle of its axis, from the Earth's center or from an observer, the subsolar point, and the colongitude that tells which craters the Sun is rising on
const astro = @import("astro");

// 1957 October 4, 19:26:24 UT, the launch of Sputnik 1.
const launch = astro.time.Instant.fromCalendar(
    .gregorian,
    .init(1957, .october, 4, 19, 26, 24),
    .ut,
);
const tt = launch.to(.tt, .model); // about 32 s later, by the ΔT model

Where the Moon is in the sky, seen from Palomar Observatory:

const palomar: astro.observer.Observer = .{
    .latitude = astro.Angle.fromDegrees(33.3561),
    .longitude = astro.Angle.fromDegrees(-116.8625), // east is positive
    .height_m = 1706,
};
const now = astro.time.Instant.fromCalendar(
    .gregorian,
    .init(2026, .october, 23, 4, 0, 0),
    .ut,
);
const moon = astro.moon.topocentric(now, palomar);
// moon.horizontal.azimuth.degrees() is about 153.0, a little east of
// south, and moon.horizontal.altitude.degrees() about 51.1.

Longitude counts positive east of Greenwich, and azimuth from the north through the east, as almost everyone counts them; Meeus counts both differently, and the conversions from his conventions are made inside the library. Altitudes are airless; astro.observer.observedAltitude adds the refraction that lifts a body to where it is seen, about half a degree at the horizon, and airlessAltitude takes it away again.

Every calculation takes an Instant, which says whether it is counted in TT, the uniform scale the theories use, or UT, which civil time follows. UTC is counted as UT. ΔT, the difference, comes from the Espenak–Meeus polynomials for the past, the U.S. Naval Observatory's measurements since 2005, and a prediction joining them to the long-term trend, unless a caller supplies a value of their own. Calendar dates say which calendar they are in: there is no automatic switch at 1582, because countries changed calendars at different times.

The API documentation is generated from the doc comments and rebuilt from main on every push.

Where this lives #

The Forgejo instance at https://git.jcollie.dev/jeff/zig-astro is the main home of the repository:

$ git clone https://git.jcollie.dev/jeff/zig-astro.git

and it is mirrored on Tangled at https://tangled.org/jcollie.dev/zig-astro.

Adding it to a project #

$ zig fetch --save git+https://git.jcollie.dev/jeff/zig-astro.git

then, in build.zig:

const astro = b.dependency("astro", .{ .target = target, .optimize = optimize });
exe.root_module.addImport("astro", astro.module("astro"));

Building #

The Nix flake's development shell has everything, Zig 0.17.0 included:

$ nix develop
$ zig build test --summary all    # the tests
$ zig build check                 # compile everything without running it
$ zig build fuzz --fuzz           # fuzz every calculation with any f64 at all
$ zig build docs-serve            # the API documentation, at http://127.0.0.1:8000/

Without Nix, Zig 0.17.0 is all that is needed; the library has no dependencies.

The tests check every calculation two ways: against the worked examples in Meeus's Astronomical Algorithms, which shows the code does what the book says, and against ephemerides from JPL Horizons over 1900–2100, which shows the book agrees with the sky. The Horizons tables are kept in tests/reference/ exactly as Horizons sent them, and tools/horizons.sh is the query that fetches them again. The instants of the phases are checked against all 9,895 in NASA's table of the phases of 1901–2100, which tools/nasa-phases.sh fetches.

The documentation has to be served rather than opened: what Zig emits is a WebAssembly viewer that fetches the source it shows from a tar file beside it, and a browser refuses to fetch anything from a file:// page. tools/docs_server.zig is that server, for one person on the loopback interface.

Reporting issues and contributing #

Report issues and send pull requests to the Tangled mirror, https://tangled.org/jcollie.dev/zig-astro. CONTRIBUTING.md covers setting up, the checks a change has to pass, and how the code is laid out.

References cited #

License #

MIT. See LICENSES/MIT.txt. This project follows the REUSE specification.