@himmelszelt/sternzeit

sternzeit: German, "star time". Sidereal time, counted from one culmination of a star to the next. Since 1799; also the German for Star Trek's stardate.

Astronomy for sky renderers: the Sun and the Moon, accurate, anywhere, any time.

by Daniel Limberger · Last updated 10 October 2026 · Unfamiliar term?Unfamiliar term Like this one: a dotted underline marks a term this site explains. Hover it, or tap it on a touch screen, and its definition opens right where you are reading. The small numbered citations work the same way, with the source they point to.

Contents
  1. Counting time the astronomer’s way
  2. Earth, the platform we observe from
  3. Coordinates on the sky
  4. The Sun
  5. The Moon
  6. Eclipses
  7. A month of Moons
  8. Precise or approximate?
  9. The people behind the numbers
  10. References

A sky can look reasonable without any astronomy: for a mood, a game level or a film shot, the Sun, the Moon and the stars may stand wherever they serve the picture. A sky display faithful to a georeferenced site at a given hour, however, first has to know how the real sky is composed at that moment and place. Where is the Sun? Is the Moon up, and how high? How large does it appear, which way is its crescent tilted? None of this is guesswork: Jean Meeus wrote it all down as recipes [1]Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998., and libraries such as SunCalc, astronomia and Astronomy Engine compute it in JavaScript already. sternzeit is one more implementation of the same recipes, made for real-time rendering: directions in a renderer’s frame, the Moon’s tilt, libration and earthshine, and eclipses as a shader coordinate, all as plain, fast functions.

@himmelszelt/sternzeit has no dependencies, touches neither the DOM nor the GPU, and runs anywhere JavaScript runs. It returns plain numbers, and what to do with them is up to you: give a CAD or architecture viewer the right light and shadows for a real site and hour, run a solar potential analysis for a roof, let a game’s day follow the player’s clock, tell a photographer when the full Moon rises behind a landmark, label what is up right now in augmented reality, or show the sky over a date in history. Four tasks are worked out below, code included: the golden hour for any day and place, a crescent turned the right way, the next eclipse, and a moon calendar of your own.

All of them start from the same three bodies. Earth, tilted and turning, is where we watch from (Figure), and its turning is what sweeps the sky around us once a day. It circles the Sun once a year, and the tilt makes the seasons of that year. The Moon circles Earth about once a month, lit by sunshine and, faintly, by earthshine. From the ground, the two discs almost match, as the Sun is about 400 times wider than the Moon and about 400 times farther away, a stroke of luck that will not last: the Moon drifts away by about 3.8 cm a year, and in some 600 million years it will be too small to ever cover the Sun again. We happen to live in the stretch of Earth’s history in which the sky can go dark in the middle of the day.

observereclipticvernal equinoxNSecliptic axis
sun-locked
moon-locked

Drag to rotate. Visualized with Zdog.

An armillary sphereArmillary sphere A model of the sky made of rings around Earth at its center: the celestial equator, the ecliptic, meridians, sometimes the horizon. Used since antiquity to teach the sky and to measure positions in it.: Earth's equator and axis, its north (N) and south (S) pole, the ecliptic and its axis, the vernal equinox, the observer's latitude circle and meridian, and the Sun and the Moon in their directions from Earth's center, all in geocentric equatorial coordinates. Distances are schematic, the Sun and the Moon symbols, not to scale. (a, b) The sky of the chosen place around the Sun and around the Moon, each held at the center while the horizon moves; the other body, when out of view, is an arrow towards it.
Moment and place

Every figure and table on this page is computed live by the library, in your browser, and follows one moment and one place. The same controls sit folded up below every figure and every unfolded table, so there is no need to scroll back up here. The pin fills in your location and looks up its height above sea level, which matters mostly for the air and can be set by hand below Earth’s table (Table). Animated a day a step, the Moon’s phases and libration show best.

Counting time the astronomer’s way

Calendars are a mess. Months have different lengths, leap years come and go, and in 1582 ten days were simply skipped. None of that belongs in orbital mechanics, so astronomers count days instead: the Julian DayJulian Day (JD) A continuous day count starting at noon UTC on 4713 BCE January 1 (Julian proleptic calendar). Astronomers use it so calendar irregularities (varying month lengths, leap years, the Julian-to-Gregorian switch) don't have to be handled by the actual position formulas. julianDayUT() converts a date and time to this count. is the number of days since noon on January 1st, 4713 BCE. It is a single, continuous number, fractional for the time of day. Today is somewhere around 2.46 million.

Most formulas then don’t count in days but in Julian centuriesJulian century (commonly T) The number of 36,525-day periods since J2000.0. Nearly every formula in this library is a polynomial in T, since century-scale drift terms are numerically nicer to work with in centuries than in raw days. since J2000Standard equinox / J2000.0 The reference epoch, 2000 January 1, 12:00 Terrestrial Time. Orbital elements ("mean" quantities, below) are defined relative to this fixed date rather than "now", which is why they need the corrections below (nutation, equation of the center, ...) to become an apparent, as-seen-right-now position., noon on January 1st, 2000. What changes slowly, such as the tilt of Earth’s axis or the shape of an orbit, is written as a polynomial in time, and counted in centuries its terms stay modest numbers instead of tiny coefficients multiplied by millions of days.

Earth’s rotation makes a fine clock, but not a perfect one. Tidal friction slows it, by an amount that varies over the centuries, so time counted by the turning Earth falls behind the uniform time in which the orbits are computed. The gap between the two is ΔTDelta T (ΔT) The difference between Terrestrial Time (TT), the uniform time the orbits are computed in, and Universal Time (UT), which follows Earth's actual rotation. Tidal friction slows the rotation, so ΔT grows: about 69 seconds in 2026, some hours two thousand years ago. Known from historical eclipse records and, since the 1950s, atomic clocks; its future is an extrapolation.: about a minute today, and hours in antiquity (Figure). Wherever you pass a date and a place, ΔT is taken care of. A function that takes a bare Julian Day expects it in ephemeris time, which julianEphemerisDay gives you.

ΔTDelta T (ΔT) The difference between Terrestrial Time (TT), the uniform time the orbits are computed in, and Universal Time (UT), which follows Earth's actual rotation. Tidal friction slows the rotation, so ΔT grows: about 69 seconds in 2026, some hours two thousand years ago. Known from historical eclipse records and, since the 1950s, atomic clocks; its future is an extrapolation. after Espenak and Meeus, measured by the IERS since 1962, and at the chosen moment, on a logarithmic scale. The years before 1800 are compressed on one too. Before −500 the curve is their long-term parabola, fitted to ancient eclipses, and before −2000 only extrapolated, as are the years after today. Years in astronomical numbering, with a year 0. Hover to read off any other.

Then there is sidereal timeSidereal time The hour angle of the vernal equinox, i.e., "what time it is" for pointing a telescope, since it tracks Earth's rotation relative to the stars rather than the Sun (a sidereal day is ~4 minutes shorter than a solar day). siderealTime() gives Greenwich's mean sidereal time, measured from the mean equinox, and apparentSiderealTime() the apparent one, from the true equinox, which apparent right ascensions need.. A solar day, noon to noon, is 24 hours. But Earth also moves along its orbit, so it has to turn a little more than once for the Sun to come back to the same spot. Relative to the distant stars, one rotation takes about 23 hours and 56 minutes. Sidereal time measures that rotation, as an angle. It is what connects the sky’s coordinates to your spot on the ground: the dashed meridian through your location rotates with it (Figure). Step the moment a day at a time, and sidereal time gains almost a degree with every step (Table). Over a year, these add up to one whole turn: Earth rotates 366 times while the Sun comes back only 365.

import {
    fromDate, fromJulianDay, julianDayUT, julianEphemerisDay, siderealTime,
    toDate,
} from "@himmelszelt/sternzeit";

// Any JavaScript Date, e.g., new Date() for now; its time zone
// offset comes along.
const time = fromDate(new Date("2026-09-21T20:00:00+02:00"));
// The same moment spelled out: 20:00 in Berlin (UTC+2).
const same = {
    year: 2026, month: 9, day: 21,
    hour: 20, minute: 0, second: 0, utcOffsetSeconds: 7200,
};

const jd = julianDayUT(time); // 2461305.25, the offset applied
const jde = julianEphemerisDay(time); // ΔT added, for the orbits
const theta = siderealTime(time); // Greenwich mean sidereal, in deg
const date = toDate(fromJulianDay(jd)); // and back to a Date
Moment and place

Earth, the platform we observe from

Earth’s rotation axis is tilted against the plane of its orbit by about 23.4°, the obliquity of the eclipticObliquity of the ecliptic (ε) The tilt angle (~23.4°) between Earth's equatorial plane and its orbital plane, i.e., the axial tilt responsible for seasons. Needed to convert between ecliptic and equatorial coordinates. "Mean" obliquity ignores nutation (below); "true" obliquity includes it., and that is why we have seasons. The tilt drifts slowly over millennia (the mean obliquity), and the Moon’s pull on Earth’s equatorial bulge adds a nod with a period of 18.6 years, the nutationNutation (in longitude Δψ, in obliquity Δε) A small, roughly-18.6-year periodic wobble in Earth's axis, caused mainly by the Moon's varying gravitational pull as its orbital plane precesses. Layered on top of the much larger, slow (26,000-year) precession. "In longitude" nutates ecliptic longitude; "in obliquity" nutates the obliquity angle itself.. It tilts the axis a little more and a little less, and slides the vernal equinoxVernal equinox The point on the sky where the Sun crosses the celestial equator northwards, around 20 March, and the zero from which both ecliptic longitude and right ascension are counted. Also called the First Point of Aries, after the constellation it lay in some two thousand years ago; precession has since carried it into Pisces. Nutation makes it wobble by up to about 17 arcseconds around its mean position., the point where the Sun crosses the equator northwards in March. Both effects stay below 20 arcseconds, but they separate a position that is almost right from one that is right.

Moment and place

The table also holds the constants the atmosphere needs (Table): Earth’s radius [2]NASA Space Science Data Coordinated Archive: Planetary Fact Sheets: Earth, Moon, Sun. nssdc.gsfc.nasa.gov, 2024., the thickness of the atmosphere [3]Tomoyuki Nishita, Takao Sirai, Katsumi Tadamura, Eihachiro Nakamae: Display of the Earth Taking into Account Atmospheric Scattering. SIGGRAPH, 1993. [4]Eric Bruneton, Fabrice Neyret: Precomputed Atmospheric Scattering. Computer Graphics Forum 27(4), EGSR, 2008., and the refraction that keeps the Sun visible for a few minutes after it has geometrically set. Refraction comes in two directions, from the true altitude [5]Þorsteinn Sæmundsson: Atmospheric Refraction. Sky and Telescope 72, 1986. and from the apparent one [6]G. G. Bennett: The Calculation of Astronomical Refraction in Marine Navigation. Journal of Navigation 35, 1982., and a renderer bending its view rays wants the latter. The rows look towards the Sun, so once it is more than a degree below the horizon, refraction shows as n/a: the line of sight ends in the ground. Raise the height to 10 km, a jet’s cruising altitude, and the horizon sinks by more than 3° (horizonDip).

Coordinates on the sky

The tilt and the vernal equinox are also what the sky’s coordinates are built on. The equinox makes a natural zero: it is one of the two points where the equator and the ecliptic cross, so it lies on both, and the Sun marks it every March, no star needed. There are several coordinate systems, and each exists because one step of the computation is simplest in it. Ecliptic coordinatesEcliptic coordinates (ecliptic longitude l, ecliptic latitude β) Coordinates using the plane of Earth's orbit (the ecliptic) as the reference plane instead of the equator. Since the Sun's ecliptic latitude is always ~0 by definition, and the Moon and planets orbit close to this plane, orbital calculations are often simplest here before converting to equatorial. take the plane of Earth’s orbit, the ecliptic (Figure), as their equator. The Sun never leaves it, and the Moon and the planets stay within a few degrees, so orbits are computed here: an ecliptic longitude counted from the vernal equinox, and a small latitude off the plane. The orbit itself is almost a circle, at an eccentricity of about 0.0167 [1]Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998..

Equatorial coordinatesEquatorial coordinates (right ascension α, declination δ) Coordinates on the celestial sphere using Earth's rotational axis and equator as the reference plane, analogous to longitude/latitude on Earth but for the sky. Right ascension is measured eastward from the vernal equinox; declination is the celestial-equator equivalent of latitude. take Earth’s equator instead: right ascension along it, also counted from the vernal equinox, and declination north or south of it. This is the sky’s own map, the one star catalogues use, since a star keeps both values while Earth turns underneath. The two systems differ only by the obliquity, a tilt about the line towards the vernal equinox.

Both are geocentricGeocentric / topocentric Seen from Earth's center, or from the observer's own place on its surface. Formulas and tables give geocentric positions; for the Moon, the difference, its parallax, reaches about a degree., and nobody stands at Earth’s center. From the surface, a nearby body appears slightly shifted by its parallaxParallax (equatorial horizontal / topocentric position) The Moon is close enough (~384,000 km) that its apparent position shifts noticeably (up to ~1°) depending on where on Earth's surface you're standing, unlike the Sun or stars. equatorialHorizontalParallax is the size of that shift for an observer at Earth's equator with the Moon on their horizon; topocentricPosition applies the actual correction for a given observer, which horizontalPosition then uses.: not at all for the stars, by 8.8 arcseconds for the Sun, and by up to a degree for the Moon, two of its own diameters. The topocentric position corrects for it, with the observer on the flattened ellipsoid and at their height above sea level.

Horizontal coordinatesHorizontal coordinates (azimuth A, altitude h) Coordinates relative to one observer's local horizon: altitude is height above the horizon, azimuth is the compass-like direction along it. Unlike the other two systems, this is what you'd actually point at, and it depends on the observer's latitude/longitude and the current sidereal time., finally, are the observer’s own: an altitude above the horizon and an azimuth from north through east. They follow from the topocentric position through the hour angleHour angle (H) How far a point on the sky has rotated past the local meridian (due south/north), i.e., local sidereal time minus its right ascension. Used to convert equatorial coordinates to horizontal ones., which sidereal time provides.

The prefixes of the function names say how far a position has been corrected, as in meanLongitude or apparentPosition:

For a renderer, all of this ends in a single call. direction returns the horizontalPosition as a unit vector in the observer’s east-north-up frame, ENU for short, with x pointing east, y north and z up; an engine with y up, as most real-time renderers have it, assigns it as (x, z, -y). Refraction is left out on purpose. The altitude is the true one, not the apparent one the eye sees, which at the horizon is lifted by more than the Sun’s own diameter, and bending the light that way is the job of an atmosphere renderer.

The Sun

Computing the Sun’s position follows a pattern that most of this library repeats. It starts on an idealized orbit, a perfect circle traversed at constant speed, where the mean anomalyMean anomaly (M) The angle a body would have traveled along its orbit if it moved at a constant average speed, i.e., its position ignoring that real orbits are elliptical (faster near perihelion, slower near aphelion). A simple near-linear function of time. and the mean longitudeMean longitude (L₀) Mean anomaly plus the longitude of perihelion, i.e., the mean anomaly re-expressed as an absolute ecliptic longitude rather than an angle measured from perihelion. grow almost linearly in time. Then each step corrects for reality. Orbits are ellipses, and the equation of the centerEquation of the center (C) The correction added to mean anomaly to get true anomaly, arising because orbits are ellipses, not circles. Usually the single largest correction when approximating orbital motion as uniform circular motion. gives the true longitudeTrue anomaly / true longitude The mean anomaly/longitude corrected for orbital eccentricity (see "equation of the center"), giving the body's actual angular position along its real elliptical orbit. on one. Nutation and aberration, a small shift caused by Earth’s own orbital speed, turn it into the apparent positionApparent position Where a body's light seems to come from, as seen from Earth's center: its true orbital position further corrected for nutation and, for the Sun and stars, aberration (a small angular shift caused by Earth's own orbital velocity, similar to rain appearing to slant when you run through it). in right ascension and declination. The topocentric and the horizontal position finally bring it to the observer.

Aberration hides a subtlety. The Sun’s light takes 8 minutes 20 seconds to reach us, so we see the Sun where it was back then. Yet it does not lag the 2° the sky turns in that time: that turning is Earth spinning under the light, which changes nothing about where the light comes from. Only the Sun’s own drift against the stars counts, Earth’s orbit mirrored, and in 8 minutes 20 seconds that is 20.5” [1]Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998., under a ninetieth of the Sun’s disc. The true longitude leaves it out. The apparent position adds it, and so does everything built on it, up to sun.direction, which therefore points where the Sun shows. The Moon’s light is underway for only 1.3 seconds, in which the Moon moves some 0.7”, and sternzeit leaves that out.

analemma: the Sun at this hour, every day of the year, a dot on the 1st of each month

Drag to rotate. Visualized with Zdog.

A sun path diagramSun path diagram A chart of the Sun's track across the sky of one place, over a day, often for several dates at once: flat, as altitude over azimuth, or on a dome. Architects and solar engineers use it to plan shading and daylight.: the Sun's path (a dot per hour) and the Moon's (grey) across the sky of the chosen place, in horizontal coordinates, local midnight to midnight, dotted below the horizon, arrows pointing forward in time. Symbols, not to scale. (a) The Sun's analemmaAnalemma The figure the Sun traces in the sky when observed at the same clock time every day for a year: usually a lopsided figure eight. Its height range is twice the obliquity of the ecliptic, about 47°; its width and its eight shape come from the equation of time. at this time of day, a dot on the first of every month.
Moment and place

Over a day, both cross your sky along a path, and the Sun followed through a year, always at the same hour, traces an analemmaAnalemma The figure the Sun traces in the sky when observed at the same clock time every day for a year: usually a lopsided figure eight. Its height range is twice the obliquity of the ecliptic, about 47°; its width and its eight shape come from the equation of time. (Figure). The Moon’s path never quite closes, as it moves about 13° a day against the stars. The analemma’s height swings with the seasons, from the tilt of Earth’s axis, and its sideways swing comes from the equation of timeEquation of time The difference between apparent solar time, what a sundial shows, and mean solar time, what a clock shows: about 14 minutes one way in February and about 16 the other in November. It has two causes: Earth moves faster along its elliptical orbit near perihelion in early January, and the Sun's motion along the tilted ecliptic projects unevenly onto the equator. Together they shape the analemma's figure eight.: Earth moves faster along its orbit in January than in July, and the tilt adds a second, twice-yearly wobble, so the Sun runs up to about 16 minutes ahead of the clock in November and 14 behind it in February [1]Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998.. Where its dots crowd, the Sun is turning around.

Moment and place

distance and apparentAngularDiameter belong together (Table). Earth is about 3% closer to the Sun in early January than in early July, and the Sun’s disc looks about 3% larger then.

With the Sun’s altitude at hand, the library leaves out the conveniences on purpose: there is no sunrise(), and no dawn() or dusk() either. Each is the moment the altitude crosses a threshold, so walking a day in one-minute steps finds all of them, the golden and the blue hour too, or any other threshold you care to render.

import {
    fromDate, fromJulianDay, julianDayUT, sun, toDate,
} from "@himmelszelt/sternzeit";

// The Sun is up once its refracted upper limb clears the horizon,
// the -0.833 degrees every almanac uses.
const midnight = Math.floor(julianDayUT(fromDate(new Date())) - 0.5) + 0.5;
const above = (jd: number, deg: number) =>
    sun.horizontalPosition(fromJulianDay(jd), { latitude, longitude })
        .altitude > deg;

function crossings(deg: number): Date[] {
    const found: Date[] = [];
    for (let minute = 1; minute <= 1440; minute++) {
        const [before, now] = [minute - 1, minute]
            .map((m) => midnight + m / 1440);
        if (above(before, deg) !== above(now, deg)) {
            found.push(toDate(fromJulianDay(now)));
        }
    }
    return found;
}

const [sunrise, sunset] = crossings(-0.833);
const [dawn, dusk] = crossings(-6); // civil twilight

Run for the chosen day, place and clock:

Walk the same thresholds through a whole year and they draw the light of every day at once (Figure): the days lengthening and shortening, the hour by which summer time shifts them, and the twilights, which far enough north fill whole summer nights.

The year at the chosen place, day by day from left to right, midnight to midnight from top to bottom on the chosen clock, its daylight saving time included: day, the golden and the blue hour, then nautical and astronomical twilight, each a shade darker, and night. The line through the golden hour is sunrise and sunset. Click to move the moment there. Try another latitude below: the farther from the equator, the more the days swing over the year, and the longer the twilights last.
Moment and place

The Moon

The Moon is close, its orbit is tilted against Earth’s by about 5°, and the Sun actually pulls on it about twice as hard as Earth does: a three-body problem, the original one, which has no exact solution. Its position needs dozens of periodic correction terms, all built from four slowly turning angles of its orbit. Being close also means a large parallaxParallax (equatorial horizontal / topocentric position) The Moon is close enough (~384,000 km) that its apparent position shifts noticeably (up to ~1°) depending on where on Earth's surface you're standing, unlike the Sun or stars. equatorialHorizontalParallax is the size of that shift for an observer at Earth's equator with the Moon on their horizon; topocentricPosition applies the actual correction for a given observer, which horizontalPosition then uses., which is why the topocentric correction matters most here.

Moment and place

The rows after distance are for rendering the Moon rather than locating it (Table). The Moon always shows the same face, almost: tilted orbit and varying speed let us peek a few degrees around its edges over a month, the optical librationLibration The Moon keeps (nearly) the same face toward Earth, but the tilt of its orbit and axis and its varying speed along the orbit let an observer see a few degrees around its edges over a month, as if it were slowly nodding: the optical libration. The Moon's own slight wobble about its mean rotation adds the physical libration, a few hundredths of a degree. moon.librations gives both, moon.opticalLibrations the first alone., and the Moon’s own slight wobble adds a few hundredths of a degree. The position angle of the axisPosition angle of axis The angle, on the sky, between celestial north and the Moon's own north pole/rotation axis, needed to draw the Moon's surface features (or a lit crescent) correctly oriented rather than always "upright". says how the Moon’s north pole is oriented on the sky, and the parallactic angleParallactic angle The angle between "straight up" (the local zenith direction) and celestial north at a given point on the sky, such as the Moon. Relevant for orienting how a crescent or terminator will actually appear tilted for a specific observer. how that sky direction relates to your local “up”. Together they determine how the Moon’s surface, and its lit crescent, appear rotated for you.

The last four rows light it. The phase anglePhase angle (i) The angle Sun-Moon-Earth, i.e., how far the Sun is "behind" the Moon as seen from Earth: 0° at full moon, 180° at new moon. illuminatedFraction is (1 + cos i) / 2., the angle Sun-Moon-Earth, says how much of the lit half faces us, and the illuminated fractionIlluminated fraction (k) How much of the Moon's visible disc is lit, 0 at new moon to 1 at full moon. What the phase looks like, as a single number. turns that into what the phase looks like, from 0 at new moon to 1 at full moon [1]Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998.. For actual shading, a renderer wants the light direction itself: sunDirection points from the Moon’s center to the Sun, in the same frame as every other direction here, east-north-up. It is not quite the Sun’s direction as seen from Earth, since the Moon sits a few hundred thousand kilometers off to the side, but it is close.

The night side is not entirely dark either. The Earth reflects sunlight too, and around new moon, when the Moon sees an almost full Earth, that earthshineEarthshine Sunlight reflected by the Earth onto the Moon, which lights its night side with a faint, ashen glow, easiest to see on a thin crescent. Strongest around new moon, when the Moon sees a full Earth, and gone at full moon, when the side of the Earth facing it is dark. gives the unlit part of a thin crescent a faint, ashen glow. How strong it is follows from the Earth’s phase as seen from the Moon [7]Hendrik C. van de Hulst: Multiple Light Scattering: Tables, Formulas, and Applications. Academic Press, 1980., scaled by the Earth’s albedo [8]Henrik Wann Jensen, Frédo Durand, Julie Dorsey, Michael M. Stark, Peter Shirley, Simon Premože: A Physically-Based Night Sky Model. SIGGRAPH, 2001..

With the Moon located as well, a scene can be lit. The two directions a renderer wants are one call each, and the Moon’s brightness comes with them: moonlight is sunlight, so the lit fraction of the disc is roughly how much of it there is.

import {
    fromDate, julianEphemerisDay, moon, sun,
} from "@himmelszelt/sternzeit";

const time = fromDate(new Date());
// Unit vectors in the observer's ENU frame: x east, y north, z up.
// Real-time rendering mostly puts y up: assign them as (x, z, -y).
const observer = { latitude, longitude, heightM }; // heightM is optional
const toSun = sun.direction(time, observer);
const toMoon = moon.direction(time, observer);
// Functions of a bare Julian Day take ephemeris time.
const moonlight = moon.illuminatedFraction(julianEphemerisDay(time));

Shading the Moon itself is a different, optional question. It takes the Moon’s own sunDirection rather than toSun, along with the libration and the earthshine.

Even a Moon drawn without any shading needs one angle: the tilt of its crescent. A phase widget that only scales the crescent gets it wrong, as the lit side points at the Sun, which is rarely straight down. Getting it wrong is a classic. Games and illustrations like to hang a crescent in the night sky with its lit side facing up, and that cannot happen. The lit side faces the Sun, and a crescent means the Sun is less than 90° from the Moon, so pointing the lit side up puts the Sun above the Moon and therefore above the horizon: it would be day. A gibbous or full Moon is a different matter. There the Sun stands on the far side of the sky, and the lit side may well face up while the Sun is 80° below the horizon. For a crescent, point the lit side at least somewhat towards the horizon. For the exact tilt, the position angle from the Moon to the Sun is the angle to rotate the crescent by, measured on the sky from north through east.

import {
    fromDate, julianEphemerisDay, moon, positionAngle, sun,
} from "@himmelszelt/sternzeit";

const jd = julianEphemerisDay(fromDate(new Date()));
const lit = moon.illuminatedFraction(jd); // 0 new, 1 full
// Half a day on tells waxing from waning without a calendar.
const waxing = moon.illuminatedFraction(jd + 0.5) > lit;

const m = moon.apparentPosition(jd);
const s = sun.apparentPosition(jd);
const brightLimb = positionAngle(
    m.rightAscension, m.declination,
    s.rightAscension, s.declination,
);

The other classic is the unlit part. It is not a hole: no star has ever shone through the Moon. What faintly fills it is earthshine, sunlight that went to Earth and back (moon.earthshine), which shades the Moon’s night side below (Figure).

The Moon as seen from the chosen place, zenith up: lit by the Sun (arrow), its night side in earthshineEarthshine Sunlight reflected by the Earth onto the Moon, which lights its night side with a faint, ashen glow, easiest to see on a thin crescent. Strongest around new moon, when the Moon sees a full Earth, and gone at full moon, when the side of the Earth facing it is dark. (bluer when stronger). The grid is the Moon's own, its center shifted by the librationLibration The Moon keeps (nearly) the same face toward Earth, but the tilt of its orbit and axis and its varying speed along the orbit let an observer see a few degrees around its edges over a month, as if it were slowly nodding: the optical libration. The Moon's own slight wobble about its mean rotation adds the physical libration, a few hundredths of a degree. moon.librations gives both, moon.opticalLibrations the first alone. (arrow, from its mean center to the point we look at) and its north pole (N) turned, against the zenith (dashed), by the position angle of the axisPosition angle of axis The angle, on the sky, between celestial north and the Moon's own north pole/rotation axis, needed to draw the Moon's surface features (or a lit crescent) correctly oriented rather than always "upright". and the parallactic angleParallactic angle The angle between "straight up" (the local zenith direction) and celestial north at a given point on the sky, such as the Moon. Relevant for orienting how a crescent or terminator will actually appear tilted for a specific observer. (arc). The dashed circles are its size at apogee and perigee. Magnified, disc and horizon true to each other. Locked to the Moon, its north is up and only the librations move.
Moment and place

Eclipses

With both bodies located, eclipses follow from their positions. A solar eclipse is a matter of perspective: the Moon passing in front of the Sun, which depends on where you stand. Stand on the line the Moon’s shadow sweeps and the Sun goes out (total), or, with the Moon near apogee and too small to cover it, leaves a ring of light around the black disc (annular); a few hundred kilometers to the side the Moon only takes a bite out of it (partial). A lunar eclipse is the Moon passing through Earth’s shadow, and it looks the same from anywhere the Moon is up: total once the whole disc is inside the umbra, partial while only part of it is, and penumbral, barely a dimming, when the Moon misses the umbra altogether. Both are summarized as a single phaseEclipse phase How deep an eclipse is, as a single 0-1+ number shaped for a lookup texture, next to the catalogs' magnitude (0 = deepest, 1 = outer edge, above 1 = no eclipse). See phase() in eclipse.ts for the exact shape and the fixed 0.5 boundary, and its cited source paper. Whether a lunar eclipse occurs is geocentric (same for every observer who can see the Moon); a solar eclipse is a perspective effect and depends on the observer's location. [9]Daniel Müller (now Limberger), Juri Engel, Jürgen Döllner: Single-Pass Rendering of Day and Night Sky Phenomena. Vision, Modeling and Visualization (VMV), 2012.: 0 at the deepest point, 0.5 where a total or annular eclipse turns partial, 1 where the discs, or the Moon and the shadow, just touch, and above 1 when there is nothing to see. The fixed 0.5 boundary is what makes the phase usable as a texture coordinate for a shader.

nearby eclipse
solar eclipse, as seen from the chosen place: zenith up, azimuth increasing to the right
nearby eclipse
lunar eclipse, the same from anywhere: ecliptic north up, east left, as on the sky
Eclipses. (a) The Moon in front of the Sun. (b) The Moon in Earth's umbraUmbra / penumbra The two parts of a shadow cast by a light source that is not a point. In the umbra, the core, the Sun is hidden completely; in the penumbra around it, only partly. Earth's shadow is a pair of cones behind it: at the Moon's distance the umbra is about 4,600 km in radius, the penumbra about 8,200 km, against the Moon's own 1,737 km. and penumbraUmbra / penumbra The two parts of a shadow cast by a light source that is not a point. In the umbra, the core, the Sun is hidden completely; in the penumbra around it, only partly. Earth's shadow is a pair of cones behind it: at the Moon's distance the umbra is about 4,600 km in radius, the penumbra about 8,200 km, against the Moon's own 1,737 km.. Magnified, discs, shadows and horizon true to each other. Corona modified from a photograph by Phil Hart of the total eclipse of 20 April 2023, from Exmouth, Western Australia.
Moment and place

Most of the time there is nothing to see, hence the four buttons (Figure): they search the sky itself, forwards or backwards from the moment you are on, for the next eclipse, and jump to its maximum. Nothing is looked up. NASA’s catalogs list every eclipse over five millennia [10]Fred Espenak, Jean Meeus: Five Millennium Canon of Solar Eclipses: -1999 to +3000. NASA Technical Publication TP-2006-214141, 2006. [11]Fred Espenak, Jean Meeus: Five Millennium Canon of Lunar Eclipses: -1999 to +3000. NASA Technical Publication TP-2009-214172, 2009., but the search finds them from the positions alone: only a full moon can enter Earth’s shadow and only a new moon can cross the Sun, so it needs to look closer only once a lunationLunation One cycle of the Moon's phases, from new moon to new moon: the synodic month, 29.53 days on average. Individual lunations vary by up to about seven hours around the mean.. The solar search answers for the place you chose, which is why it often lands on a modest partial eclipse. One I watched myself: set the place to Seddiner See near Potsdam (52.3° N, 13.0° E) and the date to 12 August 2026. Around 20:09 local time, low above the horizon, the Moon covered nearly nine tenths of the Sun’s diameter, and the Sun set still partly eclipsed. How the search works is shown below.

Eclipses also come in pairs. They happen in seasons, while the Sun stands near one of the two points where the Moon’s orbit crosses the ecliptic, and a season lasts longer than the month from one new moon to the next. So every eclipse of the Sun has one of the Moon about two weeks before or after it, if often only a faint penumbral one. From the eclipse of 12 August 2026, the next lunar eclipse is that of 28 August.

From the maximum, step the Julian Day by minutes and watch it unfold. The Moon darkens in the penumbra first, barely noticeable, then turns a dark red in the umbra, lit only by sunlight that Earth’s atmosphere bends around the planet, its outer rim tinged blue by the ozone the light passes through. Tracing that light through the atmosphere gives the colors physically [12]Theodore C. Yapo, Barbara Cutler: Rendering Lunar Eclipses. Graphics Interface, 2009., but far from real time. A renderer can approximate them from the eclipse’s phase instead [9]Daniel Müller (now Limberger), Juri Engel, Jürgen Döllner: Single-Pass Rendering of Day and Night Sky Phenomena. Vision, Modeling and Visualization (VMV), 2012., a lookup coordinate from 0 at the center to 1 as the discs part. The panel only hints at those colors. Next to the phase, the table lists the magnitudeEclipse magnitude How much of the eclipsed body is covered, as a fraction of its diameter: of the Sun's by the Moon in a solar eclipse, of the Moon's by Earth's umbra or penumbra in a lunar one. 1 or more means total. It is the number the eclipse catalogs list, and not the phase, which runs the other way, from 0 at the center to 1 as the discs part., the number the catalogs give (Table).

Moment and place

Rare as they are, eclipses should not cost a renderer anything on every frame, yet they are special enough to be worth a notification when one comes. This example finds the next total lunar eclipse: it steps from full moon to full moon, checks each one in coarse steps for whether the Moon gets near Earth’s shadow at all, and only then walks it minute by minute. This is, give or take the search window, what the buttons above do (Figure):

import {
    eclipse, fromDate, fromJulianDay, julianDayUT, julianEphemerisDay, moon,
    toDate,
} from "@himmelszelt/sternzeit";

// Only a full moon can be eclipsed, so step by lunations rather than
// by days. Full moons sit half a lunation after the counted new ones.
const { MEAN_NEW_MOON, MEAN_SYNODIC_MONTH } = moon;
const firstFullMoon = MEAN_NEW_MOON + MEAN_SYNODIC_MONTH / 2;

// Totality: the whole disc fits inside the umbra. The search runs in UT,
// the orbits in ephemeris time.
const total = (jd: number) => {
    const shadow = eclipse.lunar(julianEphemerisDay(jd));
    return shadow.axisOffsetKm + moon.MEAN_RADIUS_KM
        <= shadow.umbraRadiusKm;
};

function nextTotalLunarEclipse(from: number): Date | null {
    const gone = (from - firstFullMoon) / MEAN_SYNODIC_MONTH;
    for (let month = Math.floor(gone); month < gone + 250; month++) {
        const full = firstFullMoon + month * MEAN_SYNODIC_MONTH;
        // The true full moon wanders up to a day from the mean one;
        // three-hour steps say whether to bother looking closer.
        let near = false;
        for (let jd = full - 1; jd <= full + 1 && !near; jd += 1 / 8) {
            const shadow = eclipse.lunar(julianEphemerisDay(jd));
            near = shadow.axisOffsetKm < 2 * shadow.penumbraRadiusKm;
        }
        if (!near) continue;
        for (let jd = full - 1; jd <= full + 1; jd += 1 / 1440) {
            if (jd > from && total(jd)) return toDate(fromJulianDay(jd));
        }
    }
    return null;
}

// Up to twenty years ahead; from late 2026 that is totality on
// 2028-12-31, from 16:18 UTC.
const next = nextTotalLunarEclipse(julianDayUT(fromDate(new Date())));

A month of Moons

Put together, the pieces above make a moon calendar that shows more than the phase: which way the Moon is turned where you are, and whether it is up while it is dark. In the calendar below (Figure), the discs are the Moon at the same time of day all month, turned as it stands in the sky there; the lanes say when it is up, and the Sun with it. How to draw them is up to you, so here they are two functions you bring yourself:

import { fromDate, julianEphemerisDay, moon, sun } from "@himmelszelt/sternzeit";

const DAY_MS = 86_400_000;
const observer = { latitude, longitude };
// Upper limb on the horizon, refraction included, as for sunrise.
const isUp = (altitude: number) => altitude > -0.833;

// firstDay: the first midnight, timeOfDay: milliseconds after it.
for (let day = 0; day < days; day++) {
    const time = fromDate(new Date(firstDay + day * DAY_MS + timeOfDay));
    const jd = julianEphemerisDay(time);
    // The parallactic angle turns the sky's north into the zenith.
    const q = moon.parallacticAngle(time, observer);
    drawMoonDisc(day, {
        lit: moon.illuminatedFraction(jd),
        earthshine: moon.earthshine(jd),
        libration: moon.librations(jd),
        // Counterclockwise from up, already turned into this sky.
        brightLimb: moon.brightLimbAngle(time, observer),
        north: moon.positionAngleOfAxis(jd) - q,
        up: isUp(moon.horizontalPosition(time, observer).altitude),
    });
}

// The lanes, in ten-minute steps: the altitude picks the color.
for (let minute = 0; minute < days * 1440; minute += 10) {
    const time = fromDate(new Date(firstDay + minute * 60_000));
    drawLane("moon", minute, moon.horizontalPosition(time, observer).altitude);
    drawLane("sun", minute, sun.horizontalPosition(time, observer).altitude);
}

Filled in for the chosen place, that is a whole month at once. Slide the time of day beneath it, and every Moon turns with the sky:

The Moon on every day of a month at the chosen time of day, as it stands in the sky at the chosen place: beside the date its compass point and, after the arrow, its altitude, hatched while it is below the horizon. Under each week, midnight to midnight, when the Moon is up (yellow while low) and the Sun (fading through civil twilight); the ticks mark the time of day, which the slider below sets. Hover or tap a day for its moonrise and moonset, click it to make it the moment.
Moment and place

The same few functions reach beyond this chapter. Close to the code above: a sun path diagram with the Sun and the Moon every hour, or a search for the night the Moon rises behind a landmark. A little further: your horizon with the eclipses of the coming years, or a sundial drawn for your latitude. And everything that depends on where sunlight falls, from a solar panel’s yield over a year to the sun studies architects run in CAD.

Precise or approximate?

Every quantity comes in two variants. The precise one follows Jean Meeus’ Astronomical Algorithms [1]Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998., the standard reference for this kind of computation. The approximate one follows the night sky model of Jensen et al. [8]Henrik Wann Jensen, Frédo Durand, Julie Dorsey, Michael M. Stark, Peter Shirley, Simon Premože: A Physically-Based Night Sky Model. SIGGRAPH, 2001., with far fewer terms. The tables show the precise values; approx in any table’s heading switches all of them, and hovering an approximate value shows how far off it is.

For most rendering, the approximation is plenty. Over a year, the Sun’s approximate altitude stays within 0.002° of the precise one, invisible next to a disc half a degree wide. The Moon’s can be off by almost a tenth of a degree, a fifth of its diameter: fine for a sky dome, not for an eclipse. That is what the precise variant is for, along with the exact moment of sunset or a telescope view. Both live behind the same names, so switching is a change of import path.

import { sun } from "@himmelszelt/sternzeit"; // precise
import { sun as fast } from "@himmelszelt/sternzeit/approx"; // approximate

The people behind the numbers

Almost every number in this library goes back to Jean Meeus, a Belgian meteorologist who worked at Brussels Airport until 1993 and computed the sky in his spare time. His Astronomical Algorithms [1]Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998. turned the celestial mechanics of the observatories into recipes for a pocket calculator, and it is still the book every astronomy library is checked against. The asteroid 2213 Meeus carries his name.

The library’s two refraction formulas, one for each direction, come from either end of the craft. George G. Bennett fitted the one behind earth.atmosphericRefractionFromApparent for navigators, who by 1982 worked out their positions on programmable calculators [6]G. G. Bennett: The Calculation of Astronomical Refraction in Marine Navigation. Journal of Navigation 35, 1982.. Þorsteinn Sæmundsson, astronomer at the University of Iceland and for decades the editor of its almanac, fitted the other, behind earth.atmosphericRefraction [5]Þorsteinn Sæmundsson: Atmospheric Refraction. Sky and Telescope 72, 1986.. The approximate variants follow the night sky model of Henrik Wann Jensen and his colleagues [8]Henrik Wann Jensen, Frédo Durand, Julie Dorsey, Michael M. Stark, Peter Shirley, Simon Premože: A Physically-Based Night Sky Model. SIGGRAPH, 2001., which renders moonlight, starlight, zodiacal light, and airglow from their physics. And Fred Espenak, for decades at NASA’s Goddard Space Flight Center and known as Mr. Eclipse, compiled the eclipse canons with Meeus, along with the polynomials for ΔT [10]Fred Espenak, Jean Meeus: Five Millennium Canon of Solar Eclipses: -1999 to +3000. NASA Technical Publication TP-2006-214141, 2006..

References

  1. Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998. The precise variants' main source.
  2. NASA Space Science Data Coordinated Archive: Planetary Fact Sheets: Earth, Moon, Sun. nssdc.gsfc.nasa.gov, 2024. Mean radii and similar constants.
  3. Tomoyuki Nishita, Takao Sirai, Katsumi Tadamura, Eihachiro Nakamae: Display of the Earth Taking into Account Atmospheric Scattering. SIGGRAPH, 1993.
  4. Eric Bruneton, Fabrice Neyret: Precomputed Atmospheric Scattering. Computer Graphics Forum 27(4), EGSR, 2008.
  5. Þorsteinn Sæmundsson: Atmospheric Refraction. Sky and Telescope 72, 1986. Atmospheric refraction from the true altitude.
  6. G. G. Bennett: The Calculation of Astronomical Refraction in Marine Navigation. Journal of Navigation 35, 1982. Atmospheric refraction from the apparent altitude.
  7. Hendrik C. van de Hulst: Multiple Light Scattering: Tables, Formulas, and Applications. Academic Press, 1980. Earthshine from the Earth's phase as seen from the Moon.
  8. Henrik Wann Jensen, Frédo Durand, Julie Dorsey, Michael M. Stark, Peter Shirley, Simon Premože: A Physically-Based Night Sky Model. SIGGRAPH, 2001. The approximate variants' main source.
  9. Daniel Müller (now Limberger), Juri Engel, Jürgen Döllner: Single-Pass Rendering of Day and Night Sky Phenomena. Vision, Modeling and Visualization (VMV), 2012.
  10. Fred Espenak, Jean Meeus: Five Millennium Canon of Solar Eclipses: -1999 to +3000. NASA Technical Publication TP-2006-214141, 2006. Every solar eclipse from 2000 BCE to 3000 CE, and the polynomials for ΔT.
  11. Fred Espenak, Jean Meeus: Five Millennium Canon of Lunar Eclipses: -1999 to +3000. NASA Technical Publication TP-2009-214172, 2009. Every lunar eclipse from 2000 BCE to 3000 CE.
  12. Theodore C. Yapo, Barbara Cutler: Rendering Lunar Eclipses. Graphics Interface, 2009. Traces light through Earth's atmosphere into the umbra: physically based, far from real time.