Skip to content

Sunrise, sunset, twilight, noon, dawn and dusk time calculation for given coordinates with zero dependencies.

Notifications You must be signed in to change notification settings

udivankin/sunrise-sunset

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

67 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

sunrise-sunset-js

High-precision sunrise and sunset calculation library using NREL's Solar Position Algorithm (SPA).

npm version Node.js TypeScript

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.

Demo Screenshot

Overview

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.

Algorithm Reference

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

Features

  • βœ… High-precision calculations (Β±30 seconds accuracy)
  • βœ… Advanced twilight detection (Civil, Nautical, Astronomical, Golden, and Blue hours)
  • βœ… Proper high-latitude handling (returns null during 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

Installation

npm install sunrise-sunset-js

Requirements: Node.js β‰₯18.0.0

Quick Start

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()}`);

API Reference

getSunrise(latitude, longitude, date?, options?)

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 Date object with sunrise time
  • Returns null during 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)

getSunset(latitude, longitude, date?, options?)

Calculate sunset time for a given location and date.

Parameters: Same as getSunrise()

Returns: Date | null

  • Returns Date object with sunset time
  • Returns null during 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)

getSolarNoon(latitude, longitude, date?, options?)

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()}`);

getSolarPosition(latitude, longitude, date?, options?)

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)}Β°`);
}

getTwilight(latitude, longitude, date?, options?)

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()}`);
}

getSunTimes(latitude, longitude, date?, options?)

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);

Options

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)
  }
);

Usage Examples

Browser Integration with Geolocation

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()}`);
});

Annual Solar Events

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);

Day Length Calculation

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)}`);

Golden Hour Detection

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! πŸ“Έ');
}

Project Structure

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

Testing

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:coverage

Test 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

Migration from v2.x

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 null instead 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() and getSunset() 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

Build & Development

# 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 prepublishOnly

Build outputs:

  • dist/index.js - ESM bundle with source maps
  • dist/index.cjs - CommonJS bundle with source maps
  • dist/index.d.ts - TypeScript declarations

Algorithm Details

Why SPA?

The previous version used a simplified USNO algorithm with ~1-2 minute accuracy. The NREL SPA algorithm provides:

  1. Higher accuracy: Β±30 seconds vs Β±1-2 minutes
  2. Wider date range: Year -2000 to 6000 (vs 1900-2100)
  3. Better high-latitude handling: Proper polar day/night detection
  4. More comprehensive: Includes nutation, aberration, atmospheric refraction

Calculation Flow

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

Accuracy Factors

The algorithm accounts for:

  • Earth's elliptical orbit
  • Axial precession and nutation
  • Atmospheric refraction
  • Observer elevation
  • Temperature and pressure effects
  • Gravitational aberration

Development

To run the interactive demo locally:

cd demo
npm install
npm run dev

The demo will be available at http://localhost:5173.

License

MIT

Contributing

Contributions welcome! Please open an issue or submit a pull request.

Acknowledgments

  • 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

References

  1. Reda, I.; Andreas, A. (2004). "Solar Position Algorithm for Solar Radiation Applications." Solar Energy, 76(5), 577-589.
  2. NREL Technical Report TP-560-34302 (Revised January 2008)
  3. NREL Solar Position Algorithm

Version 3.1.4 - Enhanced with full SPA implementation and extended twilight data.

About

Sunrise, sunset, twilight, noon, dawn and dusk time calculation for given coordinates with zero dependencies.

Topics

Resources

Stars

Watchers

Forks

Packages

No packages published

Contributors 6