No description
Find a file
Aric Camarata 51dcf89d63 nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison
Fixed:
- calcSpa with empty angles array no longer crashes (consistent guard with getSpa)
- getSpa with SPA_ZA/SPA_ZA_INC now returns NaN for sunrise/solarNoon/sunset
  instead of misleading 0; calcSpa returns "N/A" for those fields
- lib/spa.js header comment corrected from dist/spa.js to lib/spa.js
- dist/spa.js removed (file moved to lib/spa.js in v2.0.0, stale copy deleted)
- wiki-sync.yml handles first-run when GitHub Wiki repo does not yet exist
- CI pack-check grep uses word-boundary pattern to prevent false prefix matches
- Removed package-import-method=hardlink from .npmrc (pnpm default, caused npm warn)

Added:
- options.function validated before calculation; invalid code throws RangeError
- angles with non-RTS function code throws RangeError (requires suntransit)
- TypeScript function overloads for getSpa and calcSpa; angles typed as
  [number, ...number[]] non-empty tuple, narrows return type automatically
- SpaFormattedAnglesResult interface, consistent with SpaAnglesResult
- CI jobs declare explicit permissions: contents: read
- Wiki: Implementation Comparison page with accuracy table (8 locations vs C
  reference, max delta 0.49 s) and performance benchmarks (nrel-spa vs solar-spa
  vs C, both SPA_ZA_RTS and SPA_ZA modes, 200k iterations on Node v24.6.0)
- Wiki: API Reference updated with named types, all throws, Named Types block
- Wiki: Architecture updated with all exported interfaces
2026-02-25 11:54:03 -05:00
.github/workflows nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
.wiki nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
bin Major fix to core time calculations 2025-05-04 07:50:00 -04:00
lib nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
src nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
.editorconfig v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
.gitignore v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
.npmrc Remove pnpm-specific .npmrc setting that causes npm warning 2026-02-25 11:17:19 -05:00
.nvmrc v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
CHANGELOG.md nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
LICENSE v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
package.json nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
pnpm-lock.yaml v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
pnpm-workspace.yaml v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
README.md nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
test-cjs.cjs v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
test.mjs nrel-spa v2.0.1: validation, NaN returns, overloads, wiki comparison 2026-02-25 11:54:03 -05:00
tsconfig.json v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00
tsup.config.ts v2.0.0: TypeScript rewrite with dual CJS/ESM build 2026-02-25 11:01:38 -05:00

nrel-spa

npm version CI license

Pure JavaScript implementation of the NREL Solar Position Algorithm (SPA). Computes solar zenith angle, azimuth, sunrise, sunset, and solar noon for any location and date. Validated to produce identical results to the original NREL C reference implementation.

Installation

npm install nrel-spa

Quick Start

import { getSpa, calcSpa } from 'nrel-spa';

const date = new Date('2025-06-21T00:00:00Z'); // UTC date/time

// Minimum required parameters
const raw = getSpa(date, 40.7128, -74.006, -4); // New York, EDT (UTC-4)
console.log(raw.sunrise);   // 5.417 (fractional hours)
console.log(raw.solarNoon); // 12.965
console.log(raw.sunset);    // 20.509

// Formatted output — same parameters, HH:MM:SS strings
const fmt = calcSpa(date, 40.7128, -74.006, -4);
console.log(fmt.sunrise);   // "05:25:03"
console.log(fmt.solarNoon); // "12:57:56"
console.log(fmt.sunset);    // "20:30:35"

// With atmospheric parameters and custom zenith angles (twilight)
const result = calcSpa(
  date,
  40.7128,          // latitude (degrees, negative = south)
  -74.006,          // longitude (degrees, negative = west)
  -4,               // timezone offset in hours from UTC
  {
    elevation: 10,  // meters above sea level
    pressure: 1013, // millibars
    temperature: 20 // degrees Celsius
  },
  [96, 102, 108],   // civil, nautical, astronomical twilight zenith angles
);
console.log(result.sunrise);        // "05:25:03"
console.log(result.angles[0]);      // { sunrise: "04:53:...", sunset: "20:02:..." }

API

getSpa(date, latitude, longitude, timezone?, options?, angles?)

Returns raw numerical values. Sunrise, solarNoon, and sunset are fractional hours (e.g., 5.417 for 05:25).

Parameter Type Required Description
date Date Yes UTC date and time for the calculation
latitude number Yes Observer latitude in degrees (-90 to 90)
longitude number Yes Observer longitude in degrees (-180 to 180)
timezone number | null No Hours from UTC. Default: 0
options SpaOptions | null No Atmospheric and calculation parameters
angles number[] No Custom zenith angles in degrees for twilight

Returns: SpaResult (or SpaResultWithAngles when angles is provided)

interface SpaResult {
  zenith:    number; // topocentric zenith angle (degrees)
  azimuth:   number; // topocentric azimuth, eastward from north (degrees)
  sunrise:   number; // local sunrise time (fractional hours)
  solarNoon: number; // local solar noon (fractional hours)
  sunset:    number; // local sunset time (fractional hours)
}

calcSpa(date, latitude, longitude, timezone?, options?, angles?)

Same as getSpa(), but formats sunrise, solarNoon, and sunset as HH:MM:SS strings. Returns "N/A" for those fields during polar day or polar night.

formatTime(hours)

Converts fractional hours to HH:MM:SS format. Returns "N/A" for negative or non-finite values.

import { formatTime } from 'nrel-spa';
formatTime(5.417489); // "05:25:03"
formatTime(-1);       // "N/A"

SpaOptions

Option Type Default Description
elevation number 0 Observer elevation in meters
pressure number 1013 Atmospheric pressure in millibars
temperature number 15 Temperature in degrees Celsius
delta_ut1 number 0 UT1-UTC correction in seconds
delta_t number 67 TT-UTC difference in seconds
slope number 0 Surface slope from horizontal (degrees)
azm_rotation number 0 Surface azimuth rotation from south (degrees)
atmos_refract number 0.5667 Atmospheric refraction at sunrise/sunset (degrees)
function SpaFunctionCode SPA_ZA_RTS Which outputs to compute

Function Codes

import { SPA_ZA, SPA_ZA_INC, SPA_ZA_RTS, SPA_ALL } from 'nrel-spa';
Code Value Computes
SPA_ZA 0 Zenith and azimuth only
SPA_ZA_INC 1 Zenith, azimuth, and incidence angle
SPA_ZA_RTS 2 Zenith, azimuth, sunrise, noon, sunset (default)
SPA_ALL 3 All outputs

Architecture

The core algorithm in lib/spa.js is a direct port of the NREL SPA C source to JavaScript, preserving the same mathematical structure: 63 periodic nutation terms, full heliocentric coordinate calculation, topocentric correction, and atmospheric refraction. It has been validated to produce output identical to the C reference within rounding.

The TypeScript wrapper in src/ provides the public API, input validation, and the formatTime utility. The package ships dual CJS and ESM builds via tsup, with full TypeScript definitions.

See the wiki for a detailed breakdown of the algorithm and architecture.

Compatibility

  • Node.js: 20, 22, 24 (CI tested)
  • ESM: import { getSpa } from 'nrel-spa'
  • CommonJS: const { getSpa } = require('nrel-spa')
  • TypeScript: Full type definitions included

Bundlers (Vite, Webpack, esbuild, Rollup) work via the exports map in package.json.

TypeScript

import {
  getSpa,
  calcSpa,
  formatTime,
  SPA_ZA,
  SPA_ZA_INC,
  SPA_ZA_RTS,
  SPA_ALL,
  type SpaOptions,
  type SpaResult,
  type SpaFormattedResult,
  type SpaAnglesResult,
  type SpaFormattedAnglesResult,
  type SpaResultWithAngles,
  type SpaFormattedResultWithAngles,
  type SpaFunctionCode,
} from 'nrel-spa';

Documentation

Full documentation is available on the GitHub Wiki:

Other packages in this collection:

  • solar-spa: WASM build of the same algorithm, async, for high-throughput batch calculations
  • pray-calc: Islamic prayer times built on nrel-spa

Acknowledgments

The core algorithm is a JavaScript port of the Solar Position Algorithm (SPA) developed by Ibrahim Reda and Afshin Andreas at the National Renewable Energy Laboratory (NREL):

Reda, I., Andreas, A. (2004). "Solar Position Algorithm for Solar Radiation Applications." Solar Energy, 76(5), 577-589. https://doi.org/10.1016/j.solener.2003.12.003

Original source: https://midcdmz.nrel.gov/spa/

License

MIT (TypeScript wrapper and build tooling). The core algorithm in lib/spa.js is a port of NREL's SPA C source, which is subject to its own terms. See the LICENSE file for the full notice.