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

ParameterTypeRequiredDescription
dateDateYesDate to compute times for
latitudenumberYesObserver latitude in decimal degrees
longitudenumberYesObserver longitude in decimal degrees
timezonestringYesIANA timezone string (e.g. 'America/New_York')
methodCalculationMethodNoCalculation method (default: 'MWL')
asrMethod'shafii' | 'hanafi'NoAsr shadow multiplier (default: 'shafii')
elevationnumberNoMetres above sea level (default: 0)
pressurenumberNoMillibars (default: 1013.25)
temperaturenumberNoCelsius (default: 15)
highLatRuleHighLatRuleNoFallback 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)">// &quot;HH:MM&quot; 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)">&#39;pray-calc&#39;</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)">&#39;America/New_York&#39;</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)">&#39;ISNA&#39;</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: &quot;05:34&quot;,</span></span>
<span><span style="color: var(--shiki-token-comment)">//   sunrise: &quot;06:52&quot;,</span></span>
<span><span style="color: var(--shiki-token-comment)">//   dhuhr: &quot;13:02&quot;,</span></span>
<span><span style="color: var(--shiki-token-comment)">//   asr: &quot;16:28&quot;,</span></span>
<span><span style="color: var(--shiki-token-comment)">//   maghrib: &quot;19:47&quot;,</span></span>
<span><span style="color: var(--shiki-token-comment)">//   isha: &quot;21:12&quot;,</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)">&#39;pray-calc&#39;</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)">&#39;ISNA&#39;</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)">&#39;MWL&#39;</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)">&#39;EGYPT&#39;</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)">&#39;UMM_AL_QURA&#39;</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)">&#39;TEHRAN&#39;</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)">&#39;KARACHI&#39;</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)">&#39;KUWAIT&#39;</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)">&#39;QATAR&#39;</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)">&#39;SINGAPORE&#39;</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)">&#39;FRANCE&#39;</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)">&#39;RUSSIA&#39;</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)">&#39;GULF&#39;</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)">&#39;FCNA&#39;</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)">&#39;JAKIM&#39;</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)">&#39;DPC&#39;</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)">&#39;Europe/London&#39;</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)">&#39;DPC&#39;</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)">&#39;pray-calc&#39;</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)">&#39;seventh-of-night&#39;</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)">&#39;half-of-night&#39;</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)">&#39;angle-based&#39;</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)">&#39;nearest-day&#39;</span><span style="color: var(--shiki-color-text)">        </span><span style="color: var(--shiki-token-comment)">// nearest valid day</span></span>
<span></span>