Almanac
Where the Sun and Moon are, when they rise and set, how the Moon is lit, and when the Earth's shadow crosses it. No network, no data files to ship, no API key — just geometry, in your app or on your boat. The sky below is live: it is this library running in your browser right now.
Key Features
Offline by construction
Zero network calls and zero runtime data files. Everything on this page is computed in your browser from pure geometry — 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 and eclipses
Positions, rise and set, all three twilights, transit, moon phase and illumination, and lunar eclipses with contact times and local visibility.
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.
Using Almanac
TypeScript
npm install @openwaters/almanacimport { sunEvents, nextLunarEclipse, lunarEclipseVisibility } 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());
}
// 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);Swift
.package(url: "https://github.com/openwatersio/almanac.git", from: "0.1.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)
}
let eclipse = try nextLunarEclipse(after: now)
let visibility = try lunarEclipseVisibility(eclipse, observer: observer)
print(eclipse.kind, eclipse.peak, visibility.visibleAtPeak)Supported interval: 1950-01-01 through 2100-12-31. Where the library is going next is written down in the roadmap.
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.