High-accuracy topocentric solar position, solar transit, and arbitrary-horizon crossing calculations for TypeScript.
@yogur/solpos is an ESM package for Node.js 20 or later and browser-oriented runtimes. It provides:
- geometric and optional atmosphere-corrected topocentric solar position;
- daily solar transit;
- sunrise/sunset and civil, nautical, and astronomical twilight;
- custom geometric solar-center horizons;
- explicit polar and civil-day-boundary outcomes; and
- a convenience Delta-T estimator with caller overrides for precision-sensitive work.
The package is deterministic and offline. Named time-zone calculations use the IANA data.
npm install @yogur/solposimport { calculateSolarPosition } from "@yogur/solpos";
const position = calculateSolarPosition({
instant: {
epochMilliseconds: Date.UTC(2026, 6, 17, 9, 0, 0),
},
observer: {
latitudeDegrees: 33.8938,
longitudeDegrees: 35.5018,
elevationMeters: 50,
},
atmosphere: {
pressureMillibars: 1013.25,
temperatureCelsius: 25,
},
});
console.log(position.azimuthDegrees);
console.log(position.elevationDegrees);
console.log(position.apparent?.elevationDegrees);Azimuth is measured eastward from true north in [0, 360) degrees. Elevation is geometric unless read
from the optional apparent result. Atmospheric refraction is evaluated only when both pressure and
temperature are supplied, and its correction is zero below the geometric horizon.
import { calculateDailySolarEvents } from "@yogur/solpos";
const events = calculateDailySolarEvents({
date: { year: 2026, month: 7, day: 17 },
timeZone: "Asia/Beirut",
location: {
latitudeDegrees: 33.8938,
longitudeDegrees: 35.5018,
},
horizons: ["sunrise-sunset", "civil-twilight", { elevationDegrees: -16 }],
});
console.log(new Date(events.transit.epochMilliseconds));
for (const event of events.horizons) {
switch (event.outcome.kind) {
case "crossings":
console.log(
new Date(event.outcome.ascending.epochMilliseconds),
new Date(event.outcome.descending.epochMilliseconds),
);
break;
case "ascending-only":
console.log(new Date(event.outcome.ascending.epochMilliseconds));
break;
case "descending-only":
console.log(new Date(event.outcome.descending.epochMilliseconds));
break;
case "never-above":
case "never-below":
console.log(event.outcome.kind);
break;
}
}The date is proleptic Gregorian with astronomical year numbering. timeZone accepts an IANA
identifier or a textual fixed offset such as "+03:00" or "UTC". Returned instants are integer Unix
epoch milliseconds and always belong to the requested local civil day.
Standard horizons are geometric solar-center elevations:
| Name | Elevation |
|---|---|
sunrise-sunset |
-0.8333 |
civil-twilight |
-6 |
nautical-twilight |
-12 |
astronomical-twilight |
-18 |
Custom horizons do not receive hidden corrections for terrain, observer elevation, horizon dip, solar
radius, or atmosphere. Incorporate any desired adjustment into elevationDegrees.
Calculations require Delta-T (TT - UT1) and UT1 - UTC. By default, the package estimates Delta-T
and uses UT1 - UTC = 0. Every result reports the values and their sources.
const position = calculateSolarPosition({
instant: { epochMilliseconds },
observer,
timeScales: {
deltaTSeconds: 69.2,
ut1MinusUtcSeconds: 0.04,
},
});The built-in estimator covers years -1999 through 3000. Supply deltaTSeconds explicitly outside
that interval or when an observed or more suitable modeled value is available. See
the Delta-T model for provenance and limitations.
TypeErrorreports wrong runtime shapes, primitive types, and non-finite numbers.RangeErrorreports values outside the documented ranges, invalid dates or zones, and exceptional civil days that cannot fit the singular result shape.SolarCalculationErrorwith code"NUMERICAL_FAILURE"reports an unexpected finite-input numerical failure.
never-above and never-below are ordinary horizon outcomes, not errors.
The API reference defines all units, ranges, calendar behavior, outcomes, and error conditions.
Published worked values and stage calculations guard conformance to the selected mathematical procedure. A separately sourced regression corpus covers both hemispheres, equatorial and high latitudes, atmospheric inputs, civil-day transitions, custom horizons, single-direction crossings, polar day, and polar night.
Computational agreement is not the same as physical prediction accuracy. The revised report states
approximately +/-0.0003 deg uncertainty for solar zenith and azimuth over years -2000 through
6000. Event observations also depend on atmosphere, terrain, the visible horizon, solar-radius
convention, Earth-orientation inputs, and time-zone data. See the
conformance record for measured ceilings and maxima.
The package intentionally does not expose astronomical intermediate stages, coefficient arrays, surface incidence, irradiance, formatted local times, terrain models, or application-specific scheduling policy.
The implementation follows the selected procedures in:
Ibrahim Reda and Afshin Andreas, Solar Position Algorithm for Solar Radiation Applications (Revised), NREL/TP-560-34302, revised January 2008 (report, DOI).
The repository preserves the reviewed mathematical procedure and conformance decisions used to implement the focused package surface.
MIT