zero-deps-prayer-times
Version:
A lightweight, embeddable Islamic prayer times calculator based on date and coordinates, with zero external dependencies.
599 lines (580 loc) • 17 kB
JavaScript
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// index.ts
var a4an_exports = {};
__export(a4an_exports, {
getPrayerTimes: () => getPrayerTimes
});
module.exports = __toCommonJS(a4an_exports);
// utils/formatting.ts
function minutesToTime(minutes) {
const hours = Math.floor(minutes / 60);
const remainingMinutes = round(minutes % 60).toFixed(0);
return `${hours}:${remainingMinutes}`;
}
function round(number) {
return Math.round(number * 100) / 100;
}
function formatTimeHHMM12Hour(date) {
return date.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
hour12: true
});
}
function formatTimeHHMM24Hour(date) {
return date.toLocaleTimeString([], {
hour: "2-digit",
minute: "2-digit",
hour12: false
});
}
function formatTime(date) {
return {
date,
formatted12H: formatTimeHHMM12Hour(date),
formatted24H: formatTimeHHMM24Hour(date)
};
}
// calculations/day-length.ts
function getDayLength(hourAngle, EoT) {
try {
const totalHourAngle = 2 * hourAngle;
const dayLengthInHours = totalHourAngle / 15;
let dayLengthInMinutes = dayLengthInHours * 60;
dayLengthInMinutes += EoT;
return Number(round(dayLengthInMinutes).toFixed(0));
} catch (error) {
console.error("Error calculating day length", error);
throw error;
}
}
// calculations/declination.ts
function getDeclination(dayOfYear) {
const tilt = 23.44;
const degToRad = Math.PI / 180;
const radToDeg = 180 / Math.PI;
const M = (357.5291 + 0.98560028 * dayOfYear) % 360;
let L = M + 1.9148 * Math.sin(M * degToRad) + 0.02 * Math.sin(2 * M * degToRad) + 282.9404;
L = L % 360;
const declination = Math.asin(Math.sin(L * degToRad) * Math.sin(tilt * degToRad)) * radToDeg;
return round(declination);
}
// calculations/equation-of-time.ts
function getEoT(dayOfYear) {
const meanLongitude = (280.46 + 0.9856474 * dayOfYear) % 360;
const meanAnomaly = (357.528 + 0.9856003 * dayOfYear) % 360;
const obliquity = 23.439 - 4e-7 * dayOfYear;
const rad = Math.PI / 180;
const y = Math.tan(obliquity / 2 * rad) ** 2;
const EoT = (y * Math.sin(2 * meanLongitude * rad) - 2 * 0.0167 * Math.sin(meanAnomaly * rad) + 4 * 0.0167 * y * Math.sin(meanAnomaly * rad) * Math.cos(2 * meanLongitude * rad) - 0.5 * y ** 2 * Math.sin(4 * meanLongitude * rad) - 1.25 * 0.0167 ** 2 * Math.sin(2 * meanAnomaly * rad)) * (4 / rad);
return EoT;
}
// utils/conversions.ts
function radiansToDegrees(radians) {
return radians * 180 / Math.PI;
}
function degreesToRadians(degrees) {
return degrees * Math.PI / 180;
}
// calculations/hour-angle.ts
function getHourAngle(lat, declination, altitude = -0.83) {
const latRad = degreesToRadians(lat);
const decRad = degreesToRadians(declination);
const altRad = degreesToRadians(altitude);
const cosH = (Math.sin(altRad) - Math.sin(latRad) * Math.sin(decRad)) / (Math.cos(latRad) * Math.cos(decRad));
if (cosH < -1 || cosH > 1) {
return 0;
}
const hourAngleRad = Math.acos(cosH);
const hourAngleDeg = radiansToDegrees(hourAngleRad);
return round(hourAngleDeg);
}
// prayers/asr.ts
function getAsrTime(noonTime, lat, declination, hanafi = false) {
try {
const shadowRatio = hanafi ? 2 : 1;
const altitudeAsrRadians = Math.atan(
1 / (shadowRatio + Math.tan(Math.abs(lat - declination) * (Math.PI / 180)))
);
const hourAngleAsrRadians = Math.acos(
(Math.sin(altitudeAsrRadians) - Math.sin(lat * (Math.PI / 180)) * Math.sin(declination * (Math.PI / 180))) / (Math.cos(lat * (Math.PI / 180)) * Math.cos(declination * (Math.PI / 180)))
);
const asrOffsetMinutes = hourAngleAsrRadians * (180 / Math.PI) / 15 * 60;
const noonMinutesUTC = noonTime.getUTCHours() * 60 + noonTime.getUTCMinutes();
const asrTimeMinutesUTC = noonMinutesUTC + asrOffsetMinutes;
const hours = Math.floor(asrTimeMinutesUTC / 60);
const minutes = Math.floor(asrTimeMinutesUTC % 60);
const seconds = Math.round(asrTimeMinutesUTC % 1 * 60);
return new Date(
Date.UTC(
noonTime.getUTCFullYear(),
noonTime.getUTCMonth(),
noonTime.getUTCDate(),
hours,
minutes,
seconds
)
);
} catch (error) {
console.error("Error calculating Asr time");
throw error;
}
}
// prayers/boundaries.ts
function getDayBoundaryTime(lon, hourAngle, date, EoT) {
try {
if (typeof lon !== "number" || lon < -180 || lon > 180) {
throw new Error("lon must be a number between -180 and 180");
}
const solarNoonUTC = 12 * 60 - lon * 4 - EoT;
const sunriseUTC = solarNoonUTC - hourAngle * 4;
const sunsetUTC = solarNoonUTC + hourAngle * 4;
const sunriseHours = Math.floor(sunriseUTC / 60);
const sunriseMinutes = Math.floor(sunriseUTC % 60);
const sunriseSeconds = Math.round(sunriseUTC % 1 * 60);
const sunsetHours = Math.floor(sunsetUTC / 60);
const sunsetMinutes = Math.floor(sunsetUTC % 60);
const sunsetSeconds = Math.round(sunsetUTC % 1 * 60);
const sunriseTime = new Date(
Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
sunriseHours,
sunriseMinutes,
sunriseSeconds
)
);
const maghribTime = new Date(
Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
sunsetHours,
sunsetMinutes,
sunsetSeconds
)
);
return { sunriseTime, maghribTime };
} catch (error) {
console.error("Error calculating sunrise and sunset");
throw error;
}
}
// constants/index.ts
var conventions = [
{
convention: "Muslim World League",
considersRamadan: false,
conventionMethods: {
fajr: "angle",
isha: "angle"
},
angle: {
fajr: 18,
isha: 17
},
time: {
fajr: null,
isha: null
}
},
{
convention: "Islamic Society of North America (ISNA)",
considersRamadan: false,
conventionMethods: {
fajr: "angle",
isha: "angle"
},
angle: {
fajr: 15,
isha: 15
},
time: {
fajr: null,
isha: null
}
},
{
convention: "Egyptian General Authority of Survey",
considersRamadan: false,
conventionMethods: {
fajr: "angle",
isha: "angle"
},
angle: {
fajr: 19.5,
isha: 17.5
},
time: {
fajr: null,
isha: null
}
},
{
convention: "Umm al-Qura University, Makkah",
considersRamadan: true,
conventionMethods: {
fajr: "angle",
isha: "time"
},
angle: {
fajr: 18.5,
isha: null
},
time: {
fajr: null,
isha: {
nonRamadan: 90,
ramadan: 120
}
}
},
{
convention: "University of Islamic Sciences, Karachi",
considersRamadan: false,
conventionMethods: {
fajr: "angle",
isha: "angle"
},
angle: {
fajr: 18,
isha: 18
},
time: {
fajr: null,
isha: null
}
},
{
convention: "Institute of Geophysics, University of Tehran",
considersRamadan: false,
conventionMethods: {
fajr: "angle",
isha: "angle"
},
angle: {
fajr: 17.7,
isha: 14
},
time: {
fajr: null,
isha: null
}
},
{
convention: "Shia Ithna Ashari, Leva Research Institute, Qum",
considersRamadan: false,
conventionMethods: {
fajr: "angle",
isha: "angle"
},
angle: {
fajr: 16,
isha: 14
},
time: {
fajr: null,
isha: null
}
}
];
// prayers/fajr.ts
function getFajrTime(noonTime, lat, declination, convention = "Umm al-Qura University, Makkah") {
try {
const selectedConvention = conventions.find(
(item) => item.convention === convention
);
const fajrSolarAngle = selectedConvention?.angle.fajr ?? 18.5;
const fajrHourAngle = Math.acos(
(Math.cos((90 + Math.abs(fajrSolarAngle)) * (Math.PI / 180)) - Math.sin(lat * (Math.PI / 180)) * Math.sin(declination * (Math.PI / 180))) / (Math.cos(lat * (Math.PI / 180)) * Math.cos(declination * (Math.PI / 180)))
);
const fajrOffsetMinutes = -(fajrHourAngle * (180 / Math.PI)) / 15 * 60;
const noonMinutesUTC = noonTime.getUTCHours() * 60 + noonTime.getUTCMinutes();
const fajrTimeMinutesUTC = noonMinutesUTC + fajrOffsetMinutes;
const hours = Math.floor(fajrTimeMinutesUTC / 60);
const minutes = Math.floor(fajrTimeMinutesUTC % 60);
const seconds = Math.round(fajrTimeMinutesUTC % 1 * 60);
return new Date(
Date.UTC(
noonTime.getUTCFullYear(),
noonTime.getUTCMonth(),
noonTime.getUTCDate(),
hours,
minutes,
seconds
)
);
} catch (error) {
console.error("Error calculating Fajr time");
throw error;
}
}
// utils/date.ts
function getDayOfYear(date) {
const startOfYear = new Date(Date.UTC(date.getUTCFullYear(), 0, 1));
const diff = date.getTime() - startOfYear.getTime();
return Math.floor(round(diff / (1e3 * 60 * 60 * 24)) + 1);
}
function isDateInRamadan(date) {
const hijri = toHijri(date);
if (hijri.month === 9) {
return true;
}
return false;
}
var ISLAMIC_EPOCH = 19484395e-1;
function toHijri(input) {
let gYear, gMonth, gDay;
if (input instanceof Date) {
if (isNaN(input.getTime())) {
throw new Error("Invalid Gregorian date");
}
gYear = input.getFullYear();
gMonth = input.getMonth() + 1;
gDay = input.getDate();
} else if (Array.isArray(input) && input.length === 3) {
[gYear, gMonth, gDay] = input;
} else {
throw new Error(
"Invalid input. Please provide a Date object or an array [year, month, day]"
);
}
if (gYear < 1 || gMonth < 1 || gMonth > 12 || gDay < 1 || gDay > 31) {
throw new Error("Invalid Gregorian date");
}
const jd = gregorianToJulian(gYear, gMonth, gDay);
return julianToHijri(jd);
}
function gregorianToJulian(year, month, day) {
if (month < 3) {
year -= 1;
month += 12;
}
const a = Math.floor(year / 100);
const b = 2 - a + Math.floor(a / 4);
return Math.floor(365.25 * (year + 4716)) + Math.floor(30.6001 * (month + 1)) + day + b - 1524.5;
}
function julianToHijri(jd) {
jd = Math.floor(jd) + 0.5;
const year = Math.floor((30 * (jd - ISLAMIC_EPOCH) + 10646) / 10631);
const month = Math.min(
12,
Math.ceil((jd - (29 + julianToHijriDay(year, 1, 1))) / 29.5) + 1
);
const day = jd - julianToHijriDay(year, month, 1) + 1;
return { year, month, day };
}
function julianToHijriDay(year, month, day) {
return day + Math.ceil(29.5 * (month - 1)) + (year - 1) * 354 + Math.floor((3 + 11 * year) / 30) + ISLAMIC_EPOCH - 1;
}
// prayers/isha.ts
var ramadanAdjustmentMinutes = 120;
var normalAdjustmentMinutes = 90;
function getIshaTime(maghrib) {
try {
const isRamadan = isDateInRamadan(maghrib);
const adjustmentMinutes = isRamadan ? ramadanAdjustmentMinutes : normalAdjustmentMinutes;
const ishaTimeUTC = maghrib.getUTCHours() * 60 + maghrib.getUTCMinutes() + adjustmentMinutes;
const hours = Math.floor(ishaTimeUTC / 60);
const minutes = Math.floor(ishaTimeUTC % 60);
const seconds = Math.round(ishaTimeUTC % 1 * 60);
return new Date(
Date.UTC(
maghrib.getUTCFullYear(),
maghrib.getUTCMonth(),
maghrib.getUTCDate(),
hours,
minutes,
seconds
)
);
} catch (error) {
console.error("Error calculating Isha time");
throw error;
}
}
// prayers/midnight.ts
function getMidnight(maghrib, fajr) {
try {
const maghribUTCMinutes = maghrib.getUTCHours() * 60 + maghrib.getUTCMinutes();
const fajrUTCMinutes = fajr.getUTCHours() * 60 + fajr.getUTCMinutes();
const midnightUTCMinutes = (maghribUTCMinutes + fajrUTCMinutes) / 2;
const hours = Math.floor(midnightUTCMinutes / 60);
const minutes = Math.floor(midnightUTCMinutes % 60);
const seconds = Math.round(midnightUTCMinutes % 1 * 60);
return new Date(
Date.UTC(
maghrib.getUTCFullYear(),
maghrib.getUTCMonth(),
maghrib.getUTCDate(),
hours,
minutes,
seconds
)
);
} catch (error) {
console.error("Error calculating Midnight time");
throw error;
}
}
// prayers/noon.ts
function getNoonTime(date, lon, EoT) {
try {
if (typeof lon !== "number" || lon < -180 || lon > 180) {
throw new Error("lon must be a number between -180 and 180");
}
const solarNoonUTC = 12 * 60 - lon * 4 - EoT;
const hours = Math.floor(solarNoonUTC / 60);
const minutes = Math.floor(solarNoonUTC % 60);
const seconds = Math.round(solarNoonUTC % 1 * 60);
return new Date(
Date.UTC(
date.getUTCFullYear(),
date.getUTCMonth(),
date.getUTCDate(),
hours,
minutes,
seconds
)
);
} catch (error) {
console.error("Error calculating solar noon");
throw error;
}
}
// utils/helpers.ts
function findNextPrayer(currentDate, prayers) {
const futurePrayers = prayers.filter((prayer) => prayer.time > currentDate);
if (futurePrayers.length === 0) {
return {
name: "fajr",
remainingSeconds: 0
};
}
const nextPrayer = futurePrayers.reduce((closestPrayer, currentPrayer) => {
const closestDifference = closestPrayer.time.getTime() - currentDate.getTime();
const currentDifference = currentPrayer.time.getTime() - currentDate.getTime();
return currentDifference < closestDifference ? currentPrayer : closestPrayer;
});
const remainingSeconds = Math.floor(
(nextPrayer.time.getTime() - currentDate.getTime()) / 1e3
);
return {
name: nextPrayer.name,
remainingSeconds
};
}
// utils/validate.ts
function isValidCoordinates(coordinates) {
const { longitude: lon, latitude: lat } = coordinates;
if (lat < -90 || lat > 90) {
return false;
}
if (lon < -180 || lon > 180) {
return false;
}
return true;
}
// index.ts
function getPrayerTimes(date, coordinates, options) {
try {
if (isNaN(date.getTime())) {
return {
data: null,
error: new Error("Invalid date")
};
}
if (!isValidCoordinates(coordinates)) {
return {
data: null,
error: new Error("Invalid coordinates")
};
}
const { longitude, latitude } = coordinates;
const dayOfYear = getDayOfYear(date);
const EoT = getEoT(dayOfYear);
const declination = getDeclination(dayOfYear);
const hourAngle = getHourAngle(latitude, declination);
if (Math.abs(latitude) > 66.5) {
return {
data: null,
error: new Error("Unsupported polar region")
};
}
const noonTime = getNoonTime(date, longitude, EoT);
const { sunriseTime, maghribTime } = getDayBoundaryTime(
longitude,
hourAngle,
date,
EoT
);
const fajrTime = getFajrTime(
noonTime,
latitude,
declination,
options?.convention
);
const ishaTime = getIshaTime(maghribTime);
const asrTime = getAsrTime(
noonTime,
latitude,
declination,
options?.hanafiAsr
);
const midnightTime = getMidnight(maghribTime, fajrTime);
const dayLength = minutesToTime(getDayLength(hourAngle, EoT));
const nextPrayer = findNextPrayer(date, [
{ name: "fajr", time: fajrTime },
{ name: "dhuhr", time: noonTime },
{ name: "asr", time: asrTime },
{ name: "maghrib", time: maghribTime },
{ name: "isha", time: ishaTime }
]);
return {
data: {
prayers: {
fajr: formatTime(fajrTime),
dhuhr: formatTime(noonTime),
asr: formatTime(asrTime),
maghrib: formatTime(maghribTime),
isha: formatTime(ishaTime)
},
extras: {
midnight: formatTime(midnightTime),
sunrise: formatTime(sunriseTime),
dayOfYear,
dayLength,
nextPrayer
}
},
error: null
};
} catch (err) {
return {
data: null,
error: err instanceof Error ? err : new Error("An unknown error occurred")
};
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
getPrayerTimes
});