@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.
Contents
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.
Drag to rotate. Visualized with Zdog.
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.
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:
mean: on an idealized, circular orbit at constant speed.true: on the real, elliptical orbit.apparent: where the light seems to come from, after nutation and aberrationAberration A shift of a body's apparent position towards the direction Earth is moving, by up to about 20.5 arcseconds, because the light arrives at a moving observer at a slant, like rain seen from a running car. Discovered by James Bradley in 1727..topocentric: from the observer’s place, still in right ascension and declination.horizontal: as altitude and azimuth above the observer’s horizon.
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.
Drag to rotate. Visualized with Zdog.
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.
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).
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.
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:
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
- Jean Meeus: Astronomical Algorithms. 2nd edition, Willmann-Bell, 1998. The precise variants' main source.
- NASA Space Science Data Coordinated Archive: Planetary Fact Sheets: Earth, Moon, Sun. nssdc.gsfc.nasa.gov, 2024. Mean radii and similar constants.
- Tomoyuki Nishita, Takao Sirai, Katsumi Tadamura, Eihachiro Nakamae: Display of the Earth Taking into Account Atmospheric Scattering. SIGGRAPH, 1993.
- Eric Bruneton, Fabrice Neyret: Precomputed Atmospheric Scattering. Computer Graphics Forum 27(4), EGSR, 2008.
- Þorsteinn Sæmundsson: Atmospheric Refraction. Sky and Telescope 72, 1986. Atmospheric refraction from the true altitude.
- G. G. Bennett: The Calculation of Astronomical Refraction in Marine Navigation. Journal of Navigation 35, 1982. Atmospheric refraction from the apparent altitude.
- 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.
- 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.
- 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.
- 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.
- 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.
- 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.