An offline sky engine for Swift and TypeScript

Where the Sun, Moon and stars are, when they rise and set, how the Moon is lit, when the Earth's shadow crosses it, and when the Moon crosses the Sun from your location. No network, no data files to ship, no API key — just geometry, in your app or on your boat. The sky below is live: every star in it is this library running in your browser, placed for your location and the moment you choose. The Sun, Moon, and planet pages have more to explore.

The same astronomy engine powers Slackwater's Sun and Moon views, with Moon illumination and sunrise/sunset alongside the tide timeline.

Key Features

Offline by construction

Zero network calls and zero runtime data files. Every Sun, Moon, planet, star and eclipse on these pages is computed in your browser from pure geometry; only the map tiles behind the eclipse paths are downloaded. The same code runs on a boat with no signal.

Twin ports, one behavior

TypeScript and Swift implementations validated against a shared corpus of JPL Horizons, USNO and Espenak fixtures. Neither port is the other's oracle.

Sun, Moon, planets and eclipses

Positions, rise and set, all three twilights, any Sun altitude for golden and blue hour, upper and lower transits, moon phase, illumination and tilt, all eight planets, lunar and local solar eclipses with contact times, every solar eclipse on Earth with its central line, and alt/az for any star you give a catalog position.

Held to a tolerance

Rise and set times agree with USNO to within a minute across 1950–2100, and eclipse peaks match the Espenak catalog to roughly 15 seconds.

Built for interactive sky views

Almanac uses astronomy translated from Don Cross's Astronomy Engine, with optimized event searches and TypeScript Sun calculations. Scrub the sky above to see positions update in your browser. Both ports share correctness fixtures and a performance suite covering 30 workloads, including planetary tracks and rise/set.

Median query time saved compared with Almanac 0.4.1
WorkloadTypeScriptSwift
Sun events · one year73.4%62.8%
Moon events · one year57.6%57.4%
Sun events · 228 hours73.4%61.9%
Moon events · 228 hours57.1%57.0%

Measurements use seven interleaved process pairs with the same harness and toolchain; build and startup time are excluded. Timings vary by machine. CI checks for median regressions above 20%. Read the benchmark results and reproduction instructions.

Using Almanac

TypeScript

npm install @openwaters/almanac@0.8.0
import { sunEvents, sunAltitudeEvents, moonOrientation, starAltAz, nextLunarEclipse, lunarEclipseVisibility, nextSolarEclipse, nextGlobalSolarEclipse } from '@openwaters/almanac';

const observer = { latitudeDeg: 48.5, longitudeDeg: -123.0 };

// Sunrise, sunset, twilights and transit for the next 24 hours
const now = new Date();
const tomorrow = new Date(now.getTime() + 24 * 60 * 60 * 1000);
for (const { kind, time } of sunEvents(now, tomorrow, observer)) {
  console.log(kind, time.toISOString());
}

// Golden hour ends when the Sun rises through +6° and starts when it falls back
console.log(sunAltitudeEvents(now, tomorrow, observer, 6));

// Which way the Moon's lit side faces, in degrees from straight up
const moon = moonOrientation(now, observer);
console.log(moon.brightLimbAngleDeg - moon.parallacticAngleDeg);

// The next lunar eclipse, and whether you'll see it
const eclipse = nextLunarEclipse(now);
const visibility = lunarEclipseVisibility(eclipse, observer);
console.log(eclipse.kind, eclipse.peak, visibility.visibleAtPeak);

// The next solar eclipse visible from this location
const solar = nextSolarEclipse(now, observer);
console.log(solar.kind, solar.peak, solar.obscuration);

// The next solar eclipse anywhere on Earth, and where its shadow axis lands
const anywhere = nextGlobalSolarEclipse(now);
console.log(anywhere.kind, anywhere.peak, anywhere.latitudeDeg, anywhere.longitudeDeg);

// Where Sirius is right now, from its J2000 catalog position
console.log(starAltAz(101.287, -16.716, now, observer));

Swift

.package(url: "https://github.com/openwatersio/almanac.git", exact: "0.8.0")
import Almanac
import Foundation

let observer = try Observer(latitudeDeg: 48.5, longitudeDeg: -123.0)

let now = Date()
let tomorrow = now.addingTimeInterval(24 * 60 * 60)
for event in try sunEvents(from: now, to: tomorrow, observer: observer) {
  print(event.kind, event.time)
}

print(try sunAltitudeEvents(from: now, to: tomorrow, observer: observer, altitudeDeg: 6))

let moon = try moonOrientation(at: now, observer: observer)
print(moon.brightLimbAngleDeg - moon.parallacticAngleDeg)

let eclipse = try nextLunarEclipse(after: now)
let visibility = try lunarEclipseVisibility(eclipse, observer: observer)
print(eclipse.kind, eclipse.peak, visibility.visibleAtPeak)

let solar = try nextSolarEclipse(after: now, observer: observer)
print(solar.kind, solar.peak, solar.obscuration)

let anywhere = try nextGlobalSolarEclipse(after: now)
if let latitude = anywhere.latitudeDeg, let longitude = anywhere.longitudeDeg {
  print(anywhere.kind, anywhere.peak, latitude, longitude)
}

let sirius = try starAltAz(raDeg: 101.287, decDeg: -16.716, at: now, observer: observer)
print(sirius.altDeg, sirius.azDeg)

Supported interval: 1950-01-01 through 2100-12-31. See the supported scope for capabilities and limits. Coming from another library? Almanac has migration guides from SunCalc and Solar, and a comparison with other libraries.

Free & Open Source

Almanac is MIT licensed, with its astronomy translated from Don Cross's Astronomy Engine. Use it, contribute to it, or fork it.