High-precision sunrise and sunset calculation library using NREL's Solar Position Algorithm (SPA).
Visualize the annual sun cycle for any location on Earth with our interactive D3.js chart. Features include automatic timezone detection, reverse geocoding, and persistent caching for improved performance.
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)
- β Advanced twilight detection (Civil, Nautical, Astronomical, Golden, and Blue hours)
- β
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, solar position, etc.)
- β Comprehensive timezone support (implicit and explicit)
- β 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)
azimuthAstro: number; // Azimuth westward from South (astronomical)
elevation: number; // Elevation angle above horizon
rightAscension: number; // Right ascension (degrees)
declination: number; // Declination (degrees)
hourAngle: number; // Local hour angle (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Β°
goldenHour: { // Sun between 6Β° above and horizon
morning: { start: Date | null, end: Date | null };
evening: { start: Date | null, end: Date | null };
} | null;
blueHour: { // Sun between horizon and 4Β° below
morning: { start: Date | null, end: Date | null };
evening: { start: Date | null, end: Date | null };
} | null;
}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: 15)
pressure?: number; // Atmospheric pressure in mbar (default: 1013)
deltaT?: number; // Earth rotation correction in seconds (default: 67)
elevation?: number; // Observer elevation in meters (default: 0)
timezone?: number; // Explicit timezone offset in hours (e.g. -5)
timezoneId?: string; // IANA Timezone ID (e.g. 'America/New_York')
atmosphericRefraction?: number; // Refraction at horizon in degrees (default: 0.5667)
}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
To run the interactive demo locally:
cd demo
npm install
npm run devThe demo will be available at http://localhost:5173.
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.1.4 - Enhanced with full SPA implementation and extended twilight data.
