Compare commits

..

No commits in common. "main" and "v2.1.2" have entirely different histories.
main ... v2.1.2

15 changed files with 92 additions and 138 deletions

View file

@ -66,11 +66,6 @@ Full API reference, dynamic algorithm details, traditional method table, and hig
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). 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).
## Telemetry
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 ## License
MIT. Copyright (c) 2023-2026 Aric Camarata. MIT. Copyright (c) 2023-2026 Aric Camarata.

View file

@ -1,8 +0,0 @@
# 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

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

View file

@ -30,7 +30,7 @@
"lint": "eslint src/", "lint": "eslint src/",
"format": "prettier --write src/", "format": "prettier --write src/",
"format:check": "prettier --check src/", "format:check": "prettier --check src/",
"prepack": "pnpm run build", "prepublishOnly": "tsup",
"coverage": "c8 --reporter=lcov --reporter=text node test.mjs", "coverage": "c8 --reporter=lcov --reporter=text node test.mjs",
"docs": "typedoc --out .github/wiki/api src/index.ts", "docs": "typedoc --out .github/wiki/api src/index.ts",
"postbuild": "cp dist/index.d.ts dist/index.d.mts" "postbuild": "cp dist/index.d.ts dist/index.d.mts"
@ -74,13 +74,11 @@
}, },
"devDependencies": { "devDependencies": {
"@acamarata/eslint-config": "^0.1.0", "@acamarata/eslint-config": "^0.1.0",
"c8": "^10.1.3",
"@acamarata/prettier-config": "^0.1.0", "@acamarata/prettier-config": "^0.1.0",
"@acamarata/tsconfig": "^0.1.0", "@acamarata/tsconfig": "^0.1.0",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@types/node": "^25.3.0", "@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": "^10.0.3",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"prettier": "^3.8.1", "prettier": "^3.8.1",
@ -88,8 +86,7 @@
"typedoc": "^0.28.19", "typedoc": "^0.28.19",
"typedoc-plugin-markdown": "^4.11.0", "typedoc-plugin-markdown": "^4.11.0",
"typescript": "^5.9.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.56.1", "typescript-eslint": "^8.56.1"
"@acamarata/telemetry": "^0.1.0"
}, },
"type": "module", "type": "module",
"packageManager": "pnpm@10.11.1", "packageManager": "pnpm@10.11.1",

View file

@ -18,9 +18,6 @@ importers:
'@acamarata/prettier-config': '@acamarata/prettier-config':
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0(prettier@3.8.1) version: 0.1.0(prettier@3.8.1)
'@acamarata/telemetry':
specifier: ^0.1.0
version: 0.1.0
'@acamarata/tsconfig': '@acamarata/tsconfig':
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0 version: 0.1.0
@ -30,12 +27,6 @@ importers:
'@types/node': '@types/node':
specifier: ^25.3.0 specifier: ^25.3.0
version: 25.3.0 version: 25.3.0
'@typescript-eslint/eslint-plugin':
specifier: ^8.56.1
version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: ^8.56.1
version: 8.56.1(eslint@10.0.3)(typescript@5.9.3)
c8: c8:
specifier: ^10.1.3 specifier: ^10.1.3
version: 10.1.3 version: 10.1.3
@ -88,10 +79,6 @@ packages:
peerDependencies: peerDependencies:
prettier: '>=3.0.0' prettier: '>=3.0.0'
'@acamarata/telemetry@0.1.0':
resolution: {integrity: sha512-iP09ZD0bHencHLbv6kQZDgwN9crLCWGKxmiMrfJjhBCoWTgv4koSgg0Li/LFKwCCFluua6orj9fVeQ8eqcJXSQ==}
engines: {node: '>=20'}
'@acamarata/tsconfig@0.1.0': '@acamarata/tsconfig@0.1.0':
resolution: {integrity: sha512-bgzyBak43mE+0HhduZX3cvaPjKcggtGGZZMjr35qtYWolsIWgZ9nx7OOswbVYoU35qoUv6rZ0mTK6GbZ8QTYjw==} resolution: {integrity: sha512-bgzyBak43mE+0HhduZX3cvaPjKcggtGGZZMjr35qtYWolsIWgZ9nx7OOswbVYoU35qoUv6rZ0mTK6GbZ8QTYjw==}
engines: {node: '>=20'} engines: {node: '>=20'}
@ -1240,8 +1227,6 @@ snapshots:
dependencies: dependencies:
prettier: 3.8.1 prettier: 3.8.1
'@acamarata/telemetry@0.1.0': {}
'@acamarata/tsconfig@0.1.0': {} '@acamarata/tsconfig@0.1.0': {}
'@bcoe/v8-coverage@1.0.2': {} '@bcoe/v8-coverage@1.0.2': {}

View file

@ -2,9 +2,9 @@
* Formatted prayer times using the PrayCalc Dynamic Method. * Formatted prayer times using the PrayCalc Dynamic Method.
*/ */
import { formatTime } from "nrel-spa"; import { formatTime } from 'nrel-spa';
import { getTimes } from "./getTimes.js"; import { getTimes } from './getTimes.js';
import type { FormattedPrayerTimes } from "./types.js"; import type { FormattedPrayerTimes } from './types.js';
/** /**
* Compute prayer times formatted as HH:MM:SS strings. * Compute prayer times formatted as HH:MM:SS strings.

View file

@ -2,9 +2,9 @@
* Formatted prayer times dynamic method plus all traditional method comparisons. * Formatted prayer times dynamic method plus all traditional method comparisons.
*/ */
import { formatTime } from "nrel-spa"; import { formatTime } from 'nrel-spa';
import { getTimesAll } from "./getTimesAll.js"; import { getTimesAll } from './getTimesAll.js';
import type { FormattedPrayerTimesAll } from "./types.js"; import type { FormattedPrayerTimesAll } from './types.js';
/** /**
* Compute prayer times formatted as HH:MM:SS strings, plus comparison times * Compute prayer times formatted as HH:MM:SS strings, plus comparison times

View file

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

View file

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

View file

@ -22,7 +22,7 @@
* High-latitude handling (|lat| > 55°): falls back to 1/7-night rule. * 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 * Normalisation latitude (degrees) used as the divisor in MCW latitude
@ -130,21 +130,21 @@ export function getMscFajr(date: Date, latitude: number): number {
* const offset = getMscIsha(new Date('2024-06-21'), 40.7128, 'general'); * const offset = getMscIsha(new Date('2024-06-21'), 40.7128, 'general');
* // offset ≈ 84 * // 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 latAbs = Math.abs(latitude);
const { dyy, daysInYear } = computeDyy(date, latitude); const { dyy, daysInYear } = computeDyy(date, latitude);
let a: number, b: number, c: number, d: number; let a: number, b: number, c: number, d: number;
switch (shafaq) { switch (shafaq) {
case "ahmer": case 'ahmer':
// Shafaq ahmer (red glow): BASE = 62 min (shorter twilight) // Shafaq ahmer (red glow): BASE = 62 min (shorter twilight)
a = 62 + (17.4 / LAT_SCALE) * latAbs; a = 62 + (17.4 / LAT_SCALE) * latAbs;
b = 62 - (7.16 / LAT_SCALE) * latAbs; b = 62 - (7.16 / LAT_SCALE) * latAbs;
c = 62 + (5.12 / LAT_SCALE) * latAbs; c = 62 + (5.12 / LAT_SCALE) * latAbs;
d = 62 + (19.44 / LAT_SCALE) * latAbs; d = 62 + (19.44 / LAT_SCALE) * latAbs;
break; break;
case "abyad": case 'abyad':
// Shafaq abyad (white glow): BASE = 75 min (longer twilight) // Shafaq abyad (white glow): BASE = 75 min (longer twilight)
a = 75 + (25.6 / LAT_SCALE) * latAbs; a = 75 + (25.6 / LAT_SCALE) * latAbs;
b = 75 + (7.16 / LAT_SCALE) * latAbs; b = 75 + (7.16 / LAT_SCALE) * latAbs;

View file

@ -8,7 +8,7 @@
* prayer time solving still uses the full SPA via nrel-spa. * prayer time solving still uses the full SPA via nrel-spa.
*/ */
import { DEG } from "./constants.js"; import { DEG } from './constants.js';
/** /**
* Convert a JavaScript Date to a Julian Date number. * Convert a JavaScript Date to a Julian Date number.

View file

@ -6,14 +6,14 @@
* offset (tz parameter). * offset (tz parameter).
*/ */
import { getSpa } from "nrel-spa"; import { getSpa } from 'nrel-spa';
import { computeAngles } from "./getAngles.js"; import { computeAngles } from './getAngles.js';
import { getAsr } from "./getAsr.js"; import { getAsr } from './getAsr.js';
import { getQiyam } from "./getQiyam.js"; import { getQiyam } from './getQiyam.js';
import { getMidnight } from "./getMidnight.js"; import { getMidnight } from './getMidnight.js';
import { validateInputs } from "./validate.js"; import { validateInputs } from './validate.js';
import { DHUHR_OFFSET_MINUTES } from "./constants.js"; import { DHUHR_OFFSET_MINUTES } from './constants.js';
import type { PrayerTimes } from "./types.js"; import type { PrayerTimes } from './types.js';
/** /**
* Compute prayer times for a given date and location. * Compute prayer times for a given date and location.

View file

@ -24,109 +24,109 @@
* | MSC | Moonsighting Committee Worldwide (seasonal) | | | Global | * | MSC | Moonsighting Committee Worldwide (seasonal) | | | Global |
*/ */
import { getSpa } from "nrel-spa"; import { getSpa } from 'nrel-spa';
import { computeAngles } from "./getAngles.js"; import { computeAngles } from './getAngles.js';
import { getAsr } from "./getAsr.js"; import { getAsr } from './getAsr.js';
import { getQiyam } from "./getQiyam.js"; import { getQiyam } from './getQiyam.js';
import { getMidnight } from "./getMidnight.js"; import { getMidnight } from './getMidnight.js';
import { getMscFajr, getMscIsha } from "./getMSC.js"; import { getMscFajr, getMscIsha } from './getMSC.js';
import { validateInputs } from "./validate.js"; import { validateInputs } from './validate.js';
import { DHUHR_OFFSET_MINUTES } from "./constants.js"; import { DHUHR_OFFSET_MINUTES } from './constants.js';
import type { MethodDefinition, PrayerTimesAll } from "./types.js"; import type { MethodDefinition, PrayerTimesAll } from './types.js';
/** All supported traditional methods. */ /** All supported traditional methods. */
const METHODS: MethodDefinition[] = [ const METHODS: MethodDefinition[] = [
{ {
id: "UOIF", id: 'UOIF',
name: "Union des Organisations Islamiques de France", name: 'Union des Organisations Islamiques de France',
region: "France", region: 'France',
fajrAngle: 12, fajrAngle: 12,
ishaAngle: 12, ishaAngle: 12,
}, },
{ {
id: "ISNACA", id: 'ISNACA',
name: "IQNA / Islamic Council of North America", name: 'IQNA / Islamic Council of North America',
region: "Canada", region: 'Canada',
fajrAngle: 13, fajrAngle: 13,
ishaAngle: 13, ishaAngle: 13,
}, },
{ {
id: "ISNA", id: 'ISNA',
name: "FCNA / Islamic Society of North America", name: 'FCNA / Islamic Society of North America',
region: "US, UK, AU, NZ", region: 'US, UK, AU, NZ',
fajrAngle: 15, fajrAngle: 15,
ishaAngle: 15, ishaAngle: 15,
}, },
{ {
id: "SAMR", id: 'SAMR',
name: "Spiritual Administration of Muslims of Russia", name: 'Spiritual Administration of Muslims of Russia',
region: "Russia", region: 'Russia',
fajrAngle: 16, fajrAngle: 16,
ishaAngle: 15, ishaAngle: 15,
}, },
{ {
id: "IGUT", id: 'IGUT',
name: "Institute of Geophysics, University of Tehran", name: 'Institute of Geophysics, University of Tehran',
region: "Iran", region: 'Iran',
fajrAngle: 17.7, fajrAngle: 17.7,
ishaAngle: 14, 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", id: 'DIBT',
name: "Diyanet İşleri Başkanlığı, Turkey", name: 'Diyanet İşleri Başkanlığı, Turkey',
region: "Turkey", region: 'Turkey',
fajrAngle: 18, fajrAngle: 18,
ishaAngle: 17, ishaAngle: 17,
}, },
{ {
id: "Karachi", id: 'Karachi',
name: "University of Islamic Sciences, Karachi", name: 'University of Islamic Sciences, Karachi',
region: "PK, BD, IN, AF", region: 'PK, BD, IN, AF',
fajrAngle: 18, fajrAngle: 18,
ishaAngle: 18, ishaAngle: 18,
}, },
{ {
id: "Kuwait", id: 'Kuwait',
name: "Kuwait Ministry of Islamic Affairs", name: 'Kuwait Ministry of Islamic Affairs',
region: "Kuwait", region: 'Kuwait',
fajrAngle: 18, fajrAngle: 18,
ishaAngle: 17.5, ishaAngle: 17.5,
}, },
{ {
id: "UAQ", id: 'UAQ',
name: "Umm Al-Qura University, Makkah", name: 'Umm Al-Qura University, Makkah',
region: "Saudi Arabia", region: 'Saudi Arabia',
fajrAngle: 18.5, fajrAngle: 18.5,
ishaAngle: null, ishaAngle: null,
ishaMinutes: 90, ishaMinutes: 90,
}, },
{ {
id: "Qatar", id: 'Qatar',
name: "Qatar / Gulf Standard", name: 'Qatar / Gulf Standard',
region: "Qatar, Gulf", region: 'Qatar, Gulf',
fajrAngle: 18, fajrAngle: 18,
ishaAngle: null, ishaAngle: null,
ishaMinutes: 90, ishaMinutes: 90,
}, },
{ {
id: "Egypt", id: 'Egypt',
name: "Egyptian General Authority of Survey", name: 'Egyptian General Authority of Survey',
region: "EG, SY, IQ, LB", region: 'EG, SY, IQ, LB',
fajrAngle: 19.5, fajrAngle: 19.5,
ishaAngle: 17.5, ishaAngle: 17.5,
}, },
{ {
id: "MUIS", id: 'MUIS',
name: "Majlis Ugama Islam Singapura", name: 'Majlis Ugama Islam Singapura',
region: "Singapore", region: 'Singapore',
fajrAngle: 20, fajrAngle: 20,
ishaAngle: 18, ishaAngle: 18,
}, },
{ {
id: "MSC", id: 'MSC',
name: "Moonsighting Committee Worldwide", name: 'Moonsighting Committee Worldwide',
region: "Global", region: 'Global',
fajrAngle: null, fajrAngle: null,
ishaAngle: null, ishaAngle: null,
useMSC: true, useMSC: true,

View file

@ -16,17 +16,17 @@
* METHODS - Array of all supported traditional method definitions * METHODS - Array of all supported traditional method definitions
*/ */
export { getTimes } from "./getTimes.js"; export { getTimes } from './getTimes.js';
export { calcTimes } from "./calcTimes.js"; export { calcTimes } from './calcTimes.js';
export { getTimesAll, METHODS } from "./getTimesAll.js"; export { getTimesAll, METHODS } from './getTimesAll.js';
export { calcTimesAll } from "./calcTimesAll.js"; export { calcTimesAll } from './calcTimesAll.js';
export { getAngles } from "./getAngles.js"; export { getAngles } from './getAngles.js';
export { getAsr } from "./getAsr.js"; export { getAsr } from './getAsr.js';
export { getQiyam } from "./getQiyam.js"; export { getQiyam } from './getQiyam.js';
export { getMidnight } from "./getMidnight.js"; export { getMidnight } from './getMidnight.js';
export { getMscFajr, getMscIsha } from "./getMSC.js"; export { getMscFajr, getMscIsha } from './getMSC.js';
export { solarEphemeris, toJulianDate } from "./getSolarEphemeris.js"; export { solarEphemeris, toJulianDate } from './getSolarEphemeris.js';
export { DHUHR_OFFSET_MINUTES, ANGLE_MIN, ANGLE_MAX } from "./constants.js"; export { DHUHR_OFFSET_MINUTES, ANGLE_MIN, ANGLE_MAX } from './constants.js';
export type { export type {
FractionalHours, FractionalHours,
@ -41,15 +41,4 @@ export type {
FormattedPrayerTimesAll, FormattedPrayerTimesAll,
AtmosphericParams, AtmosphericParams,
MethodDefinition, 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; export type TimeString = string;
/** Asr shadow convention: Shafi'i (shadow = 1x object length) or Hanafi (2x). */ /** 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. */ /** 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. */ /** Computed twilight depression angles for Fajr and Isha. */
export interface TwilightAngles { export interface TwilightAngles {