Complete rewrite from plain JavaScript to TypeScript with dual CJS/ESM output via tsup. Removes all legacy .js source files and the old CommonJS-only index. Key changes: - Full TypeScript source in src/ with strict mode and declaration maps - tsup build: dist/index.cjs + dist/index.mjs + dual .d.ts / .d.mts types - 14 traditional fixed-angle methods (UOIF through MUIS) + MSC seasonal method - PCD dynamic algorithm: MSC seasonal base + Earth-Sun distance correction + ecliptic geometry + atmospheric refraction + observer elevation - getTimesAll() batches all 14x2 zenith angles into a single SPA call - getMscFajr() / getMscIsha() expose MSC seasonal reference directly - getAngles() returns the PCD-computed fajrAngle and ishaAngle - High-latitude bounds: angles clipped to [10, 20] above 55N - 106 tests across ESM and CJS (test.mjs + test-cjs.cjs) - CI matrix: Node 20/22/24, typecheck, pack-check - Wiki: 12 reference pages + 6-page research section with global accuracy study, home-territory comparison, observational evidence, and field observation matrix - Moon functions removed (migrated to moon-sighting package) - pnpm-only, Node >=20, sideEffects: false |
||
|---|---|---|
| .github/workflows | ||
| .wiki | ||
| src | ||
| .editorconfig | ||
| .gitignore | ||
| .npmignore | ||
| .npmrc | ||
| .nvmrc | ||
| CHANGELOG.md | ||
| LICENSE | ||
| package.json | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| README.md | ||
| test-cjs.cjs | ||
| test.mjs | ||
| tsconfig.json | ||
| tsup.config.ts | ||
pray-calc
Islamic prayer times for any location and date. The primary method uses a physics-grounded dynamic twilight angle algorithm that adjusts Fajr and Isha angles for latitude, season, Earth-Sun distance, and atmospheric conditions. Fourteen traditional fixed-angle methods are included for direct comparison.
Installation
pnpm add pray-calc # or npm install pray-calc
Quick Start
import { calcTimes } from 'pray-calc';
const times = calcTimes(
new Date('2024-06-21'),
40.7128, // New York latitude
-74.0060, // longitude
-4, // UTC offset (hours)
);
console.log(times.Fajr); // "03:51:24"
console.log(times.Sunrise); // "05:25:08"
console.log(times.Dhuhr); // "13:01:17"
console.log(times.Asr); // "17:02:43"
console.log(times.Maghrib); // "20:31:17"
console.log(times.Isha); // "22:07:43"
console.log(times.angles); // { fajrAngle: 14.8, ishaAngle: 14.6 }
CJS
const { calcTimes } = require('pray-calc');
Compare all methods
import { calcTimesAll } from 'pray-calc';
const all = calcTimesAll(new Date('2024-06-21'), 40.7128, -74.0060, -4);
// Dynamic primary times
console.log(all.Fajr); // "03:51:24"
// Traditional method comparison
console.log(all.Methods.ISNA); // ["03:57:12", "22:22:18"] [fajr, isha]
console.log(all.Methods.MWL); // ["03:25:08", "22:40:31"]
console.log(all.Methods.MSC); // ["03:53:41", "22:09:12"]
API
getTimes(date, lat, lng, tz?, elevation?, temperature?, pressure?, hanafi?)
Returns raw fractional-hour prayer times using the dynamic method.
| Parameter | Type | Default | Description |
|---|---|---|---|
date |
Date |
required | Observer's local date |
lat |
number |
required | Latitude, decimal degrees |
lng |
number |
required | Longitude, decimal degrees |
tz |
number |
system offset | UTC offset in hours |
elevation |
number |
0 |
Meters above sea level |
temperature |
number |
15 |
Ambient temperature, °C |
pressure |
number |
1013.25 |
Atmospheric pressure, mbar |
hanafi |
boolean |
false |
Asr convention: false = Shafi'i, true = Hanafi |
Returns PrayerTimes: { Qiyam, Fajr, Sunrise, Noon, Dhuhr, Asr, Maghrib, Isha, angles }.
All times are fractional hours in local time (e.g., 5.5 = 05:30:00). NaN when an event
cannot be computed (polar night, etc.).
calcTimes(date, lat, lng, tz?, elevation?, temperature?, pressure?, hanafi?)
Same as getTimes, formatted as HH:MM:SS strings. Returns "N/A" for unavailable times.
getTimesAll(...)
Same signature. Returns PrayerTimesAll: extends PrayerTimes with Methods, a record
mapping each of the 14 method IDs to [fajrTime, ishaTime] as fractional hours.
calcTimesAll(...)
Same as getTimesAll, fully formatted. Methods values are [fajrString, ishaString].
getAngles(date, lat, lng, elevation?, temperature?, pressure?)
Returns { fajrAngle, ishaAngle } in degrees (positive = below horizon).
getAsr(solarNoon, latitude, declination, hanafi?)
Computes Asr from solar noon time, latitude, and solar declination. Returns fractional hours.
getQiyam(fajrTime, ishaTime)
Returns the start of the last third of the night as fractional hours.
getMscFajr(date, latitude) / getMscIsha(date, latitude, shafaq?)
Moonsighting Committee Worldwide minute offsets: minutes before sunrise (Fajr) and
minutes after sunset (Isha). shafaq controls which twilight phase is used for Isha:
'general' (default), 'ahmer' (red glow), or 'abyad' (white glow).
METHODS
Exported array of all 14 MethodDefinition objects.
Supported Methods
| ID | Name | Fajr | Isha | Region |
|---|---|---|---|---|
UOIF |
Union des Organisations Islamiques de France | 12° | 12° | France |
ISNACA |
IQNA / Islamic Council of North America | 13° | 13° | Canada |
ISNA |
FCNA / Islamic Society of North America | 15° | 15° | US, UK, AU, NZ |
SAMR |
Spiritual Administration of Muslims of Russia | 16° | 15° | Russia |
IGUT |
Institute of Geophysics, Univ. of Tehran | 17.7° | 14° | Iran |
MWL |
Muslim World League | 18° | 17° | Global |
DIBT |
Diyanet, Turkey | 18° | 17° | Turkey |
Karachi |
Univ. of Islamic Sciences, Karachi | 18° | 18° | PK, BD, IN, AF |
Kuwait |
Kuwait Ministry of Islamic Affairs | 18° | 17.5° | Kuwait |
UAQ |
Umm Al-Qura Univ., Makkah | 18.5° | +90 min | Saudi Arabia |
Qatar |
Qatar / Gulf Standard | 18° | +90 min | Qatar, Gulf |
Egypt |
Egyptian General Authority of Survey | 19.5° | 17.5° | EG, SY, IQ, LB |
MUIS |
Majlis Ugama Islam Singapura | 20° | 18° | Singapore |
MSC |
Moonsighting Committee Worldwide | seasonal | seasonal | Global |
Dynamic Method
Standard prayer time libraries use a fixed angle (e.g., MWL: 18°) applied globally. This works near the equator but fails at higher latitudes: above 48.5°N in summer, the Sun never reaches 18° depression, so a 18°-everywhere library produces missing Isha times. Observational campaigns also show that at mid-latitudes, true dawn appears when the Sun is around 14–16° below the horizon, not 18°.
The dynamic method computes the angle in three layers:
-
MSC seasonal base — Khalid Shaukat's piecewise model, calibrated against field observations across latitudes 0°–55°N/S. Returns minutes before/after sunrise/sunset, converted to depression degrees via spherical trigonometry.
-
Physics corrections — Earth-Sun distance (r via Jean Meeus elliptical orbit), Fourier harmonic smoothing, atmospheric refraction at the computed altitude, and elevation horizon dip.
-
Physical bounds — clipped to [10°, 22°].
At the equator the result converges to approximately 18°, consistent with historical usage. At 50–55°N in summer it falls to 12–14°, matching empirical UK observations.
Full detail: Dynamic Algorithm wiki page
Architecture
- Only runtime dependency:
nrel-spa(NREL Solar Position Algorithm) getSolarEphemeris— Jean Meeus Ch. 25: declination, Earth-Sun distance, ecliptic longetTimesAll— single batch SPA call for all 14×2 + 2 dynamic zenith angles
Full detail: Architecture wiki page
Compatibility
- Node.js >= 20
- ESM and CJS builds included
- TypeScript types bundled
- No browser-incompatible APIs
TypeScript
import type {
PrayerTimes,
FormattedPrayerTimes,
PrayerTimesAll,
FormattedPrayerTimesAll,
TwilightAngles,
MethodDefinition,
} from 'pray-calc';
Documentation
Full documentation: GitHub Wiki
- API Reference
- Dynamic Algorithm
- Traditional Methods
- Architecture
- Twilight Physics
- High-Latitude Handling
Related
- nrel-spa — NREL Solar Position Algorithm
- luxon-hijri — Hijri/Gregorian calendar
- moon-sighting — Crescent visibility
License
MIT. Copyright (c) 2023-2026 Aric Camarata.
See LICENSE for full terms.