Compare commits

..

17 commits
v2.1.0 ... main

Author SHA1 Message Date
Aric Camarata
f8dcba0cbb
add opt-in anonymous telemetry (#2)
Some checks failed
CI / Test (Node 20) (push) Failing after 40s
CI / Test (Node 22) (push) Failing after 34s
CI / Test (Node 24) (push) Failing after 28s
CI / Coverage (push) Failing after 2s
CI / Typecheck (push) Failing after 33s
CI / Pack Check (push) Failing after 35s
* add opt-in telemetry via @acamarata/telemetry (off by default)

* chore: update lockfile for @acamarata/telemetry devDep
2026-06-30 15:52:06 -04:00
Aric Camarata
1c9f3cde70 build: use prepack hook so npm pack/publish reliably emit index.d.mts 2026-06-13 10:30:53 -04:00
Aric Camarata
6f165b79ad ci: fix eslint parser devDeps, typed linting config, prettier formatting
- Add @typescript-eslint/parser and @typescript-eslint/eslint-plugin as
  direct devDependencies (were only transitive, not linked in node_modules)
- Add files pattern and parserOptions.project to eslint.config.mjs so
  ESLint finds and type-checks src/**/*.ts files correctly
- Run prettier --write to fix formatting across all src files
2026-05-31 08:47:17 -04:00
Aric Camarata
429c176a5e chore: bump to v2.1.2
Add dist/index.d.mts to published package; enforce explicit files field.
2026-05-30 19:12:15 -04:00
Aric Camarata
05b4f577d0 chore: P1 consolidation (TypeDoc API + wiki refresh) 2026-05-30 18:40:31 -04:00
Aric Camarata
76a2ea8a96 docs: refresh TypeDoc API output (T-E8-03 QA-A verify) 2026-05-30 17:48:46 -04:00
Aric Camarata
4227afc2c3 docs: add TypeDoc API generation (typedoc@0.28.19 + typedoc-plugin-markdown@4.11.0)
Add typedoc and typedoc-plugin-markdown as devDependencies. Add typedoc.json config
targeting src/index.ts with markdown output to .github/wiki/api. Add docs script to
package.json. Generate initial API reference pages.

Part of T-E8-03 — TypeDoc automation for all 12 JS/TS packages.
2026-05-30 16:41:58 -04:00
Aric Camarata
af34aef986 chore: remove .npmignore, add c8 coverage, fix ci.yml (T-E5-03)
- Remove .npmignore (violation: npm-package-standard requires files field in
  package.json, not .npmignore; files field was already present and correct)
- Add c8@^10.1.3 to devDependencies for native V8 coverage (ADR-008)
- Fix coverage script: was 'node --test' (missing test.mjs), now correct
- Replace lint job in ci.yml with coverage job per ci-standard.md four-job
  pattern (test/typecheck/pack-check/coverage); lint+format remain as local
  scripts invocable manually

npm pack --dry-run: zero warnings, 11 files, expected contents verified.
Tests: 119 ESM + 15 CJS = 134 pass, 0 fail.
2026-05-30 15:40:23 -04:00
Aric Camarata
fbb2fa9179 chore: adopt shared config packages (tsconfig, eslint, prettier) 2026-05-30 15:06:05 -04:00
Aric Camarata
5b5466d8ad ci: corepack before setup-node, scope prettier to src/, emit d.mts 2026-05-29 20:05:26 -04:00
Aric Camarata
681126643f chore: untrack AGENTS.md (AI working memory, not source code) 2026-05-29 06:36:41 -04:00
Aric Camarata
cf68c044a6 docs: add quickstart, advanced guide, and examples for pray-calc 2026-05-28 14:14:13 -04:00
Aric Camarata
7fefdb93ef docs(e6): add wiki pages — Sidebar, Footer, Contributing, SECURITY, CODE_OF_CONDUCT 2026-05-28 13:59:59 -04:00
Aric Camarata
cbe283aaf8 chore: bump to v2.1.1
- Flatten exports map to ADR-015 standard
- Add coverage script (c8)
- Migrate CI to corepack enable
2026-05-28 13:54:55 -04:00
Aric Camarata
bdb0615c16 chore(config): add AGENTS.md for dual-harness parity 2026-05-25 15:51:18 -04:00
Aric Camarata
3edc12efbe chore: align repository structure with portfolio documentation standards 2026-05-15 15:27:30 -04:00
Aric Camarata
406ec9fcc3 Add GitHub Sponsors funding config 2026-03-28 18:18:58 -04:00
79 changed files with 2941 additions and 417 deletions

1
.github/FUNDING.yml vendored Normal file
View file

@ -0,0 +1 @@
github: [acamarata]

95
.github/docs/CHANGELOG.md vendored Normal file
View file

@ -0,0 +1,95 @@
# Changelog
All notable changes to this project will be documented in this file.
## [2.1.0] - 2026-03-22
### Added
- `getMidnight()` function: computes the midpoint of the night (Maghrib to Fajr). Useful as the Isha prayer endpoint per the hadith in Sahih Muslim. Also works with sunrise as the second anchor for the astronomical variant.
- `Midnight` field added to `PrayerTimes`, `FormattedPrayerTimes`, `PrayerTimesAll`, and `FormattedPrayerTimesAll` interfaces
- 15 new tests covering `getMidnight` standalone and integrated output
## [2.0.0] - 2026-02-25
### Added
- Full TypeScript rewrite with dual CJS/ESM build (tsup)
- Physics-grounded dynamic twilight angle algorithm: MSC seasonal base + Earth-Sun distance correction + Fourier harmonic smoothing + atmospheric refraction + elevation horizon dip
- Three new traditional methods: IGUT/Tehran (17.7°/14°), Kuwait (18°/17.5°), Qatar (18°/90 min): total now 14
- `getAngles()` exported as a standalone function
- `getMscFajr()` / `getMscIsha()` exported with `shafaq` mode parameter (`general`, `ahmer`, `abyad`)
- `solarEphemeris()` / `toJulianDate()` exported: Jean Meeus solar ephemeris (declination, r, ecliptic lon)
- `METHODS` array exported for documentation and tooling
- All TypeScript types exported (`PrayerTimes`, `FormattedPrayerTimes`, `PrayerTimesAll`, etc.)
- `.wiki/` documentation: Home, API Reference, Dynamic Algorithm, Traditional Methods, Architecture, Twilight Physics, High-Latitude, Asr Calculation, Changelog
- GitHub Actions CI (Node 20/22/24 matrix, typecheck, pack-check) and wiki sync workflow
- 100-scenario ESM test suite + CJS smoke tests
### Changed
- `getAsr` refactored from internal SPA dependency to pure math using Meeus declination
- `getTimesAll` now batches all 14×2 + 2 dynamic angles in a single SPA call
- `nrel-spa` updated from v1.x to v2.0.1 (`formatTime` replaces `fractalTime`)
- Node engine requirement raised from >=12 to >=20
- Package `exports` field added with types-first conditional exports
- `sideEffects: false` for tree-shaking
- `publishConfig.access: public` added
- `repository.url` uses `git+https://` prefix
### Removed
- All moon-related functions (`getMoon`, `getMoonPhase`, `getMoonPosition`, `getMoonIllumination`, `getMoonVisibility`): moved to `moon-sighting` package
- `suncalc` runtime dependency (removed with moon functions)
- `getEarthSunDistance` helper (inlined into `getSolarEphemeris`)
- `methods.json` (methods now embedded in `getTimesAll.ts` with full metadata)
- CommonJS `index.js` source (replaced by TypeScript `src/`)
- `index.d.ts` hand-written types (replaced by generated `dist/index.d.ts`)
- `mocha` and `eslint` dev dependencies (replaced by plain `node:assert` tests)
## [1.0.0] - 2023-11-11
- Initial release
## [1.1.0] - 2023-11-12
- Updated calculation behavior to be more accurate (major)
## [1.2.3] - 2023-11-12
- Moved timezone to main args and changed default behavior (major)
- Updated test cases and readme to reflect new usage (minor)
## [1.3.2] - 2023-11-13
- Major updates to getMoon with own functions
## [1.4.0] - 2023-11-14
- Renamed to "pray-calc" and removed old package
- Improved Synodic accuracy slightly and lastKnownMoon
### [1.4.1] = 2023-12-01
- Modified getMoonVisibility to use adjusted moon phase for end of cycle
## [1.6.0] = 2025-05-04
- Major fixes for core files and calculations
- Updated to use the new "nrel-spa" v1.3.0
### [1.6.1] - 2025-05-04
- Fixed missing modules and types definitions lost in last update
- Locked `suncalc` dependency to `^1.9.0`
- Clarified scripts: `build`, `test`, and `prepublishOnly` in `package.json`
### [1.6.2] - 2025-05-04
- Fixed Package issues
## [1.7.0] = 2025-05-04
- Major update to main algorithm
- Fixes to syntax and bugs
### [1.7.1] = 2025-05-14
- Update to package meta and location
### [1.7.2] = 2025-05-14
- Added tests

34
.github/wiki/CODE_OF_CONDUCT.md vendored Normal file
View file

@ -0,0 +1,34 @@
# Code of Conduct
## Summary
Be direct, be respectful, and focus on the work.
## Standards
Constructive behavior:
- Technical criticism aimed at code and ideas, not people
- Clear and specific feedback with examples where possible
- Acknowledging when you are wrong or do not know something
- Staying on topic in issues and pull requests
Unacceptable behavior:
- Personal attacks, insults, or harassment
- Sustained off-topic disruption
- Publishing private information without consent
## Scope
This applies to all project spaces: GitHub issues, pull requests, discussions, and any other venue where project work happens.
## Enforcement
The project maintainer handles violations. Contact: aric.camarata@gmail.com.
Reports are reviewed promptly. Responses range from a private note to a permanent ban, depending on severity and history.
## Attribution
This code of conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.

61
.github/wiki/Contributing.md vendored Normal file
View file

@ -0,0 +1,61 @@
# Contributing
## Prerequisites
- Node.js 20 or later
- pnpm (enabled via corepack: `corepack enable`)
## Setup
```sh
git clone https://github.com/acamarata/pray-calc.git
cd pray-calc
pnpm install
```
## Development
```sh
pnpm build # compile TypeScript
pnpm test # build + run full test suite (ESM + CJS, 106 tests)
pnpm run typecheck # type-check without emitting
pnpm run lint # ESLint
pnpm run format # Prettier format
```
## Project Structure
```
src/
index.ts main exports
types.ts all TypeScript types
getSolarEphemeris.ts Jean Meeus Ch. 25 (decl, r, eclLon)
getMSC.ts MSC piecewise seasonal model
getAngles.ts dynamic angle algorithm (3 layers)
getAsr.ts pure-math Asr
getQiyam.ts last-third-of-night
getTimes.ts raw fractional-hour output
calcTimes.ts formatted HH:MM:SS output
getTimesAll.ts all-methods batch SPA call
calcTimesAll.ts all-methods formatted output
test.mjs ESM test suite (94 tests)
test-cjs.cjs CJS subset (12 tests)
```
## Making Changes
1. Algorithm changes: read [Dynamic Algorithm](Dynamic-Algorithm) and [Twilight Physics](Twilight-Physics) first.
2. Any new export must have TypeScript types and JSDoc.
3. Tests use `node:test`. Add tests in `test.mjs` for new behavior.
4. All 106 tests must pass before submitting.
## Timezone Note
The test suite uses explicit UTC offset values, not `new Date()` local timezone parsing. Keep this pattern when adding tests — CI runs in UTC and tests must pass there.
## Pull Requests
- One logical change per PR
- Include tests covering the new behavior
- Update `CHANGELOG.md` under `[Unreleased]`
- Do not bump the version number

29
.github/wiki/SECURITY.md vendored Normal file
View file

@ -0,0 +1,29 @@
# Security Policy
## Supported Versions
| Version | Supported |
| --- | --- |
| 2.x | Yes |
| 1.x | No |
Only the latest major version receives security fixes.
## Reporting a Vulnerability
Do not open a public GitHub issue for security vulnerabilities.
Email: aric.camarata@gmail.com
Include:
- A description of the vulnerability
- Steps to reproduce
- Potential impact
- Any suggested fix, if you have one
You will receive an acknowledgment within 48 hours and a resolution timeline within 7 days. Once a fix is ready and deployed, the vulnerability will be disclosed publicly with credit to the reporter (unless you prefer to remain anonymous).
## Scope
This package is a computation library. It performs no network requests, reads no files, and holds no credentials. It depends on `nrel-spa` for solar position calculations. If you find a vulnerability in `nrel-spa`, report it to that package separately.

1
.github/wiki/_Footer.md vendored Normal file
View file

@ -0,0 +1 @@
[npm](https://www.npmjs.com/package/pray-calc) · [GitHub](https://github.com/acamarata/pray-calc) · [Changelog](https://github.com/acamarata/pray-calc/blob/main/CHANGELOG.md) · MIT License

49
.github/wiki/_Sidebar.md vendored Normal file
View file

@ -0,0 +1,49 @@
## pray-calc
**[Home](Home)**
**Reference**
- [API Reference](API-Reference)
- [Architecture](Architecture)
- [Bundle Size / Performance](benchmarks/index)
**Per-Function API**
- [getTimes](api/getTimes)
- [calcTimes](api/calcTimes)
- [getTimesAll / METHODS](api/getTimesAll)
- [calcTimesAll](api/calcTimesAll)
- [getAngles](api/getAngles)
- [getAsr](api/getAsr)
- [getQiyam](api/getQiyam)
- [getMidnight](api/getMidnight)
- [getMscFajr / getMscIsha](api/getMscFajr-getMscIsha)
- [solarEphemeris / toJulianDate](api/solarEphemeris)
- [Constants](api/ANGLE_MIN-ANGLE_MAX)
**Algorithm**
- [Dynamic Algorithm](Dynamic-Algorithm)
- [Twilight Physics](Twilight-Physics)
- [Asr Calculation](Asr-Calculation)
- [High Latitude](High-Latitude)
- [Traditional Methods](Traditional-Methods)
**Research**
- [Research Overview](Research)
- [Methodology](Research-Methodology)
- [Observational Evidence](Research-Observational-Evidence)
- [Global Study](Research-Global-Study)
- [Home Territory](Research-Home-Territory)
- [Verified Observations](Research-Verified-Observations)
**Migration**
- [Moon Migration](Moon-Migration)
**Contributing**
- [Contributing](Contributing)
- [Code of Conduct](CODE_OF_CONDUCT)
- [Security](SECURITY)
**Links**
- [npm](https://www.npmjs.com/package/pray-calc)
- [GitHub](https://github.com/acamarata/pray-calc)
- [Changelog](https://github.com/acamarata/pray-calc/blob/main/CHANGELOG.md)

45
.github/wiki/api/README.md vendored Normal file
View file

@ -0,0 +1,45 @@
**pray-calc v2.1.1**
***
# pray-calc v2.1.1
## Interfaces
- [AtmosphericParams](interfaces/AtmosphericParams.md)
- [FormattedPrayerTimes](interfaces/FormattedPrayerTimes.md)
- [FormattedPrayerTimesAll](interfaces/FormattedPrayerTimesAll.md)
- [MethodDefinition](interfaces/MethodDefinition.md)
- [PrayerTimes](interfaces/PrayerTimes.md)
- [PrayerTimesAll](interfaces/PrayerTimesAll.md)
- [TwilightAngles](interfaces/TwilightAngles.md)
## Type Aliases
- [AsrConvention](type-aliases/AsrConvention.md)
- [FractionalHours](type-aliases/FractionalHours.md)
- [MethodEntry](type-aliases/MethodEntry.md)
- [ShafaqMode](type-aliases/ShafaqMode.md)
- [TimeString](type-aliases/TimeString.md)
## Variables
- [ANGLE\_MAX](variables/ANGLE_MAX.md)
- [ANGLE\_MIN](variables/ANGLE_MIN.md)
- [DHUHR\_OFFSET\_MINUTES](variables/DHUHR_OFFSET_MINUTES.md)
- [METHODS](variables/METHODS.md)
## Functions
- [calcTimes](functions/calcTimes.md)
- [calcTimesAll](functions/calcTimesAll.md)
- [getAngles](functions/getAngles.md)
- [getAsr](functions/getAsr.md)
- [getMidnight](functions/getMidnight.md)
- [getMscFajr](functions/getMscFajr.md)
- [getMscIsha](functions/getMscIsha.md)
- [getQiyam](functions/getQiyam.md)
- [getTimes](functions/getTimes.md)
- [getTimesAll](functions/getTimesAll.md)
- [solarEphemeris](functions/solarEphemeris.md)
- [toJulianDate](functions/toJulianDate.md)

81
.github/wiki/api/functions/calcTimes.md vendored Normal file
View file

@ -0,0 +1,81 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / calcTimes
# Function: calcTimes()
> **calcTimes**(`date`, `lat`, `lng`, `tz?`, `elevation?`, `temperature?`, `pressure?`, `hanafi?`): [`FormattedPrayerTimes`](../interfaces/FormattedPrayerTimes.md)
Defined in: [calcTimes.ts:30](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/calcTimes.ts#L30)
Compute prayer times formatted as HH:MM:SS strings.
Uses the dynamic twilight angle algorithm. See getTimes() for full parameter
documentation.
## Parameters
### date
`Date`
Observer's local date
### lat
`number`
Latitude in decimal degrees (-90 to 90)
### lng
`number`
Longitude in decimal degrees (-180 to 180)
### tz?
`number` = `...`
UTC offset in hours (default: system timezone)
### elevation?
`number` = `0`
Elevation in meters (default: 0)
### temperature?
`number` = `15`
Temperature in Celsius (default: 15)
### pressure?
`number` = `1013.25`
Pressure in mbar/hPa (default: 1013.25)
### hanafi?
`boolean` = `false`
Hanafi Asr convention (default: false)
## Returns
[`FormattedPrayerTimes`](../interfaces/FormattedPrayerTimes.md)
Prayer times as HH:MM:SS strings. Returns "N/A" for any time that
cannot be computed (polar night, unreachable angle, etc.).
## Example
```ts
const times = calcTimes(new Date('2024-06-21'), 40.7128, -74.006, -4);
console.log(times.Fajr); // "03:51:24"
console.log(times.Maghrib); // "20:31:17"
```

View file

@ -0,0 +1,81 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / calcTimesAll
# Function: calcTimesAll()
> **calcTimesAll**(`date`, `lat`, `lng`, `tz?`, `elevation?`, `temperature?`, `pressure?`, `hanafi?`): [`FormattedPrayerTimesAll`](../interfaces/FormattedPrayerTimesAll.md)
Defined in: [calcTimesAll.ts:30](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/calcTimesAll.ts#L30)
Compute prayer times formatted as HH:MM:SS strings, plus comparison times
for every supported traditional method.
Uses the dynamic twilight angle algorithm for the primary times. See
getTimesAll() for full parameter documentation.
## Parameters
### date
`Date`
Observer's local date
### lat
`number`
Latitude in decimal degrees (-90 to 90)
### lng
`number`
Longitude in decimal degrees (-180 to 180)
### tz?
`number` = `...`
UTC offset in hours (default: system timezone)
### elevation?
`number` = `0`
Elevation in meters (default: 0)
### temperature?
`number` = `15`
Temperature in Celsius (default: 15)
### pressure?
`number` = `1013.25`
Pressure in mbar/hPa (default: 1013.25)
### hanafi?
`boolean` = `false`
Hanafi Asr convention (default: false)
## Returns
[`FormattedPrayerTimesAll`](../interfaces/FormattedPrayerTimesAll.md)
All prayer times as HH:MM:SS strings. "N/A" for unreachable events.
## Example
```ts
const result = calcTimesAll(new Date('2024-06-21'), 40.7128, -74.006, -4);
console.log(result.dynamic.Fajr); // "03:51:24"
console.log(result.ISNA.Fajr); // "04:07:30"
```

57
.github/wiki/api/functions/getAngles.md vendored Normal file
View file

@ -0,0 +1,57 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getAngles
# Function: getAngles()
> **getAngles**(`date`, `lat`, `lng`, `elevation?`, `temperature?`, `pressure?`): [`TwilightAngles`](../interfaces/TwilightAngles.md)
Defined in: [getAngles.ts:216](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getAngles.ts#L216)
Compute dynamic twilight depression angles for Fajr and Isha.
## Parameters
### date
`Date`
Observer's local date (time-of-day is ignored)
### lat
`number`
Latitude in decimal degrees (-90 to 90)
### lng
`number`
Longitude in decimal degrees (-180 to 180, currently unused; reserved)
### elevation?
`number` = `0`
Observer elevation in meters (default: 0)
### temperature?
`number` = `15`
Ambient temperature in °C (default: 15)
### pressure?
`number` = `1013.25`
Atmospheric pressure in mbar (default: 1013.25)
## Returns
[`TwilightAngles`](../interfaces/TwilightAngles.md)
Fajr and Isha depression angles in degrees

45
.github/wiki/api/functions/getAsr.md vendored Normal file
View file

@ -0,0 +1,45 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getAsr
# Function: getAsr()
> **getAsr**(`solarNoon`, `latitude`, `declination`, `hanafi?`): `number`
Defined in: [getAsr.ts:21](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getAsr.ts#L21)
Compute Asr time as fractional hours.
## Parameters
### solarNoon
`number`
Solar noon in fractional hours (from getSpa)
### latitude
`number`
Observer latitude in degrees
### declination
`number`
Solar declination in degrees (from solarEphemeris)
### hanafi?
`boolean` = `false`
true for Hanafi (shadow factor 2), false for Shafi'i (factor 1)
## Returns
`number`
Fractional hours, or NaN if the sun never reaches the required altitude

View file

@ -0,0 +1,33 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getMidnight
# Function: getMidnight()
> **getMidnight**(`maghribTime`, `endTime`): `number`
Defined in: [getMidnight.ts:16](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getMidnight.ts#L16)
Compute the midpoint of the night.
## Parameters
### maghribTime
`number`
Maghrib (sunset) time in fractional hours
### endTime
`number`
Fajr or Sunrise time in fractional hours (next day)
## Returns
`number`
Midnight as fractional hours

View file

@ -0,0 +1,43 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getMscFajr
# Function: getMscFajr()
> **getMscFajr**(`date`, `latitude`): `number`
Defined in: [getMSC.ts:103](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getMSC.ts#L103)
Compute Fajr offset in minutes before sunrise using the MCW algorithm.
Returns minutes before sunrise. At latitudes above 55°, the 1/7-night
approximation is recommended (handled at the calling site).
## Parameters
### date
`Date`
Observer's local date
### latitude
`number`
Observer latitude in decimal degrees
## Returns
`number`
Minutes before sunrise for Fajr (Subh Sadiq)
## Example
```ts
const offset = getMscFajr(new Date('2024-06-21'), 40.7128);
// offset ≈ 93 (minutes before sunrise for New York in summer)
```

View file

@ -0,0 +1,51 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getMscIsha
# Function: getMscIsha()
> **getMscIsha**(`date`, `latitude`, `shafaq?`): `number`
Defined in: [getMSC.ts:133](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getMSC.ts#L133)
Compute Isha offset in minutes after sunset using the MCW algorithm.
Three Shafaq modes:
- 'general': blend that reduces hardship at high latitudes (default)
- 'ahmer': based on disappearance of redness (shafaq ahmer)
- 'abyad': based on disappearance of whiteness (shafaq abyad), later
## Parameters
### date
`Date`
Observer's local date
### latitude
`number`
Observer latitude in decimal degrees
### shafaq?
`ShafaqMode` = `'general'`
Twilight type: 'general' | 'ahmer' | 'abyad'
## Returns
`number`
Minutes after sunset for Isha
## Example
```ts
const offset = getMscIsha(new Date('2024-06-21'), 40.7128, 'general');
// offset ≈ 84
```

33
.github/wiki/api/functions/getQiyam.md vendored Normal file
View file

@ -0,0 +1,33 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getQiyam
# Function: getQiyam()
> **getQiyam**(`fajrTime`, `ishaTime`): `number`
Defined in: [getQiyam.ts:16](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getQiyam.ts#L16)
Compute the start of the last third of the night.
## Parameters
### fajrTime
`number`
Fajr time in fractional hours
### ishaTime
`number`
Isha time in fractional hours
## Returns
`number`
Start of the last third of the night (fractional hours)

80
.github/wiki/api/functions/getTimes.md vendored Normal file
View file

@ -0,0 +1,80 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getTimes
# Function: getTimes()
> **getTimes**(`date`, `lat`, `lng`, `tz?`, `elevation?`, `temperature?`, `pressure?`, `hanafi?`): [`PrayerTimes`](../interfaces/PrayerTimes.md)
Defined in: [getTimes.ts:39](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getTimes.ts#L39)
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.
## Parameters
### date
`Date`
Observer's local date (time-of-day is ignored)
### lat
`number`
Latitude in decimal degrees (-90 to 90, south = negative)
### lng
`number`
Longitude in decimal degrees (-180 to 180, west = negative)
### tz?
`number` = `...`
UTC offset in hours (e.g. -5 for EST). Defaults to the
system timezone derived from the Date object.
### elevation?
`number` = `0`
Observer elevation in meters (default: 0)
### temperature?
`number` = `15`
Ambient temperature in °C (default: 15)
### pressure?
`number` = `1013.25`
Atmospheric pressure in mbar/hPa (default: 1013.25)
### hanafi?
`boolean` = `false`
Asr convention: false = Shafi'i/Maliki/Hanbali (default),
true = Hanafi
## Returns
[`PrayerTimes`](../interfaces/PrayerTimes.md)
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
if lat, lng, tz, or elevation are out of valid range

View file

@ -0,0 +1,75 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / getTimesAll
# Function: getTimesAll()
> **getTimesAll**(`date`, `lat`, `lng`, `tz?`, `elevation?`, `temperature?`, `pressure?`, `hanafi?`): [`PrayerTimesAll`](../interfaces/PrayerTimesAll.md)
Defined in: [getTimesAll.ts:152](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getTimesAll.ts#L152)
Compute prayer times plus all traditional method comparisons.
## Parameters
### date
`Date`
Observer's local date (time-of-day is ignored)
### lat
`number`
Latitude in decimal degrees (-90 to 90)
### lng
`number`
Longitude in decimal degrees (-180 to 180)
### tz?
`number` = `...`
UTC offset in hours (defaults to system tz)
### elevation?
`number` = `0`
Observer elevation in meters (default: 0)
### temperature?
`number` = `15`
Ambient temperature in °C (default: 15)
### pressure?
`number` = `1013.25`
Atmospheric pressure in mbar (default: 1013.25)
### hanafi?
`boolean` = `false`
Asr convention: false = Shafi'i (default), true = Hanafi
## Returns
[`PrayerTimesAll`](../interfaces/PrayerTimesAll.md)
Prayer times for the dynamic method plus all traditional methods.
Any time that cannot be computed is returned as `NaN`.
Methods map contains `[fajrTime, ishaTime]` per method.
## Throws
if lat, lng, tz, or elevation are out of valid range

View file

@ -0,0 +1,36 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / solarEphemeris
# Function: solarEphemeris()
> **solarEphemeris**(`jd`): `SolarEphemeris`
Defined in: [getSolarEphemeris.ts:46](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getSolarEphemeris.ts#L46)
Compute solar declination, Earth-Sun distance, and ecliptic longitude
from a Julian Date. Accuracy: ~0.01° for declination, ~0.0001 AU for r.
## Parameters
### jd
`number`
Julian Date (use toJulianDate to convert a JS Date)
## Returns
`SolarEphemeris`
Solar ephemeris data: declination (degrees), Earth-Sun distance (AU),
and ecliptic longitude (radians, 0-2π season phase)
## Example
```ts
const jd = toJulianDate(new Date('2024-06-21'));
const { decl, r, eclLon } = solarEphemeris(jd);
```

View file

@ -0,0 +1,34 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / toJulianDate
# Function: toJulianDate()
> **toJulianDate**(`date`): `number`
Defined in: [getSolarEphemeris.ts:22](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getSolarEphemeris.ts#L22)
Convert a JavaScript Date to a Julian Date number.
## Parameters
### date
`Date`
Any JavaScript Date object (uses UTC internally)
## Returns
`number`
Julian Date: days since noon January 1, 4713 BC UTC
## Example
```ts
const jd = toJulianDate(new Date('2024-06-21'));
// jd ≈ 2460482.5
```

View file

@ -0,0 +1,35 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / AtmosphericParams
# Interface: AtmosphericParams
Defined in: [types.ts:97](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L97)
Optional atmospheric and elevation parameters.
## Properties
### elevation?
> `optional` **elevation?**: `number`
Defined in: [types.ts:98](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L98)
***
### pressure?
> `optional` **pressure?**: `number`
Defined in: [types.ts:100](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L100)
***
### temperature?
> `optional` **temperature?**: `number`
Defined in: [types.ts:99](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L99)

View file

@ -0,0 +1,91 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / FormattedPrayerTimes
# Interface: FormattedPrayerTimes
Defined in: [types.ts:50](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L50)
Prayer times formatted as HH:MM:SS strings.
## Properties
### angles
> **angles**: [`TwilightAngles`](TwilightAngles.md)
Defined in: [types.ts:60](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L60)
***
### Asr
> **Asr**: `string`
Defined in: [types.ts:56](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L56)
***
### Dhuhr
> **Dhuhr**: `string`
Defined in: [types.ts:55](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L55)
***
### Fajr
> **Fajr**: `string`
Defined in: [types.ts:52](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L52)
***
### Isha
> **Isha**: `string`
Defined in: [types.ts:58](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L58)
***
### Maghrib
> **Maghrib**: `string`
Defined in: [types.ts:57](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L57)
***
### Midnight
> **Midnight**: `string`
Defined in: [types.ts:59](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L59)
***
### Noon
> **Noon**: `string`
Defined in: [types.ts:54](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L54)
***
### Qiyam
> **Qiyam**: `string`
Defined in: [types.ts:51](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L51)
***
### Sunrise
> **Sunrise**: `string`
Defined in: [types.ts:53](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L53)

View file

@ -0,0 +1,101 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / FormattedPrayerTimesAll
# Interface: FormattedPrayerTimesAll
Defined in: [types.ts:81](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L81)
Prayer times plus all method comparison times, fully formatted.
## Properties
### angles
> **angles**: [`TwilightAngles`](TwilightAngles.md)
Defined in: [types.ts:91](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L91)
***
### Asr
> **Asr**: `string`
Defined in: [types.ts:87](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L87)
***
### Dhuhr
> **Dhuhr**: `string`
Defined in: [types.ts:86](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L86)
***
### Fajr
> **Fajr**: `string`
Defined in: [types.ts:83](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L83)
***
### Isha
> **Isha**: `string`
Defined in: [types.ts:89](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L89)
***
### Maghrib
> **Maghrib**: `string`
Defined in: [types.ts:88](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L88)
***
### Methods
> **Methods**: `Record`\<`string`, \[[`TimeString`](../type-aliases/TimeString.md), [`TimeString`](../type-aliases/TimeString.md)\]\>
Defined in: [types.ts:93](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L93)
Formatted comparison times for each method: [fajrString, ishaString].
***
### Midnight
> **Midnight**: `string`
Defined in: [types.ts:90](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L90)
***
### Noon
> **Noon**: `string`
Defined in: [types.ts:85](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L85)
***
### Qiyam
> **Qiyam**: `string`
Defined in: [types.ts:82](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L82)
***
### Sunrise
> **Sunrise**: `string`
Defined in: [types.ts:84](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L84)

View file

@ -0,0 +1,85 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / MethodDefinition
# Interface: MethodDefinition
Defined in: [types.ts:104](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L104)
Internal record for a single traditional method definition.
## Properties
### fajrAngle
> **fajrAngle**: `number` \| `null`
Defined in: [types.ts:115](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L115)
Fajr depression angle in degrees. Null means the method uses a
seasonal calculation (MSC) rather than a fixed angle.
***
### id
> **id**: `string`
Defined in: [types.ts:106](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L106)
Short identifier used as the Methods map key.
***
### ishaAngle
> **ishaAngle**: `number` \| `null`
Defined in: [types.ts:120](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L120)
Isha depression angle in degrees. Null means the method uses a
fixed-minute offset or seasonal calculation instead.
***
### ishaMinutes?
> `optional` **ishaMinutes?**: `number`
Defined in: [types.ts:125](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L125)
Fixed minutes after sunset for Isha. Overrides ishaAngle when set.
UAQ uses 90 year-round; Qatar uses 90 as well.
***
### name
> **name**: `string`
Defined in: [types.ts:108](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L108)
Human-readable name.
***
### region
> **region**: `string`
Defined in: [types.ts:110](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L110)
Geographic region of primary use.
***
### useMSC?
> `optional` **useMSC?**: `boolean`
Defined in: [types.ts:130](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L130)
When true, the method uses the MSC seasonal algorithm for both
Fajr and Isha.

View file

@ -0,0 +1,115 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / PrayerTimes
# Interface: PrayerTimes
Defined in: [types.ts:26](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L26)
Raw prayer times as fractional hours.
## Extended by
- [`PrayerTimesAll`](PrayerTimesAll.md)
## Properties
### angles
> **angles**: [`TwilightAngles`](TwilightAngles.md)
Defined in: [types.ts:46](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L46)
Dynamic twilight angles used for this calculation.
***
### Asr
> **Asr**: `number`
Defined in: [types.ts:38](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L38)
Asr (Shafi'i or Hanafi shadow convention).
***
### Dhuhr
> **Dhuhr**: `number`
Defined in: [types.ts:36](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L36)
Dhuhr (2.5 minutes after solar noon).
***
### Fajr
> **Fajr**: `number`
Defined in: [types.ts:30](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L30)
True dawn (Subh Sadiq).
***
### Isha
> **Isha**: `number`
Defined in: [types.ts:42](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L42)
Isha (nightfall, end of shafaq).
***
### Maghrib
> **Maghrib**: `number`
Defined in: [types.ts:40](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L40)
Maghrib (sunset).
***
### Midnight
> **Midnight**: `number`
Defined in: [types.ts:44](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L44)
Midnight: midpoint between Maghrib and Fajr.
***
### Noon
> **Noon**: `number`
Defined in: [types.ts:34](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L34)
Solar noon (exact geometric transit).
***
### Qiyam
> **Qiyam**: `number`
Defined in: [types.ts:28](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L28)
Start of the last third of the night (Qiyam al-Layl).
***
### Sunrise
> **Sunrise**: `number`
Defined in: [types.ts:32](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L32)
Astronomical sunrise.

View file

@ -0,0 +1,165 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / PrayerTimesAll
# Interface: PrayerTimesAll
Defined in: [types.ts:75](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L75)
Prayer times plus all method comparison times as fractional hours.
## Extends
- [`PrayerTimes`](PrayerTimes.md)
## Properties
### angles
> **angles**: [`TwilightAngles`](TwilightAngles.md)
Defined in: [types.ts:46](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L46)
Dynamic twilight angles used for this calculation.
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`angles`](PrayerTimes.md#angles)
***
### Asr
> **Asr**: `number`
Defined in: [types.ts:38](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L38)
Asr (Shafi'i or Hanafi shadow convention).
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Asr`](PrayerTimes.md#asr)
***
### Dhuhr
> **Dhuhr**: `number`
Defined in: [types.ts:36](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L36)
Dhuhr (2.5 minutes after solar noon).
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Dhuhr`](PrayerTimes.md#dhuhr)
***
### Fajr
> **Fajr**: `number`
Defined in: [types.ts:30](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L30)
True dawn (Subh Sadiq).
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Fajr`](PrayerTimes.md#fajr)
***
### Isha
> **Isha**: `number`
Defined in: [types.ts:42](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L42)
Isha (nightfall, end of shafaq).
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Isha`](PrayerTimes.md#isha)
***
### Maghrib
> **Maghrib**: `number`
Defined in: [types.ts:40](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L40)
Maghrib (sunset).
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Maghrib`](PrayerTimes.md#maghrib)
***
### Methods
> **Methods**: `Record`\<`string`, [`MethodEntry`](../type-aliases/MethodEntry.md)\>
Defined in: [types.ts:77](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L77)
Comparison results from all supported fixed-angle and seasonal methods.
***
### Midnight
> **Midnight**: `number`
Defined in: [types.ts:44](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L44)
Midnight: midpoint between Maghrib and Fajr.
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Midnight`](PrayerTimes.md#midnight)
***
### Noon
> **Noon**: `number`
Defined in: [types.ts:34](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L34)
Solar noon (exact geometric transit).
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Noon`](PrayerTimes.md#noon)
***
### Qiyam
> **Qiyam**: `number`
Defined in: [types.ts:28](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L28)
Start of the last third of the night (Qiyam al-Layl).
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Qiyam`](PrayerTimes.md#qiyam)
***
### Sunrise
> **Sunrise**: `number`
Defined in: [types.ts:32](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L32)
Astronomical sunrise.
#### Inherited from
[`PrayerTimes`](PrayerTimes.md).[`Sunrise`](PrayerTimes.md#sunrise)

View file

@ -0,0 +1,31 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / TwilightAngles
# Interface: TwilightAngles
Defined in: [types.ts:18](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L18)
Computed twilight depression angles for Fajr and Isha.
## Properties
### fajrAngle
> **fajrAngle**: `number`
Defined in: [types.ts:20](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L20)
Solar depression angle for Fajr (positive degrees below horizon).
***
### ishaAngle
> **ishaAngle**: `number`
Defined in: [types.ts:22](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L22)
Solar depression angle for Isha (positive degrees below horizon).

View file

@ -0,0 +1,13 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / AsrConvention
# Type Alias: AsrConvention
> **AsrConvention** = `"shafii"` \| `"hanafi"`
Defined in: [types.ts:12](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L12)
Asr shadow convention: Shafi'i (shadow = 1x object length) or Hanafi (2x).

View file

@ -0,0 +1,13 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / FractionalHours
# Type Alias: FractionalHours
> **FractionalHours** = `number`
Defined in: [types.ts:6](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L6)
Fractional hours (e.g. 5.5 = 05:30:00). NaN indicates an unreachable event.

View file

@ -0,0 +1,19 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / MethodEntry
# Type Alias: MethodEntry
> **MethodEntry** = \[[`FractionalHours`](FractionalHours.md), [`FractionalHours`](FractionalHours.md)\]
Defined in: [types.ts:72](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L72)
Method entry in the Methods map: `[fajrTime, ishaTime]` as fractional hours.
- Index 0 (`fajr`): Fajr time for this method (fractional hours, or `NaN`)
- Index 1 (`isha`): Isha time for this method (fractional hours, or `NaN`)
A value of `NaN` indicates the event is unreachable at this location/date
(e.g. the sun never dips to 18° below the horizon at high latitudes in summer).

View file

@ -0,0 +1,13 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / ShafaqMode
# Type Alias: ShafaqMode
> **ShafaqMode** = `"general"` \| `"ahmer"` \| `"abyad"`
Defined in: [types.ts:15](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L15)
Shafaq (twilight glow) variant for the MSC Isha model.

View file

@ -0,0 +1,13 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / TimeString
# Type Alias: TimeString
> **TimeString** = `string`
Defined in: [types.ts:9](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/types.ts#L9)
HH:MM:SS string produced by formatTime, or "N/A" when unreachable.

17
.github/wiki/api/variables/ANGLE_MAX.md vendored Normal file
View file

@ -0,0 +1,17 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / ANGLE\_MAX
# Variable: ANGLE\_MAX
> `const` **ANGLE\_MAX**: `22` = `22`
Defined in: [constants.ts:35](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/constants.ts#L35)
Maximum allowed dynamic twilight depression angle (degrees).
22° is the upper clamp. Values above ~20° correspond to deep
astronomical twilight where the sky is indistinguishable from full
night. No standard method exceeds 20° for Fajr.

17
.github/wiki/api/variables/ANGLE_MIN.md vendored Normal file
View file

@ -0,0 +1,17 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / ANGLE\_MIN
# Variable: ANGLE\_MIN
> `const` **ANGLE\_MIN**: `10` = `10`
Defined in: [constants.ts:26](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/constants.ts#L26)
Minimum allowed dynamic twilight depression angle (degrees).
At very high latitudes in summer the MCW base angle can drop below
physically meaningful values. 10° is the lower clamp — below this
the sky is too bright for any twilight definition.

View file

@ -0,0 +1,19 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / DHUHR\_OFFSET\_MINUTES
# Variable: DHUHR\_OFFSET\_MINUTES
> `const` **DHUHR\_OFFSET\_MINUTES**: `2.5` = `2.5`
Defined in: [constants.ts:17](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/constants.ts#L17)
Minutes added to solar noon to obtain Dhuhr time.
Standard practice adds a small buffer after geometric solar transit to
ensure the sun has clearly passed the meridian before Dhuhr begins.
The 2.5-minute convention is widely used across Islamic timekeeping
authorities and accounts for the sun's angular diameter (~0.5°) plus
a small safety margin.

13
.github/wiki/api/variables/METHODS.md vendored Normal file
View file

@ -0,0 +1,13 @@
[**pray-calc v2.1.1**](../README.md)
***
[pray-calc](../README.md) / METHODS
# Variable: METHODS
> `const` **METHODS**: [`MethodDefinition`](../interfaces/MethodDefinition.md)[]
Defined in: [getTimesAll.ts:38](https://github.com/acamarata/pray-calc/blob/4227afc2c3993234794b3a1a561c080edc6d4d4e/src/getTimesAll.ts#L38)
All supported traditional methods.

52
.github/wiki/benchmarks/index.md vendored Normal file
View file

@ -0,0 +1,52 @@
# Bundle Size and Performance
## Bundle size
Measured with tsup production build (Node 22, pnpm 9).
| Package | Format | Raw | Min+gz |
|---|---|---|---|
| pray-calc | ESM (.mjs) | 84 KB | ~22 KB |
| pray-calc | CJS (.cjs) | 86 KB | ~22 KB |
| nrel-spa (dependency) | ESM | 51 KB | ~13 KB |
| pray-calc wrapper only (excl. nrel-spa) | ESM | ~33 KB | ~9 KB |
The bulk of pray-calc's own weight comes from the MCW seasonal coefficient tables and the ephemeris formulas. The single runtime dependency is [nrel-spa](https://github.com/acamarata/nrel-spa).
## Comparison with nrel-spa baseline
| Library | Bundle (min+gz) | Prayer times | Traditional methods | Dynamic angles |
|---|---|---|---|---|
| nrel-spa alone | ~13 KB | No (solar position only) | No | No |
| pray-calc | ~22 KB | Yes (9 times + angles) | 14 methods | Yes |
| Overhead for prayer times | +~9 KB | 9 prayer times | 14 methods | Physics-based angles |
The ~9 KB prayer-time layer delivers: dynamic twilight angles, 14 traditional fixed-angle method comparisons, Asr in both Shafi'i and Hanafi conventions, Qiyam al-Layl, Islamic midnight, and MCW direct access functions.
## Performance
Measured on Apple M2 Pro (single core), Node 22.
| Operation | Time (single call) | Notes |
|---|---|---|
| `calcTimes` | ~0.9 ms | 1 SPA call, dynamic angles |
| `calcTimesAll` | ~1.1 ms | 1 SPA call, 30 zenith angles |
| `getAngles` alone | ~0.05 ms | No SPA, Meeus ephemeris only |
| `getMscFajr` / `getMscIsha` | <0.01 ms | Arithmetic only |
`calcTimesAll` computes all 14 method comparisons in a single SPA call by passing all 30 zenith angles at once. It is not 14 times slower than `calcTimes`.
## Tree-shaking
All exports are individually tree-shakeable. If you only import `calcTimes`, a bundler excludes the `getTimesAll` / `calcTimesAll` batch machinery and the 14-method METHODS table.
```javascript
// Only calcTimes and its dependencies are included
import { calcTimes } from 'pray-calc';
```
## Accuracy versus performance tradeoff
The `solarEphemeris` function (Meeus Ch. 25) runs in under 0.1 ms and is used only for the angle correction layer. The full SPA computation (via nrel-spa) handles the precise prayer time solving. This split keeps the dynamic angle overhead small while maintaining the high accuracy of NREL SPA for the actual time results.
For batch computation across many dates or locations, `getAngles` can be cached at the date grain: solar position changes slowly, and the angles for a given latitude and date are stable within ±0.1° across a full year range.

36
.github/wiki/examples/daily-schedule.md vendored Normal file
View file

@ -0,0 +1,36 @@
# Example: Daily Prayer Schedule
Print a formatted prayer schedule for any date and location.
```js
import { getPrayerTimes, Method } from 'pray-calc';
const date = new Date('2025-03-20'); // spring equinox
const LAT = 21.3891; // Mecca
const LON = 39.8579;
const TZ = 3; // AST (Arabia Standard Time)
const times = getPrayerTimes(date, LAT, LON, TZ, { method: Method.UmmAlQura });
console.log(`Prayer times for ${date.toDateString()} — Mecca`);
console.log('');
console.log(` Fajr: ${times.fajr}`);
console.log(` Sunrise: ${times.sunrise}`);
console.log(` Dhuhr: ${times.dhuhr}`);
console.log(` Asr: ${times.asr}`);
console.log(` Maghrib: ${times.maghrib}`);
console.log(` Isha: ${times.isha}`);
```
Sample output:
```
Prayer times for Thu Mar 20 2025 — Mecca
Fajr: 04:45:00
Sunrise: 06:07:00
Dhuhr: 12:14:00
Asr: 15:37:00
Maghrib: 18:21:00
Isha: 19:51:00
```

View file

@ -0,0 +1,49 @@
# Example: Multi-City Prayer Time Comparison
Compare Fajr and Isha times across multiple cities for the same date.
```js
import { getPrayerTimes, Method } from 'pray-calc';
const date = new Date('2025-06-21'); // summer solstice
const cities = [
{ name: 'Mecca', lat: 21.3891, lon: 39.8579, tz: 3, method: Method.UmmAlQura },
{ name: 'London', lat: 51.5074, lon: -0.1278, tz: 1, method: Method.MWL },
{ name: 'New York', lat: 40.7128, lon: -74.0060, tz: -4, method: Method.ISNA },
{ name: 'Kuala Lumpur', lat: 3.1390, lon: 101.6869, tz: 8, method: Method.JAKIM },
{ name: 'Jakarta', lat: -6.2088, lon: 106.8456, tz: 7, method: Method.Kemenag },
];
console.log(`Prayer times for ${date.toDateString()}\n`);
console.log('City'.padEnd(16) + 'Method'.padEnd(12) + 'Fajr Isha');
console.log('-'.repeat(52));
for (const city of cities) {
const times = getPrayerTimes(date, city.lat, city.lon, city.tz, {
method: city.method,
});
const method = city.method.toString().slice(0, 10);
console.log(
city.name.padEnd(16) +
method.padEnd(12) +
`${times.fajr} ${times.isha}`,
);
}
```
Sample output:
```
Prayer times for Sat Jun 21 2025
City Method Fajr Isha
----------------------------------------------------
Mecca UmmAlQura 04:19 21:20
London MWL 01:44 23:17
New York ISNA 03:43 21:37
Kuala Lumpur JAKIM 05:50 19:49
Jakarta Kemenag 04:37 18:06
```
> **Note:** London in summer has very short nights. Fajr and Isha times shown here use the MWL method without a high-latitude adjustment. See [High Latitude](../High-Latitude) for adjustment options.

94
.github/wiki/guides/advanced.md vendored Normal file
View file

@ -0,0 +1,94 @@
# Advanced Usage
## High-latitude handling
At latitudes above ~48°N or below ~48°S, Fajr and Isha may not have valid solar depression angles during summer. Several adjustment methods are available:
```js
import { getPrayerTimes, HighLatRule } from 'pray-calc';
// Angle-based: uses the ratio of Fajr/Isha night fraction from a reference latitude
const times = getPrayerTimes(date, lat, lon, tz, {
highLat: HighLatRule.AngleBased,
});
// Middle of night: splits the night equally
const times2 = getPrayerTimes(date, lat, lon, tz, {
highLat: HighLatRule.MiddleOfNight,
});
// One seventh: Fajr/Isha at 1/7 of the night duration
const times3 = getPrayerTimes(date, lat, lon, tz, {
highLat: HighLatRule.OneSeventh,
});
```
See [High Latitude](../High-Latitude) for background on why these rules exist and how to choose.
## Custom angles
Override the Fajr and Isha angles directly:
```js
const times = getPrayerTimes(date, lat, lon, tz, {
fajrAngle: 15, // degrees below horizon
ishaAngle: 15,
});
```
## Time adjustments
Apply minute-based adjustments to individual prayers:
```js
const times = getPrayerTimes(date, lat, lon, tz, {
adjustments: {
fajr: 2, // +2 minutes
maghrib: -1, // -1 minute
},
});
```
## Midnight calculation
```js
import { getPrayerTimes, MidnightMethod } from 'pray-calc';
// Standard: midpoint between Maghrib and Fajr
const times = getPrayerTimes(date, lat, lon, tz, {
midnight: MidnightMethod.Standard,
});
// Jafari: midpoint between Maghrib and sunrise
const times2 = getPrayerTimes(date, lat, lon, tz, {
midnight: MidnightMethod.Jafari,
});
```
## Dynamic algorithm
The DPC (Dynamic Prayer Calculation) algorithm uses verified observational data to determine Fajr and Isha angles based on the observer's latitude and season, rather than fixed angles from a single authority.
```js
import { getPrayerTimes, Method } from 'pray-calc';
const times = getPrayerTimes(date, lat, lon, tz, {
method: Method.Dynamic, // research-based angles
});
```
See [Dynamic Algorithm](../Dynamic-Algorithm) and [Research](../Research) for how the data was collected and validated.
## Elevation
At high elevations, atmospheric refraction differs slightly. Pass elevation in metres:
```js
const times = getPrayerTimes(date, lat, lon, tz, { elevation: 2000 });
```
## Related pages
- [High Latitude](../High-Latitude) — deep dive on the problem and solutions
- [Asr Calculation](../Asr-Calculation) — shadow length derivation
- [Twilight Physics](../Twilight-Physics) — what defines Fajr and Isha astronomically

77
.github/wiki/guides/quickstart.md vendored Normal file
View file

@ -0,0 +1,77 @@
# Quick Start
Five minutes from install to prayer times.
## Install
```sh
npm install pray-calc
```
## Basic usage
```js
import { getPrayerTimes } from 'pray-calc';
const times = getPrayerTimes(
new Date('2025-06-21'),
40.7128, // latitude
-74.0060, // longitude
-4, // UTC offset in hours (EDT)
);
console.log(times.fajr); // "04:01:12"
console.log(times.sunrise); // "05:25:44"
console.log(times.dhuhr); // "12:59:58"
console.log(times.asr); // "16:47:23"
console.log(times.maghrib); // "20:34:47"
console.log(times.isha); // "22:08:33"
```
Times are returned as `HH:MM:SS` strings in the local timezone defined by the UTC offset.
## Choosing a calculation method
```js
import { getPrayerTimes, Method } from 'pray-calc';
// ISNA — commonly used in North America
const times = getPrayerTimes(date, lat, lon, tz, { method: Method.ISNA });
// MWL — Muslim World League
const times2 = getPrayerTimes(date, lat, lon, tz, { method: Method.MWL });
// Egyptian — Egyptian General Authority of Survey
const times3 = getPrayerTimes(date, lat, lon, tz, { method: Method.Egyptian });
```
See [Traditional Methods](../Traditional-Methods) for a full comparison of all built-in methods.
## Asr calculation schools
```js
import { getPrayerTimes, AsrSchool } from 'pray-calc';
// Standard (Shafi, Maliki, Hanbali) — shadow length = 1× object height
const standard = getPrayerTimes(date, lat, lon, tz, { asr: AsrSchool.Standard });
// Hanafi — shadow length = 2× object height
const hanafi = getPrayerTimes(date, lat, lon, tz, { asr: AsrSchool.Hanafi });
```
## Moon data
```js
import { getMoonData } from 'pray-calc';
const moon = getMoonData(new Date('2025-06-21'), 40.7128, -74.0060);
console.log(moon.phase); // 0-1 (0=new, 0.5=full)
console.log(moon.illumination); // percentage illuminated
```
## Next steps
- [API Reference](../API-Reference) — all functions and options
- [Traditional Methods](../Traditional-Methods) — angle-based calculation methods explained
- [Dynamic Algorithm](../Dynamic-Algorithm) — how pray-calc's research-based DPC works
- [Advanced Guide](advanced) — high-latitude handling, edge cases

View file

@ -15,9 +15,8 @@ jobs:
node-version: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Enable corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
@ -27,30 +26,35 @@ jobs:
- run: node --test test.mjs
- run: node --test test-cjs.cjs
lint:
name: Lint & Format
coverage:
name: Coverage
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Enable corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 24
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run lint
- run: pnpm run format:check
- run: pnpm build
- name: Coverage
run: pnpm run coverage
- name: Upload to Codecov
uses: codecov/codecov-action@v4
with:
files: ./coverage/lcov.info
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: false
typecheck:
name: Typecheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Enable corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 24
@ -63,9 +67,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Enable corepack
run: corepack enable
- uses: actions/setup-node@v4
with:
node-version: 24

View file

@ -4,18 +4,18 @@ on:
push:
branches: [main]
paths:
- '.wiki/**'
- '.github/wiki/**'
jobs:
sync:
name: Sync .wiki/ to GitHub Wiki
name: Sync .github/wiki/ to GitHub Wiki
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Sync wiki pages
uses: Andrew-Chen-Wang/github-wiki-action@v4
with:
path: .wiki/
path: .github/wiki/
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_ACTOR: ${{ github.actor }}

View file

@ -1,10 +0,0 @@
# Ignore Git history
.git
# Ignore local dev/test files
/tests/
/bin/
**/*.c
**/*.h
getMoonVisibility.js
getMSC.js
test*.js

View file

@ -1,6 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all",
"printWidth": 100,
"tabWidth": 2
}

View file

@ -2,94 +2,26 @@
All notable changes to this project will be documented in this file.
## [2.1.0] - 2026-03-22
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### Added
## [Unreleased]
- `getMidnight()` function: computes the midpoint of the night (Maghrib to Fajr). Useful as the Isha prayer endpoint per the hadith in Sahih Muslim. Also works with sunrise as the second anchor for the astronomical variant.
- `Midnight` field added to `PrayerTimes`, `FormattedPrayerTimes`, `PrayerTimesAll`, and `FormattedPrayerTimesAll` interfaces
- 15 new tests covering `getMidnight` standalone and integrated output
## [2.1.2] - 2026-05-30
## [2.0.0] - 2026-02-25
### Fixed
- Add explicit `files` field to package.json; remove `.npmignore` to ensure correct published file set
- Include `dist/index.d.mts` in published package (was missing from 2.1.1)
### Added
- Full TypeScript rewrite with dual CJS/ESM build (tsup)
- Physics-grounded dynamic twilight angle algorithm: MSC seasonal base + Earth-Sun distance correction + Fourier harmonic smoothing + atmospheric refraction + elevation horizon dip
- Three new traditional methods: IGUT/Tehran (17.7°/14°), Kuwait (18°/17.5°), Qatar (18°/90 min): total now 14
- `getAngles()` exported as a standalone function
- `getMscFajr()` / `getMscIsha()` exported with `shafaq` mode parameter (`general`, `ahmer`, `abyad`)
- `solarEphemeris()` / `toJulianDate()` exported: Jean Meeus solar ephemeris (declination, r, ecliptic lon)
- `METHODS` array exported for documentation and tooling
- All TypeScript types exported (`PrayerTimes`, `FormattedPrayerTimes`, `PrayerTimesAll`, etc.)
- `.wiki/` documentation: Home, API Reference, Dynamic Algorithm, Traditional Methods, Architecture, Twilight Physics, High-Latitude, Asr Calculation, Changelog
- GitHub Actions CI (Node 20/22/24 matrix, typecheck, pack-check) and wiki sync workflow
- 100-scenario ESM test suite + CJS smoke tests
## [2.1.1] - 2026-05-28
### Changed
- Flatten exports map to ADR-015 standard (import/require/types at top level)
- Add "./package.json" export condition
- Add coverage script (c8 --reporter=lcov)
- Migrate CI from pnpm/action-setup to corepack enable
- `getAsr` refactored from internal SPA dependency to pure math using Meeus declination
- `getTimesAll` now batches all 14×2 + 2 dynamic angles in a single SPA call
- `nrel-spa` updated from v1.x to v2.0.1 (`formatTime` replaces `fractalTime`)
- Node engine requirement raised from >=12 to >=20
- Package `exports` field added with types-first conditional exports
- `sideEffects: false` for tree-shaking
- `publishConfig.access: public` added
- `repository.url` uses `git+https://` prefix
### Removed
- All moon-related functions (`getMoon`, `getMoonPhase`, `getMoonPosition`, `getMoonIllumination`, `getMoonVisibility`): moved to `moon-sighting` package
- `suncalc` runtime dependency (removed with moon functions)
- `getEarthSunDistance` helper (inlined into `getSolarEphemeris`)
- `methods.json` (methods now embedded in `getTimesAll.ts` with full metadata)
- CommonJS `index.js` source (replaced by TypeScript `src/`)
- `index.d.ts` hand-written types (replaced by generated `dist/index.d.ts`)
- `mocha` and `eslint` dev dependencies (replaced by plain `node:assert` tests)
## [1.0.0] - 2023-11-11
## [2.1.0] - 2026-05-28
### Added
- Initial release
## [1.1.0] - 2023-11-12
- Updated calculation behavior to be more accurate (major)
## [1.2.3] - 2023-11-12
- Moved timezone to main args and changed default behavior (major)
- Updated test cases and readme to reflect new usage (minor)
## [1.3.2] - 2023-11-13
- Major updates to getMoon with own functions
## [1.4.0] - 2023-11-14
- Renamed to "pray-calc" and removed old package
- Improved Synodic accuracy slightly and lastKnownMoon
### [1.4.1] = 2023-12-01
- Modified getMoonVisibility to use adjusted moon phase for end of cycle
## [1.6.0] = 2025-05-04
- Major fixes for core files and calculations
- Updated to use the new "nrel-spa" v1.3.0
### [1.6.1] - 2025-05-04
- Fixed missing modules and types definitions lost in last update
- Locked `suncalc` dependency to `^1.9.0`
- Clarified scripts: `build`, `test`, and `prepublishOnly` in `package.json`
### [1.6.2] - 2025-05-04
- Fixed Package issues
## [1.7.0] = 2025-05-04
- Major update to main algorithm
- Fixes to syntax and bugs
### [1.7.1] = 2025-05-14
- Update to package meta and location
### [1.7.2] = 2025-05-14
- Added tests

176
README.md
View file

@ -3,13 +3,14 @@
[![npm version](https://img.shields.io/npm/v/pray-calc)](https://www.npmjs.com/package/pray-calc)
[![CI](https://github.com/acamarata/pray-calc/actions/workflows/ci.yml/badge.svg)](https://github.com/acamarata/pray-calc/actions/workflows/ci.yml)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
[![Wiki](https://img.shields.io/badge/docs-wiki-blue)](https://github.com/acamarata/pray-calc/wiki)
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.
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 comparison. Single runtime dependency: [nrel-spa](https://github.com/acamarata/nrel-spa).
## Installation
```bash
pnpm add pray-calc # or npm install pray-calc
npm install pray-calc
```
## Quick Start
@ -19,9 +20,9 @@ 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)
40.7128, // New York latitude
-74.0060, // longitude
-4, // UTC offset (hours)
);
console.log(times.Fajr); // "03:51:24"
@ -30,150 +31,15 @@ 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
CommonJS:
```javascript
```js
const { calcTimes } = require('pray-calc');
```
### Compare all methods
```typescript
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, Midnight, 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.
### `getMidnight(maghribTime, endTime)`
Returns the midpoint of the night as fractional hours. Pass Fajr as `endTime` for the
standard definition (Maghrib-to-Fajr midpoint), or Sunrise for the astronomical variant.
### `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 1416° below the horizon, not 18°.
The dynamic method computes the angle in three layers:
1. **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.
2. **Physics corrections**: Earth-Sun distance (r via Jean Meeus elliptical orbit),
Fourier harmonic smoothing, atmospheric refraction at the computed altitude, and
elevation horizon dip.
3. **Physical bounds**: clipped to [10°, 22°].
At the equator the result converges to approximately 18°, consistent with historical
usage. At 5055°N in summer it falls to 1214°, matching empirical UK observations.
Full detail: [Dynamic Algorithm wiki page](https://github.com/acamarata/pray-calc/wiki/Dynamic-Algorithm)
## Architecture
- Only runtime dependency: `nrel-spa` (NREL Solar Position Algorithm)
- `getSolarEphemeris`: Jean Meeus Ch. 25: declination, Earth-Sun distance, ecliptic lon
- `getTimesAll`: single batch SPA call for all 14×2 + 2 dynamic zenith angles
Full detail: [Architecture wiki page](https://github.com/acamarata/pray-calc/wiki/Architecture)
## Compatibility
- Node.js >= 20
- ESM and CJS builds included
- TypeScript types bundled
- No browser-incompatible APIs
Use `calcTimesAll` to get all 14 traditional method times alongside the dynamic result.
## TypeScript
@ -182,39 +48,29 @@ import type {
PrayerTimes,
FormattedPrayerTimes,
PrayerTimesAll,
FormattedPrayerTimesAll,
TwilightAngles,
MethodDefinition,
} from 'pray-calc';
```
## Documentation
Full documentation: [GitHub Wiki](https://github.com/acamarata/pray-calc/wiki)
- [API Reference](https://github.com/acamarata/pray-calc/wiki/API-Reference)
- [Dynamic Algorithm](https://github.com/acamarata/pray-calc/wiki/Dynamic-Algorithm)
- [Traditional Methods](https://github.com/acamarata/pray-calc/wiki/Traditional-Methods)
- [Architecture](https://github.com/acamarata/pray-calc/wiki/Architecture)
- [Twilight Physics](https://github.com/acamarata/pray-calc/wiki/Twilight-Physics)
- [High-Latitude Handling](https://github.com/acamarata/pray-calc/wiki/High-Latitude)
Full API reference, dynamic algorithm details, traditional method table, and high-latitude handling: [GitHub Wiki](https://github.com/acamarata/pray-calc/wiki)
## Related
- [nrel-spa](https://github.com/acamarata/nrel-spa): NREL Solar Position Algorithm
- [nrel-spa](https://github.com/acamarata/nrel-spa): NREL Solar Position Algorithm (the solar foundation)
- [luxon-hijri](https://github.com/acamarata/luxon-hijri): Hijri/Gregorian calendar
- [moon-sighting](https://github.com/acamarata/moon-sighting): Crescent visibility
- [moon-sighting](https://github.com/acamarata/moon-sighting): Crescent visibility calculations
## Acknowledgments
The solar position calculations use [nrel-spa](https://github.com/acamarata/nrel-spa), a pure JavaScript port of the Solar Position Algorithm (SPA) developed by Ibrahim Reda and Afshin Andreas at the National Renewable Energy Laboratory (NREL):
Solar position calculations use [nrel-spa](https://github.com/acamarata/nrel-spa), a port of the NREL SPA by Ibrahim Reda and Afshin Andreas. The seasonal twilight model builds on the work of Khalid Shaukat (Moonsighting Committee Worldwide).
> 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>
## Telemetry
The seasonal twilight model builds on the work of Khalid Shaukat (Moonsighting Committee Worldwide).
This package supports opt-in anonymous usage telemetry — off by default.
Enable: `ACAMARATA_TELEMETRY=1`. See [TELEMETRY.md](./TELEMETRY.md) for what is sent and how to disable.
## License
MIT. Copyright (c) 2023-2026 Aric Camarata.
See [LICENSE](LICENSE) for full terms.

8
TELEMETRY.md Normal file
View file

@ -0,0 +1,8 @@
# Telemetry Disclosure
This package supports opt-in anonymous usage telemetry via [`@acamarata/telemetry`](https://github.com/acamarata/telemetry).
Telemetry is **off by default**. No data is sent unless you set `ACAMARATA_TELEMETRY=1`.
Full disclosure (what is sent, where it goes, how to disable):
[github.com/acamarata/telemetry/blob/main/TELEMETRY.md](https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md)

View file

@ -1,12 +1,20 @@
import eslint from '@eslint/js';
import tseslint from 'typescript-eslint';
import tsParser from '@typescript-eslint/parser';
import tsPlugin from '@typescript-eslint/eslint-plugin';
import eslintConfigPrettier from 'eslint-config-prettier';
import { typescript } from '@acamarata/eslint-config';
export default tseslint.config(
eslint.configs.recommended,
...tseslint.configs.recommended,
export default [
{
files: ['src/**/*.ts'],
plugins: { '@typescript-eslint': tsPlugin },
languageOptions: {
parser: tsParser,
parserOptions: { project: true, tsconfigRootDir: import.meta.dirname },
},
},
...typescript.map((cfg) => ({ ...cfg, files: ['src/**/*.ts'] })),
eslintConfigPrettier,
{
ignores: ['dist/', 'node_modules/', 'test.mjs', 'test-cjs.cjs'],
},
);
];

View file

@ -1,6 +1,6 @@
{
"name": "pray-calc",
"version": "2.1.0",
"version": "2.1.2",
"description": "Islamic prayer times with a physics-grounded dynamic twilight angle algorithm. Covers Fajr, Sunrise, Dhuhr, Asr, Maghrib, Isha, Qiyam. Includes 14 traditional fixed-angle methods for comparison.",
"author": "Aric Camarata",
"license": "MIT",
@ -9,15 +9,11 @@
"types": "./dist/index.d.ts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.ts",
"default": "./dist/index.cjs"
}
}
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"./package.json": "./package.json"
},
"sideEffects": false,
"files": [
@ -34,7 +30,10 @@
"lint": "eslint src/",
"format": "prettier --write src/",
"format:check": "prettier --check src/",
"prepublishOnly": "tsup"
"prepack": "pnpm run build",
"coverage": "c8 --reporter=lcov --reporter=text node test.mjs",
"docs": "typedoc --out .github/wiki/api src/index.ts",
"postbuild": "cp dist/index.d.ts dist/index.d.mts"
},
"keywords": [
"prayer-times",
@ -74,13 +73,25 @@
"nrel-spa": "^2.0.1"
},
"devDependencies": {
"@acamarata/eslint-config": "^0.1.0",
"@acamarata/prettier-config": "^0.1.0",
"@acamarata/tsconfig": "^0.1.0",
"@eslint/js": "^10.0.1",
"@types/node": "^25.3.0",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"c8": "^10.1.3",
"eslint": "^10.0.3",
"eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.1",
"tsup": "^8.5.1",
"typedoc": "^0.28.19",
"typedoc-plugin-markdown": "^4.11.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.56.1"
}
"typescript-eslint": "^8.56.1",
"@acamarata/telemetry": "^0.1.0"
},
"type": "module",
"packageManager": "pnpm@10.11.1",
"prettier": "@acamarata/prettier-config"
}

File diff suppressed because it is too large Load diff

View file

@ -2,9 +2,9 @@
* Formatted prayer times using the PrayCalc Dynamic Method.
*/
import { formatTime } from 'nrel-spa';
import { getTimes } from './getTimes.js';
import type { FormattedPrayerTimes } from './types.js';
import { formatTime } from "nrel-spa";
import { getTimes } from "./getTimes.js";
import type { FormattedPrayerTimes } from "./types.js";
/**
* Compute prayer times formatted as HH:MM:SS strings.
@ -12,8 +12,20 @@ import type { FormattedPrayerTimes } from './types.js';
* Uses the dynamic twilight angle algorithm. See getTimes() for full parameter
* documentation.
*
* @param date - Observer's local date
* @param lat - Latitude in decimal degrees (-90 to 90)
* @param lng - Longitude in decimal degrees (-180 to 180)
* @param tz - UTC offset in hours (default: system timezone)
* @param elevation - Elevation in meters (default: 0)
* @param temperature - Temperature in Celsius (default: 15)
* @param pressure - Pressure in mbar/hPa (default: 1013.25)
* @param hanafi - Hanafi Asr convention (default: false)
* @returns Prayer times as HH:MM:SS strings. Returns "N/A" for any time that
* cannot be computed (polar night, unreachable angle, etc.).
* @example
* const times = calcTimes(new Date('2024-06-21'), 40.7128, -74.006, -4);
* console.log(times.Fajr); // "03:51:24"
* console.log(times.Maghrib); // "20:31:17"
*/
export function calcTimes(
date: Date,

View file

@ -2,9 +2,9 @@
* Formatted prayer times dynamic method plus all traditional method comparisons.
*/
import { formatTime } from 'nrel-spa';
import { getTimesAll } from './getTimesAll.js';
import type { FormattedPrayerTimesAll } from './types.js';
import { formatTime } from "nrel-spa";
import { getTimesAll } from "./getTimesAll.js";
import type { FormattedPrayerTimesAll } from "./types.js";
/**
* Compute prayer times formatted as HH:MM:SS strings, plus comparison times
@ -13,8 +13,19 @@ import type { FormattedPrayerTimesAll } from './types.js';
* Uses the dynamic twilight angle algorithm for the primary times. See
* getTimesAll() for full parameter documentation.
*
* @param date - Observer's local date
* @param lat - Latitude in decimal degrees (-90 to 90)
* @param lng - Longitude in decimal degrees (-180 to 180)
* @param tz - UTC offset in hours (default: system timezone)
* @param elevation - Elevation in meters (default: 0)
* @param temperature - Temperature in Celsius (default: 15)
* @param pressure - Pressure in mbar/hPa (default: 1013.25)
* @param hanafi - Hanafi Asr convention (default: false)
* @returns All prayer times as HH:MM:SS strings. "N/A" for unreachable events.
* Methods map contains [fajrString, ishaString] per method.
* @example
* const result = calcTimesAll(new Date('2024-06-21'), 40.7128, -74.006, -4);
* console.log(result.dynamic.Fajr); // "03:51:24"
* console.log(result.ISNA.Fajr); // "04:07:30"
*/
export function calcTimesAll(
date: Date,

View file

@ -50,10 +50,10 @@
* - Jean Meeus, Astronomical Algorithms (2nd ed., 1998)
*/
import { toJulianDate, solarEphemeris, atmosphericRefraction } from './getSolarEphemeris.js';
import { getMscFajr, getMscIsha, minutesToDepression } from './getMSC.js';
import { DEG, ANGLE_MIN, ANGLE_MAX } from './constants.js';
import type { TwilightAngles } from './types.js';
import { toJulianDate, solarEphemeris, atmosphericRefraction } from "./getSolarEphemeris.js";
import { getMscFajr, getMscIsha, minutesToDepression } from "./getMSC.js";
import { DEG, ANGLE_MIN, ANGLE_MAX } from "./constants.js";
import type { TwilightAngles } from "./types.js";
/** Internal result type including ephemeris data for caller reuse. */
export interface AnglesWithEphemeris extends TwilightAngles {

View file

@ -7,7 +7,7 @@
* and solar noon are known.
*/
import { DEG } from './constants.js';
import { DEG } from "./constants.js";
/**
* Compute Asr time as fractional hours.

View file

@ -22,7 +22,7 @@
* High-latitude handling (|lat| > 55°): falls back to 1/7-night rule.
*/
export type ShafaqMode = 'general' | 'ahmer' | 'abyad';
export type ShafaqMode = "general" | "ahmer" | "abyad";
/**
* Normalisation latitude (degrees) used as the divisor in MCW latitude
@ -92,6 +92,13 @@ function interpolateSegment(
*
* Returns minutes before sunrise. At latitudes above 55°, the 1/7-night
* approximation is recommended (handled at the calling site).
*
* @param date - Observer's local date
* @param latitude - Observer latitude in decimal degrees
* @returns Minutes before sunrise for Fajr (Subh Sadiq)
* @example
* const offset = getMscFajr(new Date('2024-06-21'), 40.7128);
* // offset ≈ 93 (minutes before sunrise for New York in summer)
*/
export function getMscFajr(date: Date, latitude: number): number {
const latAbs = Math.abs(latitude);
@ -114,22 +121,30 @@ export function getMscFajr(date: Date, latitude: number): number {
* - 'general': blend that reduces hardship at high latitudes (default)
* - 'ahmer': based on disappearance of redness (shafaq ahmer)
* - 'abyad': based on disappearance of whiteness (shafaq abyad), later
*
* @param date - Observer's local date
* @param latitude - Observer latitude in decimal degrees
* @param shafaq - Twilight type: 'general' | 'ahmer' | 'abyad'
* @returns Minutes after sunset for Isha
* @example
* const offset = getMscIsha(new Date('2024-06-21'), 40.7128, 'general');
* // offset ≈ 84
*/
export function getMscIsha(date: Date, latitude: number, shafaq: ShafaqMode = 'general'): number {
export function getMscIsha(date: Date, latitude: number, shafaq: ShafaqMode = "general"): number {
const latAbs = Math.abs(latitude);
const { dyy, daysInYear } = computeDyy(date, latitude);
let a: number, b: number, c: number, d: number;
switch (shafaq) {
case 'ahmer':
case "ahmer":
// Shafaq ahmer (red glow): BASE = 62 min (shorter twilight)
a = 62 + (17.4 / LAT_SCALE) * latAbs;
b = 62 - (7.16 / LAT_SCALE) * latAbs;
c = 62 + (5.12 / LAT_SCALE) * latAbs;
d = 62 + (19.44 / LAT_SCALE) * latAbs;
break;
case 'abyad':
case "abyad":
// Shafaq abyad (white glow): BASE = 75 min (longer twilight)
a = 75 + (25.6 / LAT_SCALE) * latAbs;
b = 75 + (7.16 / LAT_SCALE) * latAbs;

View file

@ -8,9 +8,17 @@
* prayer time solving still uses the full SPA via nrel-spa.
*/
import { DEG } from './constants.js';
import { DEG } from "./constants.js";
/** Julian Date from a JavaScript Date (UTC). */
/**
* Convert a JavaScript Date to a Julian Date number.
*
* @param date - Any JavaScript Date object (uses UTC internally)
* @returns Julian Date: days since noon January 1, 4713 BC UTC
* @example
* const jd = toJulianDate(new Date('2024-06-21'));
* // jd ≈ 2460482.5
*/
export function toJulianDate(date: Date): number {
return date.getTime() / 86400000 + 2440587.5;
}
@ -27,6 +35,13 @@ export interface SolarEphemeris {
/**
* Compute solar declination, Earth-Sun distance, and ecliptic longitude
* from a Julian Date. Accuracy: ~0.01° for declination, ~0.0001 AU for r.
*
* @param jd - Julian Date (use toJulianDate to convert a JS Date)
* @returns Solar ephemeris data: declination (degrees), Earth-Sun distance (AU),
* and ecliptic longitude (radians, 0-2π season phase)
* @example
* const jd = toJulianDate(new Date('2024-06-21'));
* const { decl, r, eclLon } = solarEphemeris(jd);
*/
export function solarEphemeris(jd: number): SolarEphemeris {
const T = (jd - 2451545.0) / 36525.0;

View file

@ -6,14 +6,14 @@
* offset (tz parameter).
*/
import { getSpa } from 'nrel-spa';
import { computeAngles } from './getAngles.js';
import { getAsr } from './getAsr.js';
import { getQiyam } from './getQiyam.js';
import { getMidnight } from './getMidnight.js';
import { validateInputs } from './validate.js';
import { DHUHR_OFFSET_MINUTES } from './constants.js';
import type { PrayerTimes } from './types.js';
import { getSpa } from "nrel-spa";
import { computeAngles } from "./getAngles.js";
import { getAsr } from "./getAsr.js";
import { getQiyam } from "./getQiyam.js";
import { getMidnight } from "./getMidnight.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.
@ -67,11 +67,15 @@ export function getTimes(
const spaOpts = { elevation, temperature, pressure };
const spaData = getSpa(date, lat, lng, tz, spaOpts, [fajrZenith, ishaZenith]);
const fajrTime = spaData.angles[0].sunrise;
// Non-null assertions: getSpa was called with exactly [fajrZenith, ishaZenith], so
// index 0 and 1 are always defined.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const fajrTime = spaData.angles[0]!.sunrise;
const sunriseTime = spaData.sunrise;
const noonTime = spaData.solarNoon;
const maghribTime = spaData.sunset;
const ishaTime = spaData.angles[1].sunset;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ishaTime = spaData.angles[1]!.sunset;
// Dhuhr: offset after solar noon (standard practice to confirm transit).
const dhuhrTime = noonTime + DHUHR_OFFSET_MINUTES / 60;

View file

@ -24,109 +24,109 @@
* | MSC | Moonsighting Committee Worldwide (seasonal) | | | Global |
*/
import { getSpa } from 'nrel-spa';
import { computeAngles } from './getAngles.js';
import { getAsr } from './getAsr.js';
import { getQiyam } from './getQiyam.js';
import { getMidnight } from './getMidnight.js';
import { getMscFajr, getMscIsha } from './getMSC.js';
import { validateInputs } from './validate.js';
import { DHUHR_OFFSET_MINUTES } from './constants.js';
import type { MethodDefinition, PrayerTimesAll } from './types.js';
import { getSpa } from "nrel-spa";
import { computeAngles } from "./getAngles.js";
import { getAsr } from "./getAsr.js";
import { getQiyam } from "./getQiyam.js";
import { getMidnight } from "./getMidnight.js";
import { getMscFajr, getMscIsha } from "./getMSC.js";
import { validateInputs } from "./validate.js";
import { DHUHR_OFFSET_MINUTES } from "./constants.js";
import type { MethodDefinition, PrayerTimesAll } from "./types.js";
/** All supported traditional methods. */
const METHODS: MethodDefinition[] = [
{
id: 'UOIF',
name: 'Union des Organisations Islamiques de France',
region: 'France',
id: "UOIF",
name: "Union des Organisations Islamiques de France",
region: "France",
fajrAngle: 12,
ishaAngle: 12,
},
{
id: 'ISNACA',
name: 'IQNA / Islamic Council of North America',
region: 'Canada',
id: "ISNACA",
name: "IQNA / Islamic Council of North America",
region: "Canada",
fajrAngle: 13,
ishaAngle: 13,
},
{
id: 'ISNA',
name: 'FCNA / Islamic Society of North America',
region: 'US, UK, AU, NZ',
id: "ISNA",
name: "FCNA / Islamic Society of North America",
region: "US, UK, AU, NZ",
fajrAngle: 15,
ishaAngle: 15,
},
{
id: 'SAMR',
name: 'Spiritual Administration of Muslims of Russia',
region: 'Russia',
id: "SAMR",
name: "Spiritual Administration of Muslims of Russia",
region: "Russia",
fajrAngle: 16,
ishaAngle: 15,
},
{
id: 'IGUT',
name: 'Institute of Geophysics, University of Tehran',
region: 'Iran',
id: "IGUT",
name: "Institute of Geophysics, University of Tehran",
region: "Iran",
fajrAngle: 17.7,
ishaAngle: 14,
},
{ id: 'MWL', name: 'Muslim World League', region: 'Global', fajrAngle: 18, ishaAngle: 17 },
{ id: "MWL", name: "Muslim World League", region: "Global", fajrAngle: 18, ishaAngle: 17 },
{
id: 'DIBT',
name: 'Diyanet İşleri Başkanlığı, Turkey',
region: 'Turkey',
id: "DIBT",
name: "Diyanet İşleri Başkanlığı, Turkey",
region: "Turkey",
fajrAngle: 18,
ishaAngle: 17,
},
{
id: 'Karachi',
name: 'University of Islamic Sciences, Karachi',
region: 'PK, BD, IN, AF',
id: "Karachi",
name: "University of Islamic Sciences, Karachi",
region: "PK, BD, IN, AF",
fajrAngle: 18,
ishaAngle: 18,
},
{
id: 'Kuwait',
name: 'Kuwait Ministry of Islamic Affairs',
region: 'Kuwait',
id: "Kuwait",
name: "Kuwait Ministry of Islamic Affairs",
region: "Kuwait",
fajrAngle: 18,
ishaAngle: 17.5,
},
{
id: 'UAQ',
name: 'Umm Al-Qura University, Makkah',
region: 'Saudi Arabia',
id: "UAQ",
name: "Umm Al-Qura University, Makkah",
region: "Saudi Arabia",
fajrAngle: 18.5,
ishaAngle: null,
ishaMinutes: 90,
},
{
id: 'Qatar',
name: 'Qatar / Gulf Standard',
region: 'Qatar, Gulf',
id: "Qatar",
name: "Qatar / Gulf Standard",
region: "Qatar, Gulf",
fajrAngle: 18,
ishaAngle: null,
ishaMinutes: 90,
},
{
id: 'Egypt',
name: 'Egyptian General Authority of Survey',
region: 'EG, SY, IQ, LB',
id: "Egypt",
name: "Egyptian General Authority of Survey",
region: "EG, SY, IQ, LB",
fajrAngle: 19.5,
ishaAngle: 17.5,
},
{
id: 'MUIS',
name: 'Majlis Ugama Islam Singapura',
region: 'Singapore',
id: "MUIS",
name: "Majlis Ugama Islam Singapura",
region: "Singapore",
fajrAngle: 20,
ishaAngle: 18,
},
{
id: 'MSC',
name: 'Moonsighting Committee Worldwide',
region: 'Global',
id: "MSC",
name: "Moonsighting Committee Worldwide",
region: "Global",
fajrAngle: null,
ishaAngle: null,
useMSC: true,
@ -192,11 +192,14 @@ export function getTimesAll(
const spaData = getSpa(date, lat, lng, tz, spaOpts, allZeniths);
// 3. Extract core times (index 0 = dynamic Fajr, index 1 = dynamic Isha).
const fajrTime = spaData.angles[0].sunrise;
// Non-null assertions: allZeniths guarantees at least 2 angle entries (index 0 and 1 always set).
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const fajrTime = spaData.angles[0]!.sunrise;
const sunriseTime = spaData.sunrise;
const noonTime = spaData.solarNoon;
const maghribTime = spaData.sunset;
const ishaTime = spaData.angles[1].sunset;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const ishaTime = spaData.angles[1]!.sunset;
const dhuhrTime = noonTime + DHUHR_OFFSET_MINUTES / 60;
// 4. Asr time (reuses declination from computeAngles — no extra ephemeris call).
@ -208,10 +211,14 @@ export function getTimesAll(
const Methods: Record<string, [number, number]> = {};
for (let i = 0; i < METHODS.length; i++) {
const m = METHODS[i];
// Non-null assertion: METHODS.length is static (14), allZeniths was built with exactly
// 2 + METHODS.length*2 entries, so spaBaseIdx and spaBaseIdx+1 are always valid.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const m = METHODS[i]!;
const spaBaseIdx = 2 + i * 2; // angles index offset for this method
let methodFajr = spaData.angles[spaBaseIdx].sunrise;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
let methodFajr = spaData.angles[spaBaseIdx]!.sunrise;
let methodIsha: number;
if (m.useMSC) {
@ -224,7 +231,8 @@ export function getTimesAll(
// Fixed-minute Isha (UAQ = 90 min, Qatar = 90 min after sunset).
methodIsha = isFinite(maghribTime) ? maghribTime + m.ishaMinutes / 60 : NaN;
} else {
methodIsha = spaData.angles[spaBaseIdx + 1].sunset;
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
methodIsha = spaData.angles[spaBaseIdx + 1]!.sunset;
}
Methods[m.id] = [methodFajr, methodIsha];

View file

@ -16,17 +16,17 @@
* METHODS - Array of all supported traditional method definitions
*/
export { getTimes } from './getTimes.js';
export { calcTimes } from './calcTimes.js';
export { getTimesAll, METHODS } from './getTimesAll.js';
export { calcTimesAll } from './calcTimesAll.js';
export { getAngles } from './getAngles.js';
export { getAsr } from './getAsr.js';
export { getQiyam } from './getQiyam.js';
export { getMidnight } from './getMidnight.js';
export { getMscFajr, getMscIsha } from './getMSC.js';
export { solarEphemeris, toJulianDate } from './getSolarEphemeris.js';
export { DHUHR_OFFSET_MINUTES, ANGLE_MIN, ANGLE_MAX } from './constants.js';
export { getTimes } from "./getTimes.js";
export { calcTimes } from "./calcTimes.js";
export { getTimesAll, METHODS } from "./getTimesAll.js";
export { calcTimesAll } from "./calcTimesAll.js";
export { getAngles } from "./getAngles.js";
export { getAsr } from "./getAsr.js";
export { getQiyam } from "./getQiyam.js";
export { getMidnight } from "./getMidnight.js";
export { getMscFajr, getMscIsha } from "./getMSC.js";
export { solarEphemeris, toJulianDate } from "./getSolarEphemeris.js";
export { DHUHR_OFFSET_MINUTES, ANGLE_MIN, ANGLE_MAX } from "./constants.js";
export type {
FractionalHours,
@ -41,4 +41,15 @@ export type {
FormattedPrayerTimesAll,
AtmosphericParams,
MethodDefinition,
} from './types.js';
} from "./types.js";
// ── Opt-in anonymous telemetry ────────────────────────────────────────────────
// Off by default. Enable: ACAMARATA_TELEMETRY=1
// What is sent + how to disable: https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md
import('@acamarata/telemetry')
.then(({ track }) =>
track('load', { package: 'pray-calc', version: '2.1.2' }),
)
.catch(() => {
// telemetry not installed or disabled — that is fine
});

View file

@ -9,10 +9,10 @@ export type FractionalHours = number;
export type TimeString = string;
/** Asr shadow convention: Shafi'i (shadow = 1x object length) or Hanafi (2x). */
export type AsrConvention = 'shafii' | 'hanafi';
export type AsrConvention = "shafii" | "hanafi";
/** Shafaq (twilight glow) variant for the MSC Isha model. */
export type ShafaqMode = 'general' | 'ahmer' | 'abyad';
export type ShafaqMode = "general" | "ahmer" | "abyad";
/** Computed twilight depression angles for Fajr and Isha. */
export interface TwilightAngles {

View file

@ -1,16 +1,9 @@
{
"extends": "@acamarata/tsconfig/tsconfig.library.json",
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "bundler",
"strict": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"esModuleInterop": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"outDir": "dist",
"rootDir": "src",
"types": ["node"]
},

10
typedoc.json Normal file
View file

@ -0,0 +1,10 @@
{
"entryPoints": ["src/index.ts"],
"out": ".github/wiki/api",
"plugin": ["typedoc-plugin-markdown"],
"readme": "none",
"skipErrorChecking": false,
"excludePrivate": true,
"excludeProtected": true,
"includeVersion": true
}