mirror of
https://github.com/acamarata/pray-calc.git
synced 2026-06-30 19:04:26 +00:00
- Extract magic numbers into named constants (DHUHR_OFFSET_MINUTES, ANGLE_MIN/MAX, LAT_SCALE) with source citations for MCW coefficients - Add input validation (RangeError) for lat, lng, tz, elevation on all public API functions (getTimes, getTimesAll) - Optimize solar ephemeris: computeAngles() returns declination so getTimes/getTimesAll reuse it for Asr instead of computing twice - DRY: shared constants.ts for DEG, Dhuhr offset, angle bounds - Improve MethodEntry type with labeled tuple elements and NaN docs - Add stricter tsconfig (noImplicitReturns, noFallthroughCasesInSwitch) - Switch tests to node:test framework (TAP output, describe/it blocks) - Add 8 new input validation tests (104 ESM + 13 CJS total) - Add ESLint + Prettier with CI lint job - Remove src/ from npm package files (smaller published tarball) - Document NaN return behavior in JSDoc for getTimes/getTimesAll
95 lines
3.6 KiB
TypeScript
95 lines
3.6 KiB
TypeScript
/**
|
|
* Core prayer times computation using the PrayCalc Dynamic Method.
|
|
*
|
|
* Returns all prayer times as fractional hours using the dynamic twilight
|
|
* angle algorithm. Times are in local time as determined by the timezone
|
|
* offset (tz parameter).
|
|
*/
|
|
|
|
import { getSpa } from 'nrel-spa';
|
|
import { computeAngles } from './getAngles.js';
|
|
import { getAsr } from './getAsr.js';
|
|
import { getQiyam } from './getQiyam.js';
|
|
import { validateInputs } from './validate.js';
|
|
import { DHUHR_OFFSET_MINUTES } from './constants.js';
|
|
import type { PrayerTimes } from './types.js';
|
|
|
|
/**
|
|
* Compute prayer times for a given date and location.
|
|
*
|
|
* Uses the dynamic twilight angle algorithm to determine Fajr and Isha
|
|
* depression angles, then solves for all prayer events via SPA.
|
|
*
|
|
* @param date - Observer's local date (time-of-day is ignored)
|
|
* @param lat - Latitude in decimal degrees (-90 to 90, south = negative)
|
|
* @param lng - Longitude in decimal degrees (-180 to 180, west = negative)
|
|
* @param tz - UTC offset in hours (e.g. -5 for EST). Defaults to the
|
|
* system timezone derived from the Date object.
|
|
* @param elevation - Observer elevation in meters (default: 0)
|
|
* @param temperature - Ambient temperature in °C (default: 15)
|
|
* @param pressure - Atmospheric pressure in mbar/hPa (default: 1013.25)
|
|
* @param hanafi - Asr convention: false = Shafi'i/Maliki/Hanbali (default),
|
|
* true = Hanafi
|
|
* @returns Prayer times as fractional hours and the dynamic angles used.
|
|
* Any time that cannot be computed (e.g. polar night/day, or the
|
|
* sun never reaching the required depression) is returned as `NaN`.
|
|
* @throws {RangeError} if lat, lng, tz, or elevation are out of valid range
|
|
*/
|
|
export function getTimes(
|
|
date: Date,
|
|
lat: number,
|
|
lng: number,
|
|
tz: number = -date.getTimezoneOffset() / 60,
|
|
elevation = 0,
|
|
temperature = 15,
|
|
pressure = 1013.25,
|
|
hanafi = false,
|
|
): PrayerTimes {
|
|
validateInputs(lat, lng, tz, elevation);
|
|
|
|
// 1. Compute dynamic twilight angles and reuse solar declination.
|
|
const { fajrAngle, ishaAngle, decl } = computeAngles(
|
|
date,
|
|
lat,
|
|
lng,
|
|
elevation,
|
|
temperature,
|
|
pressure,
|
|
);
|
|
|
|
// 2. Convert depression angles to SPA zenith angles.
|
|
// SPA uses zenith angle (90° + depression) for custom altitude events.
|
|
const fajrZenith = 90 + fajrAngle;
|
|
const ishaZenith = 90 + ishaAngle;
|
|
|
|
// 3. Run SPA for solar position + custom twilight times.
|
|
const spaOpts = { elevation, temperature, pressure };
|
|
const spaData = getSpa(date, lat, lng, tz, spaOpts, [fajrZenith, ishaZenith]);
|
|
|
|
const fajrTime = spaData.angles[0].sunrise;
|
|
const sunriseTime = spaData.sunrise;
|
|
const noonTime = spaData.solarNoon;
|
|
const maghribTime = spaData.sunset;
|
|
const ishaTime = spaData.angles[1].sunset;
|
|
|
|
// Dhuhr: offset after solar noon (standard practice to confirm transit).
|
|
const dhuhrTime = noonTime + DHUHR_OFFSET_MINUTES / 60;
|
|
|
|
// 4. Asr time (reuses declination from computeAngles — no extra ephemeris call).
|
|
const asrTime = getAsr(noonTime, lat, decl, hanafi);
|
|
|
|
// 5. Qiyam al-Layl (last third of the night).
|
|
const qiyamTime = getQiyam(fajrTime, ishaTime);
|
|
|
|
return {
|
|
Qiyam: isFinite(qiyamTime) ? qiyamTime : NaN,
|
|
Fajr: isFinite(fajrTime) ? fajrTime : NaN,
|
|
Sunrise: isFinite(sunriseTime) ? sunriseTime : NaN,
|
|
Noon: isFinite(noonTime) ? noonTime : NaN,
|
|
Dhuhr: isFinite(dhuhrTime) ? dhuhrTime : NaN,
|
|
Asr: isFinite(asrTime) ? asrTime : NaN,
|
|
Maghrib: isFinite(maghribTime) ? maghribTime : NaN,
|
|
Isha: isFinite(ishaTime) ? ishaTime : NaN,
|
|
angles: { fajrAngle, ishaAngle },
|
|
};
|
|
}
|