UNPKG

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
"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 });