pray-calc
The main PrayCalc prayer times engine. Built on NREL SPA for solar position, covering all 14 major calculation methods and a physics-grounded dynamic angle mode.
Overview
pray-calc computes the six daily Islamic prayer times (Fajr, Sunrise, Dhuhr, Asr, Maghrib, Isha) for any location on Earth, any date, and any standard calculation method.
- 14 standard methods (ISNA, MWL, Egypt, Umm al-Qura, Tehran, Karachi, and more)
- Dynamic angle mode (DPC) for physics-grounded Fajr and Isha
- Asr: Shafi'i (shadow = 1×) and Hanafi (shadow = 2×)
- High latitude fallback rules (1/7 night, angle-based, nearest day)
- TypeScript-first, full type exports
- ESM + CommonJS builds
- Zero runtime dependencies (NREL SPA bundled)
GitHub: github.com/acamarata/pray-calc
Installation
<span><span style="color: var(--shiki-token-function)">pnpm</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">add</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string)">pray-calc</span></span>
<span></span>
Requires Node.js 20+.
getPrayerTimes
<span><span style="color: var(--shiki-token-function)">getPrayerTimes</span><span style="color: var(--shiki-color-text)">(options: PrayerTimesOptions): PrayerTimes</span></span>
<span></span>
Options
| Parameter | Type | Required | Description |
|---|---|---|---|
date | Date | Yes | Date to compute times for |
latitude | number | Yes | Observer latitude in decimal degrees |
longitude | number | Yes | Observer longitude in decimal degrees |
timezone | string | Yes | IANA timezone string (e.g. 'America/New_York') |
method | CalculationMethod | No | Calculation method (default: 'MWL') |
asrMethod | 'shafii' | 'hanafi' | No | Asr shadow multiplier (default: 'shafii') |
elevation | number | No | Metres above sea level (default: 0) |
pressure | number | No | Millibars (default: 1013.25) |
temperature | number | No | Celsius (default: 15) |
highLatRule | HighLatRule | No | Fallback for extreme latitudes |
Returns
<span><span style="color: var(--shiki-color-text)">{</span></span>
<span><span style="color: var(--shiki-color-text)"> fajr</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string </span><span style="color: var(--shiki-token-comment)">// "HH:MM" in local time</span></span>
<span><span style="color: var(--shiki-color-text)"> sunrise</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)"> dhuhr</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)"> asr</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)"> maghrib</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)"> isha</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)"> midnight</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)"> date</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string </span><span style="color: var(--shiki-token-comment)">// ISO 8601 date</span></span>
<span><span style="color: var(--shiki-color-text)"> timezone</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)"> method</span><span style="color: var(--shiki-token-punctuation)">:</span><span style="color: var(--shiki-color-text)"> string</span></span>
<span><span style="color: var(--shiki-color-text)">}</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> { getPrayerTimes } </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'pray-calc'</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">times</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getPrayerTimes</span><span style="color: var(--shiki-color-text)">({</span></span>
<span><span style="color: var(--shiki-color-text)"> date</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> latitude</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">41.4993</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> longitude</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-constant)">81.6944</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> timezone</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'America/New_York'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> method</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'ISNA'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">})</span></span>
<span></span>
<span><span style="color: var(--shiki-token-constant)">console</span><span style="color: var(--shiki-token-function)">.log</span><span style="color: var(--shiki-color-text)">(times)</span></span>
<span><span style="color: var(--shiki-token-comment)">// {</span></span>
<span><span style="color: var(--shiki-token-comment)">// fajr: "05:34",</span></span>
<span><span style="color: var(--shiki-token-comment)">// sunrise: "06:52",</span></span>
<span><span style="color: var(--shiki-token-comment)">// dhuhr: "13:02",</span></span>
<span><span style="color: var(--shiki-token-comment)">// asr: "16:28",</span></span>
<span><span style="color: var(--shiki-token-comment)">// maghrib: "19:47",</span></span>
<span><span style="color: var(--shiki-token-comment)">// isha: "21:12",</span></span>
<span><span style="color: var(--shiki-token-comment)">// ...</span></span>
<span><span style="color: var(--shiki-token-comment)">// }</span></span>
<span></span>
Methods
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">type</span><span style="color: var(--shiki-color-text)"> { CalculationMethod } </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'pray-calc'</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">methods</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">CalculationMethod</span><span style="color: var(--shiki-color-text)">[] </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> [</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'ISNA'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 15°/15° — North America</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'MWL'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 18°/17° — Muslim World League</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'EGYPT'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 19.5°/17.5° — Egypt</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'UMM_AL_QURA'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 18°/90min — Saudi Arabia</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'TEHRAN'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 17.7°/14° — Iran</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'KARACHI'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 18°/18° — Pakistan</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'KUWAIT'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 18°/17.5° — Kuwait</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'QATAR'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 18°/90min — Qatar</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'SINGAPORE'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 20°/18° — Singapore</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'FRANCE'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 12°/12° — France</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'RUSSIA'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 16°/15° — Russia</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'GULF'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 19.5°/90min — Gulf states</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'FCNA'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 15°/15° — North America (Fiqh Council)</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'JAKIM'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 20°/18° — Malaysia</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'DPC'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// Dynamic PrayCalc — physics-grounded</span></span>
<span><span style="color: var(--shiki-color-text)">]</span></span>
<span></span>
Dynamic mode
Use method: 'DPC' to enable dynamic angle computation. The algorithm adapts Fajr and Isha depression angles based on latitude, day of year, Earth-Sun distance, and elevation.
<span><span style="color: var(--shiki-token-keyword)">const</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">times</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">getPrayerTimes</span><span style="color: var(--shiki-color-text)">({</span></span>
<span><span style="color: var(--shiki-color-text)"> date</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">new</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">Date</span><span style="color: var(--shiki-color-text)">()</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> latitude</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-constant)">52.48</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> longitude</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">-</span><span style="color: var(--shiki-token-constant)">1.90</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> timezone</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'Europe/London'</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> method</span><span style="color: var(--shiki-token-keyword)">:</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'DPC'</span><span style="color: var(--shiki-token-punctuation)">,</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// dynamic angles</span></span>
<span><span style="color: var(--shiki-color-text)">})</span></span>
<span></span>
Dynamic mode is especially useful at latitudes above 45° or for applications where sky-accurate times are required. See Dynamic vs. Fixed Angles for the full explanation.
TypeScript types
<span><span style="color: var(--shiki-token-keyword)">import</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">type</span><span style="color: var(--shiki-color-text)"> {</span></span>
<span><span style="color: var(--shiki-color-text)"> PrayerTimesOptions</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> PrayerTimes</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> CalculationMethod</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> AsrMethod</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)"> HighLatRule</span><span style="color: var(--shiki-token-punctuation)">,</span></span>
<span><span style="color: var(--shiki-color-text)">} </span><span style="color: var(--shiki-token-keyword)">from</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'pray-calc'</span></span>
<span></span>
<span><span style="color: var(--shiki-token-keyword)">type</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-function)">HighLatRule</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">=</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">|</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'seventh-of-night'</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 1/7 of the night</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">|</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'half-of-night'</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// 1/2 of the night</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">|</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'angle-based'</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// reduced angle fallback</span></span>
<span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-keyword)">|</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-string-expression)">'nearest-day'</span><span style="color: var(--shiki-color-text)"> </span><span style="color: var(--shiki-token-comment)">// nearest valid day</span></span>
<span></span>