UNPKG

prayer-first

Version:

Prayer times for all cities based on location for developers only

574 lines (474 loc) 18.7 kB
import path from 'path'; import { fileURLToPath } from 'url'; import Database from 'better-sqlite3'; /** * PrayerTimes Class * ---------------- * This class calculates Islamic prayer times given geographical location (latitude, longitude) * and timezone. * It calculates prayer times such as Fajr, Sunrise, * Dhuhr, Asr, Maghrib, and Isha * * Parameters: * - latitude, longitude: the location's geographic coordinates * - timeZone: local timezone offset from UTC * - methodParams: Parameters for default Calculation Method are [18, 1, 0, 0, 17] which is MWL - Muslim World League method that dictates angles * you can use MWL - Muslim World League [18, 1, 0, 0, 17] ISNA - North America [15, 1, 0, 0, 15] Egyptian General Authority [19.5, 1, 0, 0, 17.5] Umm al-Qura University (مكة) [18.5, 1, 0, 90, 0] Dubai (UAE) [18.2, 1, 0, 0, 18.2] Qatar [18, 1, 0, 90, 0] Kuwait [18, 1, 0, 0, 17.5] Karachi (Pakistan) [18, 1, 0, 0, 18] Tehran (Iran) [17.7, 1, 0, 0, 14] Moonsighting Committee [18, 1, 0, 0, 18] Custom / Manual [0, 0, 0, 0, 0] by region and sect * - asrMethod: how to compute azimuth of Asr prayer (1 for Shafii, 2 for Hanafi) * - higherLatitudeMethod: how to make time adjustments at high latitude ('NONE', 'ANGLE_BASED', 'MID_NIGHT', 'ONE_SEVEN') * Principal Methods: * - getPrayerTimes(date): returns prayer times for the given date in 24-hour format. * * * Usage example: * * const { PrayerTimes } = require('prayer-first'); * const pt = new PrayerTimes(40.7128, -74.0060, -4); // e.g New York coordinates and timezone * const times = pt.getPrayerTimes(new Date()); * console.log(times); * * Output: * { * status: true * city: "Based on latitude and longitude", * times: { * fajr: "05:12", * sunrise: "06:30", * dhuhr: "12:45", * asr: "15:30", * maghrib: "18:00", * isha: "19:15" * } * } **/ class PrayerTimes { constructor(latitude, longitude, timeZone, methodParams = [18, 1, 0, 0, 17], asrMethod = 1, higherLatitudeMethod = 'NONE') { this.lat = latitude; this.lng = longitude; this.timeZone = timeZone; this.methodParams = methodParams; this.asrMethod = asrMethod; // 1 for Shafii, 2 for Hanafi this.higherLatitudeMethod = higherLatitudeMethod; // 'NONE', 'ANGLE_BASED', 'MID_NIGHT', 'ONE_SEVEN' this.invalidTime = "-----"; } fixAngle(a) { a = a - 360 * Math.floor(a / 360); return (a < 0) ? a + 360 : a; } fixHour(a) { a = a - 24 * Math.floor(a / 24); return (a < 0) ? a + 24 : a; } radiansToDegrees(rad) { return rad * 180 / Math.PI; } degreesToRadians(deg) { return deg * Math.PI / 180; } dSin(d) { return Math.sin(this.degreesToRadians(d)); } dCos(d) { return Math.cos(this.degreesToRadians(d)); } dTan(d) { return Math.tan(this.degreesToRadians(d)); } dArcSin(x) { return this.radiansToDegrees(Math.asin(x)); } dArcCos(x) { return this.radiansToDegrees(Math.acos(x)); } dArcTan2(y, x) { return this.radiansToDegrees(Math.atan2(y, x)); } dArcCot(x) { return this.radiansToDegrees(Math.atan2(1, x)); } julianDate(year, month, day) { if (month <= 2) { 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; } sunPosition(jd) { const D = jd - 2451545.0; const g = this.fixAngle(357.529 + 0.98560028 * D); const q = this.fixAngle(280.459 + 0.98564736 * D); const L = this.fixAngle(q + 1.915 * this.dSin(g) + 0.020 * this.dSin(2 * g)); const e = 23.439 - 0.00000036 * D; const d = this.dArcSin(this.dSin(e) * this.dSin(L)); let ra = this.dArcTan2(this.dCos(e) * this.dSin(L), this.dCos(L)) / 15; ra = this.fixHour(ra); const eqt = q / 15 - ra; return [d, eqt]; } equationOfTime(jd) { return this.sunPosition(jd)[1]; } sunDeclination(jd) { return this.sunPosition(jd)[0]; } computeMidDay(t, jd) { const eqt = this.equationOfTime(jd + t); return this.fixHour(12 - eqt); } computeTime(angle, t, jd) { const d = this.sunDeclination(jd + t); const z = this.computeMidDay(t, jd); const beg = -this.dSin(angle) - this.dSin(d) * this.dSin(this.lat); const mid = this.dCos(d) * this.dCos(this.lat); const v = this.dArcCos(beg / mid) / 15; return z + (angle > 90 ? -v : v); } computeAsr(step, t, jd) { const d = this.sunDeclination(jd + t); const angle = -this.dArcCot(step + Math.tan(Math.abs(this.lat - d) * Math.PI / 180)); return this.computeTime(angle, t, jd); } fixTimes(times) { // Convert times to fraction of day for (let i = 0; i < times.length; i++) { times[i] /= 24; } return times; } nightPortion(angle) { switch(this.higherLatitudeMethod) { case 'ANGLE_BASED': return angle / 60.0; case 'MID_NIGHT': return 0.5; case 'ONE_SEVEN': return 1.0 / 7.0; case 'NONE': default: return 0.0; } } timeDiff(time1, time2) { return this.fixHour(time2 - time1); } adjustHighLatTimes(times) { const nightTime = this.timeDiff(times[4], times[1]); const fajrDiff = this.nightPortion(this.methodParams[0]) * nightTime; if (isNaN(times[0]) || this.timeDiff(times[0], times[1]) > fajrDiff) { times[0] = times[1] - fajrDiff; } const ishaAngle = this.methodParams[4]; const ishaDiff = this.nightPortion(ishaAngle) * nightTime; if (isNaN(times[6]) || this.timeDiff(times[4], times[6]) > ishaDiff) { times[6] = times[4] + ishaDiff; } if (this.methodParams[1] === 0) { const maghribAngle = this.methodParams[2]; const maghribDiff = this.nightPortion(maghribAngle) * nightTime; if (isNaN(times[5]) || this.timeDiff(times[4], times[5]) > maghribDiff) { times[5] = times[4] + maghribDiff; } } } adjustTimes(times) { for (let i = 0; i < times.length; i++) { times[i] += this.timeZone - this.lng / 15; } if (this.methodParams[1] === 1) { times[5] = times[4] + this.methodParams[2] / 60; } if (this.methodParams[3] === 1) { times[6] = times[5] + this.methodParams[4] / 60; } if (this.higherLatitudeMethod !== 'NONE') { this.adjustHighLatTimes(times); } return times; } floatToTime24(time) { if (isNaN(time)) return this.invalidTime; time = this.fixHour(time + 0.5 / 60); // round const hours = Math.floor(time); const minutes = Math.floor((time - hours) * 60); return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; } floatToTime12(time) { if (isNaN(time)) return this.invalidTime; time = this.fixHour(time + 0.5 / 60); // round let hours = Math.floor(time); const minutes = Math.floor((time - hours) * 60); const suffix = hours >= 12 ? 'PM' : 'AM'; hours = hours % 12; if (hours === 0) hours = 12; // 12 AM or 12 PM return `${hours}:${minutes.toString().padStart(2, '0')} ${suffix}`; } computeTimes(jd) { let times = [5, 6, 12, 13, 18, 18, 18]; // guess times (Fajr, Sunrise, Dhuhr, Asr, Sunset, Maghrib, Isha) times = this.fixTimes(times); for (let i = 0; i < 1; i++) { // iterations times[0] = this.computeTime(180 - this.methodParams[0], times[0], jd); // Fajr times[1] = this.computeTime(180 - 0.833, times[1], jd); // Sunrise times[2] = this.computeMidDay(times[2], jd); // Dhuhr times[3] = this.computeAsr(this.asrMethod, times[3], jd); // Asr times[4] = this.computeTime(0.833, times[4], jd); // Sunset times[5] = this.computeTime(this.methodParams[2], times[5], jd); // Maghrib times[6] = this.computeTime(this.methodParams[4], times[6], jd); // Isha } times = this.adjustTimes(times); return times; } getPrayerTimes(date) { const year = date.getFullYear(); const month = date.getMonth() + 1; const day = date.getDate(); let jd = this.julianDate(year, month, day); // Correct Julian Date for longitude jd -= this.lng / (15 * 24); const times = this.computeTimes(jd); return { status:true, city:"Based on Astronomical Calculation", day:day, month:month, times:{ fajr: this.floatToTime24(times[0]), sunrise: this.floatToTime24(times[1]), dhuhr: this.floatToTime24(times[2]), asr: this.floatToTime24(times[3]), maghrib: this.floatToTime24(times[5]), isha: this.floatToTime24(times[6]), } }; } } // calculate distance // // degrees to radians by radians = degrees × (piay / 180) function toradians(deg) { return deg * (Math.PI / 180); } // Haversine math to calculate distance between two location // function getDistanceLatLonInKm(lat1, lon1, lat2, lon2) { const R = 6371; const dLat = toradians(lat2 - lat1); const dLon = toradians(lon2 - lon1); const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(toradians(lat1)) * Math.cos(toradians(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const d = R * c; return d; } // calculate distance // // parsing date // function to12Hour(time24) { let [hour, minute] = time24.split(':').map(Number); const ampm = hour >= 12 ? 'PM' : 'AM'; hour = hour % 12; if (hour === 0) hour = 12; // 0 تصبح 12 في نظام 12 ساعة return `${hour.toString().padStart(2, '0')}:${minute.toString().padStart(2, '0')} ${ampm}`; } function parseDate(inputDate, dbFormat = "MM-DD") { // only "DD-MM" or "YYYY-MM-DD" or "DD-M-YYYY" date let dateObj; if (inputDate.includes('-')) { const parts = inputDate.split('-'); if (parts[0].length === 4) { // "YYYY-MM-DD" dateObj = new Date(inputDate); } else { // "DD-M-YYYY" const [day, month, year] = parts.map(Number); dateObj = new Date(year, month - 1, day); } } const day = String(dateObj.getDate()).padStart(2, '0'); const month = String(dateObj.getMonth() + 1).padStart(2, '0'); if (dbFormat === "MM-DD") { return `${month}-${day}`; } else { return `${day}-${month}`; } } // db path const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const dbPath = path.join(__dirname, 'muslim_db_v2.5.1.db'); const Db = new Database(dbPath); function findClosestCity(latitude,longitude) { // const rows = Db.prepare('SELECT latitude, longitude , name,_id From location').all() const delta = 0.1; const rows = Db.prepare(` SELECT latitude, location.name AS city_name, longitude,prayer_dependent_id,has_fixed_prayer_time, location._id, country.name AS country_name FROM location LEFT JOIN country ON country._id = location.country_id WHERE latitude BETWEEN ? AND ? AND longitude BETWEEN ? AND ? `).all(latitude - delta, latitude + delta, longitude - delta, longitude + delta); let closestCity = null; let minDistance = Infinity; for (const city of rows) { const dist = getDistanceLatLonInKm(latitude, longitude, city.latitude, city.longitude); if (dist < minDistance) { minDistance = dist; closestCity = city; } } if (!closestCity) { return {status :false, error:"No data found for this location"}; } return {status :true, data:closestCity}; } function findCity(countryName,countryCity){ // const country = Db.prepare(` // SELECT name, code, _id FROM country // WHERE name = ? OR code = ? // `).get(countryName,countryName); // if (!country){ // return {status :false, error:"Country not found Enter a country name or country code"} // } // const city = Db.prepare(` // SELECT latitude, name, longitude,prayer_dependent_id,has_fixed_prayer_time, _id FROM location // WHERE country_id = ? AND name = ? // `).get(country._id,countryCity); const city = Db.prepare(` SELECT country.name AS country_name, country.code, location._id, latitude, location.name AS city_name, longitude,prayer_dependent_id,has_fixed_prayer_time FROM location LEFT JOIN country ON country._id = location.country_id WHERE (country.name = ? OR country.code = ?) AND location.name = ? `).get(countryName, countryName ,countryCity); if (!city){ return {status :false, error:"city not found Enter a city name"}; } // city.country = country.name; return {status :true, data:city}; } // here date parameter can be a day or date to deal with difrent date format function getDbPrayerTime(location_id, date, month, year) { let rows; if (date && month) { const dayNum = Number(date); const monthNum = Number(month); if (monthNum >= 1 && monthNum <= 12 && dayNum >= 1 && dayNum <= 31){ const row = Db.prepare(` SELECT month, day, fajr, sunrise, dhuhr, asr, maghrib, isha FROM prayer_time WHERE location_id = ? AND month = ? AND day = ? `).get(location_id, monthNum, dayNum); if (!row) { return {status :false, error:"No data found for this date"}; } return {status :true, day:dayNum, month:monthNum, date:`${month}-${date}` , times:row}; } else { return {status :false, error:"month must be number btween 1-12 And day must be btween 1-31"} } } // return month else if (/^\d{1,2}$/.test(date) && !month && !year) { const monthNum = Number(date); if (monthNum >= 1 && monthNum <= 12){ rows = Db.prepare(` SELECT month, day, fajr, sunrise, dhuhr, asr, maghrib, isha FROM prayer_time WHERE location_id = ? AND month = ? `).all(location_id, monthNum); return {status: true, month: monthNum, times: rows}; } else { return {status :false, error:"for geting all month times enter number btween 1-12 with out month and year parameter"} } // return year } else if (/^\d{4}$/.test(date) || (year && !date && !month)) { rows = Db.prepare(` SELECT month, day, fajr, sunrise, dhuhr, asr, maghrib, isha FROM prayer_time WHERE location_id = ? `).all(location_id); return {status: true, times: rows}; // spefic day } else { const formattedDate = parseDate(date, "MM-DD"); const row = Db.prepare(` SELECT fajr, sunrise, dhuhr, asr, maghrib, isha FROM prayer_time WHERE location_id = ? AND date = ? `).get(location_id, formattedDate); if (!row) { return {status :false, error:"No data found for date"} } return {status :true, date:formattedDate, data:row}; } } // findClosestCity & get Prayer Time Based on database fixed prayer times // function getPrayerTime(latitude, longitude, day, month, year, GetmidNightAndLastThird){ let city = findClosestCity(latitude, longitude); if (!city.status ){ return city } const isFixed = city.data.has_fixed_prayer_time && city.data.has_fixed_prayer_time !== 0; let times; if (isFixed) { times = getDbPrayerTime(city.data.prayer_dependent_id || city.data._id, day, month, year); if (times.status ){ times.city = city.data.city_name; times.country = city.data.country_name; if (GetmidNightAndLastThird && times.times.maghrib){ let { midNight, lastThirdStart } = midNightAndLastThird(times.times.maghrib, times.times.fajr); times.times.midNight = midNight; times.times.lastThirdStart = lastThirdStart; } } return times; } else { return {status :false, error:"There are no fixed prayer times for this location"}; } } // findCity & get Prayer Time Based on database fixed prayer times // function getPrayerTimeByCity(countryName, countryCity, day, month, year, GetmidNightAndLastThird){ let city = findCity(countryName, countryCity); if (!city.status ){ return city } const isFixed = city.data.has_fixed_prayer_time && city.data.has_fixed_prayer_time !== 0; let times; if (isFixed) { times = getDbPrayerTime(city.data.prayer_dependent_id || city.data._id, day, month, year); if (times.status ){ times.city = city.data.city_name; times.country = city.data.country_name; times.code = city.data.code; if (GetmidNightAndLastThird){ let { midNight, lastThirdStart } = midNightAndLastThird(times.times.maghrib, times.times.fajr); times.times.midNight = midNight; times.times.lastThirdStart = lastThirdStart; } } return times; } else { return {status :false, error:"There are no fixed prayer times for this location"} } } // calculate midnight And lastThird based on maghribTime & fajrTime // function midNightAndLastThird(maghribTime, fajrTime) { function toMinutes(t) { const [h, m] = t.split(":").map(Number); return h * 60 + m; } function toTimeString(minutes) { const h = Math.floor(minutes / 60) % 24; const m = minutes % 60; return `${h.toString().padStart(2, '0')}:${m.toString().padStart(2, '0')}`; } let maghrib = toMinutes(maghribTime); let fajr = toMinutes(fajrTime); // crossing midnight if (fajr <= maghrib) { fajr += 24 * 60; } const nightDuration = fajr - maghrib; const midNight = maghrib + Math.floor(nightDuration / 2); const lastThird = maghrib + Math.floor(nightDuration * 2 / 3); return { midNight: toTimeString(midNight), lastThirdStart: toTimeString(lastThird) }; } export { PrayerTimes, findCity, findClosestCity, getDbPrayerTime, getDistanceLatLonInKm, getPrayerTime, getPrayerTimeByCity, midNightAndLastThird, parseDate, to12Hour, toradians };