High-precision sunrise and sunset calculation library using NREL's Solar Position Algorithm (SPA).
This library provides accurate solar position calculations with uncertainties of Β±0.0003 degrees for dates from year -2000 to 6000. It's based on the National Renewable Energy Laboratory's Solar Position Algorithm (SPA), which offers significantly improved accuracy over simplified algorithms.
The implementation is based on:
Reda, I.; Andreas, A. (2004). Solar Position Algorithm for Solar Radiation Applications. Solar Energy, Vol. 76(5), pp. 577-589.
NREL Technical Report: TP-560-34302, Revised January 2008
Distributed by the National Renewable Energy Laboratory
Solar Radiation Research Laboratory
- β High-precision calculations (Β±30 seconds accuracy)
- β
Proper high-latitude handling (returns
nullduring polar day/night) - β Full TypeScript support with comprehensive type definitions
- β ESM and CommonJS dual package exports
- β Zero dependencies for runtime
- β Extended solar calculations (solar noon, twilight times, solar position)
- β Timezone-aware date handling
- β Tree-shakeable modular architecture
npm install sunrise-sunset-jsRequirements: Node.js β₯18.0.0
import { getSunrise, getSunset } from 'sunrise-sunset-js';
// Sunrise today in London
const sunrise = getSunrise(51.5074, -0.1278);
// Sunset on a specific date
const sunset = getSunset(51.5074, -0.1278, new Date('2024-06-21'));
console.log(`Sunrise: ${sunrise?.toLocaleTimeString()}`);
console.log(`Sunset: ${sunset?.toLocaleTimeString()}`);Calculate sunrise time for a given location and date.
Parameters:
latitude(number): Latitude in decimal degrees (-90 to 90)longitude(number): Longitude in decimal degrees (-180 to 180)date(Date, optional): Date to calculate for (defaults to today)options(SpaOptions, optional): Additional calculation options
Returns: Date | null
- Returns
Dateobject with sunrise time - Returns
nullduring polar night (sun never rises)
Example:
// Basic usage
const sunrise = getSunrise(40.7128, -74.0060); // New York City
// Specific date
const summerSolstice = getSunrise(
51.1788,
-1.8262,
new Date('2024-06-21')
); // Stonehenge
// High-latitude location during polar night
const arcticWinter = getSunrise(
69.6496,
18.9560,
new Date('2024-12-21')
); // TromsΓΈ, Norway
console.log(arcticWinter); // null (polar night)Calculate sunset time for a given location and date.
Parameters: Same as getSunrise()
Returns: Date | null
- Returns
Dateobject with sunset time - Returns
nullduring polar day (midnight sun)
Example:
const sunset = getSunset(40.7128, -74.0060);
// During midnight sun period
const arcticSummer = getSunset(
67.9323,
13.0887,
new Date('2024-06-21')
); // Reine, Norway
console.log(arcticSummer); // null (midnight sun)Calculate solar noon (sun transit) - when the sun reaches its highest point in the sky.
Returns: Date | null
Example:
const solarNoon = getSolarNoon(51.5074, -0.1278);
console.log(`Solar noon: ${solarNoon?.toLocaleTimeString()}`);Calculate the sun's position in the sky at a given time.
Returns: SolarPosition | null
interface SolarPosition {
zenith: number; // Zenith angle (0Β° = directly overhead)
azimuth: number; // Azimuth angle (0Β° = North, 90Β° = East)
elevation: number; // Elevation angle above horizon
rightAscension: number; // Right ascension (hours)
declination: number; // Declination (degrees)
}Example:
const position = getSolarPosition(51.5074, -0.1278);
if (position) {
console.log(`Azimuth: ${position.azimuth.toFixed(2)}Β°`);
console.log(`Elevation: ${position.elevation.toFixed(2)}Β°`);
console.log(`Zenith: ${position.zenith.toFixed(2)}Β°`);
}Calculate twilight times (civil, nautical, and astronomical).
Returns: TwilightTimes | null
interface TwilightTimes {
civilDawn: Date | null; // Sun at -6Β° (before sunrise)
civilDusk: Date | null; // Sun at -6Β° (after sunset)
nauticalDawn: Date | null; // Sun at -12Β°
nauticalDusk: Date | null; // Sun at -12Β°
astronomicalDawn: Date | null; // Sun at -18Β°
astronomicalDusk: Date | null; // Sun at -18Β°
}Example:
const twilight = getTwilight(51.5074, -0.1278);
if (twilight) {
console.log(`Civil dawn: ${twilight.civilDawn?.toLocaleTimeString()}`);
console.log(`Civil dusk: ${twilight.civilDusk?.toLocaleTimeString()}`);
console.log(`Astronomical dawn: ${twilight.astronomicalDawn?.toLocaleTimeString()}`);
}Get all sun times in a single efficient calculation.
Returns: Object with all sun times
interface SunTimes {
sunrise: Date | null;
sunset: Date | null;
solarNoon: Date | null;
twilight: TwilightTimes | null;
}Example:
const times = getSunTimes(51.5074, -0.1278);
console.log('Sunrise:', times.sunrise);
console.log('Solar Noon:', times.solarNoon);
console.log('Sunset:', times.sunset);
console.log('Civil Dawn:', times.twilight?.civilDawn);
console.log('Civil Dusk:', times.twilight?.civilDusk);All functions accept an optional SpaOptions parameter:
interface SpaOptions {
temperature?: number; // Temperature in Β°C (default: 14.6)
pressure?: number; // Atmospheric pressure in mbar (default: 1013)
deltaT?: number; // Difference between earth rotation time and terrestrial time
elevation?: number; // Observer elevation in meters (default: 0)
}Example:
// Calculate for high-altitude location
const sunrise = getSunrise(
27.9881, // Everest Base Camp
86.9250,
new Date(),
{
elevation: 5364, // meters above sea level
temperature: -10, // Β°C
pressure: 500 // mbar (lower at altitude)
}
);import { getSunrise, getSunset } from 'sunrise-sunset-js';
navigator.geolocation.getCurrentPosition((position) => {
const { latitude, longitude } = position.coords;
const sunrise = getSunrise(latitude, longitude);
const sunset = getSunset(latitude, longitude);
console.log(`Sunrise at your location: ${sunrise?.toLocaleTimeString()}`);
console.log(`Sunset at your location: ${sunset?.toLocaleTimeString()}`);
});import { getSunrise, getSunset } from 'sunrise-sunset-js';
function getAnnualSolarEvents(latitude: number, longitude: number, year: number) {
const events = [];
const startDate = new Date(year, 0, 1);
for (let day = 0; day < 365; day++) {
const date = new Date(startDate);
date.setDate(startDate.getDate() + day);
const sunrise = getSunrise(latitude, longitude, date);
const sunset = getSunset(latitude, longitude, date);
if (sunrise) events.push({ type: 'sunrise', date: sunrise });
if (sunset) events.push({ type: 'sunset', date: sunset });
}
return events;
}
// Get all sunrise/sunset times for 2024 in London
const events = getAnnualSolarEvents(51.5074, -0.1278, 2024);import { getSunrise, getSunset } from 'sunrise-sunset-js';
function getDaylightHours(latitude: number, longitude: number, date: Date) {
const sunrise = getSunrise(latitude, longitude, date);
const sunset = getSunset(latitude, longitude, date);
if (!sunrise || !sunset) {
// Polar day or polar night
return sunrise === null && sunset === null ? 0 : 24;
}
const milliseconds = sunset.getTime() - sunrise.getTime();
return milliseconds / (1000 * 60 * 60); // Convert to hours
}
const hours = getDaylightHours(51.5074, -0.1278, new Date('2024-06-21'));
console.log(`Daylight hours on summer solstice: ${hours.toFixed(2)}`);import { getSunrise, getSunset, getSolarPosition } from 'sunrise-sunset-js';
function isGoldenHour(latitude: number, longitude: number, date: Date): boolean {
const position = getSolarPosition(latitude, longitude, date);
if (!position) return false;
// Golden hour: sun elevation between 0Β° and 6Β° above horizon
return position.elevation > 0 && position.elevation < 6;
}
const now = new Date();
if (isGoldenHour(51.5074, -0.1278, now)) {
console.log('Perfect time for photography! πΈ');
}src/
βββ index.ts # Main API exports
βββ types.ts # TypeScript type definitions
βββ constants.ts # Physical constants & periodic terms
βββ spa.ts # Main SPA calculation orchestrator
βββ calculations/
β βββ earth.ts # Earth heliocentric position
β βββ sun.ts # Geocentric sun position
β βββ nutation.ts # Nutation & obliquity calculations
β βββ observer.ts # Topocentric corrections
β βββ rts.ts # Rise/Transit/Set calculations
βββ utils/
βββ date.ts # Julian day conversions
βββ time.ts # Time conversion utilities
βββ math.ts # Mathematical helpers
The library includes comprehensive test coverage:
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Generate coverage report
npm run test:coverageTest suite includes:
- β Basic sunrise/sunset calculations
- β High-latitude edge cases (polar day/night)
- β Solar noon calculations
- β Solar position accuracy
- β Twilight time calculations
- β Timezone handling
- β All sun times in single call
The v3.0.0 release brings significant improvements but maintains backward compatibility for basic usage:
Breaking changes:
- Node.js 18+ required (was Node 8+)
- Returns
nullinstead of incorrect dates during polar day/night - Slightly different times due to improved algorithm accuracy (Β±30 seconds vs Β±1-2 minutes)
What stays the same:
getSunrise()andgetSunset()function signatures unchanged- Still works in browser and Node.js
- TypeScript support (now with strict types)
New features:
// v2.x - only sunrise/sunset
const sunrise = getSunrise(lat, lng);
const sunset = getSunset(lat, lng);
// v3.0 - extended API
const solarNoon = getSolarNoon(lat, lng);
const position = getSolarPosition(lat, lng);
const twilight = getTwilight(lat, lng);
const allTimes = getSunTimes(lat, lng); // Single efficient call# Install dependencies
npm install
# Build the library
npm run build
# Type check
npm run type-check
# Run all checks (type-check + build + test)
npm run prepublishOnlyBuild outputs:
dist/index.js- ESM bundle with source mapsdist/index.cjs- CommonJS bundle with source mapsdist/index.d.ts- TypeScript declarations
The previous version used a simplified USNO algorithm with ~1-2 minute accuracy. The NREL SPA algorithm provides:
- Higher accuracy: Β±30 seconds vs Β±1-2 minutes
- Wider date range: Year -2000 to 6000 (vs 1900-2100)
- Better high-latitude handling: Proper polar day/night detection
- More comprehensive: Includes nutation, aberration, atmospheric refraction
1. Julian Day Calculation (date.ts)
β
2. Earth Heliocentric Position (earth.ts)
- Longitude, latitude, radius vector
- ~250 periodic terms for precision
β
3. Geocentric Sun Position (sun.ts)
- Apply nutation corrections
- Calculate apparent position
β
4. Observer Corrections (observer.ts)
- Topocentric adjustments
- Atmospheric refraction
β
5. Rise/Transit/Set Times (rts.ts)
- Solve for horizon crossing
- Handle polar day/night cases
The algorithm accounts for:
- Earth's elliptical orbit
- Axial precession and nutation
- Atmospheric refraction
- Observer elevation
- Temperature and pressure effects
- Gravitational aberration
MIT
Contributions welcome! Please open an issue or submit a pull request.
- NREL (National Renewable Energy Laboratory) for the SPA algorithm
- Ibrahim Reda & Afshin Andreas for their comprehensive research paper
- Original library contributors for establishing the foundation
- Reda, I.; Andreas, A. (2004). "Solar Position Algorithm for Solar Radiation Applications." Solar Energy, 76(5), 577-589.
- NREL Technical Report TP-560-34302 (Revised January 2008)
- NREL Solar Position Algorithm
Version 3.0.0 - Completely rewritten with NREL's Solar Position Algorithm