UNPKG

praytime

Version:

A JavaScript library for calculating prayer times.

398 lines (320 loc) 12.2 kB
// praytime.js - Prayer Times Calculator (v3.2) // Copyright (c) 2007-2025 Hamid Zarrabi-Zadeh // Source: https://praytimes.org // License: MIT //------------------------- User Interface ------------------------ /* method(method) // set calculation method location(coordinates) // set location timezone(timezone) // set timezone utcOffset(number) // set UTC offset in minutes or hours adjust(parameters) // adjust calculation parameters tune(mins) // tune times by given minutes format(format) // options: 24h, 12h, 12H, x, X round(method) // options: nearest, up, down, none getTimes(date) // options: date, array, timestamp //------------------------- Sample Usage -------------------------- const praytime = new PrayTime('ISNA'); praytime.location([43, -80]).timezone('America/Toronto'); praytime.getTimes(); */ //------------------------- PrayTime Class ------------------------ class PrayTime { constructor(method) { this.methods = { MWL: { fajr: 18, isha: 17 }, ISNA: { fajr: 15, isha: 15 }, Egypt: { fajr: 19.5, isha: 17.5 }, Makkah: { fajr: 18.5, isha: '90 min' }, Karachi: { fajr: 18, isha: 18 }, Tehran: { fajr: 17.7, maghrib: 4.5, midnight: 'Jafari' }, Jafari: { fajr: 16, maghrib: 4, midnight: 'Jafari' }, France: { fajr: 12, isha: 12 }, Russia: { fajr: 16, isha: 15 }, Singapore: { fajr: 20, isha: 18 }, defaults: { isha: 14, maghrib: '1 min', midnight: 'Standard' } }; this.settings = { dhuhr: '0 min', asr: 'Standard', highLats: 'NightMiddle', tune: {}, format: '24h', rounding: 'nearest', utcOffset: 'auto', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone, location: [0, -(new Date()).getTimezoneOffset() / 4], iterations: 1 }; this.labels = [ 'Fajr', 'Sunrise', 'Dhuhr', 'Asr', 'Sunset', 'Maghrib', 'Isha', 'Midnight' ]; this.method(method || 'MWL'); } //---------------------- Setters ------------------------ // set calculation method method(method) { return this.set(this.methods.defaults).set(this.methods[method]); } // set calculating parameters adjust(params) { return this.set(params); } // set location location(location) { return this.set({ location }); } // set timezone timezone(timezone) { return this.set({ timezone }); } // set tuning minutes tune(tune) { return this.set({ tune }); } // set rounding method round(rounding = 'nearest') { return this.set({ rounding }); } // set time format format(format) { return this.set({ format }); } // set settings parameters set(settings) { Object.assign(this.settings, settings); return this; } // set utc offset utcOffset(utcOffset = 'auto') { if (typeof utcOffset === 'number' && Math.abs(utcOffset) < 16) utcOffset *= 60; this.set({ timezone: 'UTC' }); return this.set({ utcOffset }); } //---------------------- Getters ------------------------ // get prayer times times(date = 0) { if (typeof date === 'number') date = new Date((date < 1000) ? Date.now() + date * 864e5 : date); if (date.constructor === Date) date = [date.getFullYear(), date.getMonth() + 1, date.getDate()]; this.utcTime = Date.UTC(date[0], date[1] - 1, date[2]); let times = this.computeTimes(); this.formatTimes(times); return times; } // get prayer times (backward compatible) getTimes(date, location, timezone = 'auto', dst = 0, format = '24h') { if (!location) return this.times(date); const utcOffset = (timezone == 'auto') ? timezone : timezone + dst; this.location(location).utcOffset(utcOffset).format(format); return this.times(date); } //---------------------- Deprecated ------------------------- // deprecated: set calculation method setMethod(method) { this.method(method); } //---------------------- Compute Times ----------------------- // compute prayer times computeTimes() { let times = { fajr: 5, sunrise: 6, dhuhr: 12, asr: 13, sunset: 18, maghrib: 18, isha: 18, midnight: 24 }; for (let i = 0; i < this.settings.iterations; i++) times = this.processTimes(times); this.adjustHighLats(times); this.updateTimes(times); this.tuneTimes(times); this.convertTimes(times); return times; } // process prayer times processTimes(times) { const params = this.settings; const horizon = 0.833; return { fajr: this.angleTime(params.fajr, times.fajr, -1), sunrise: this.angleTime(horizon, times.sunrise, -1), dhuhr: this.midDay(times.dhuhr), asr: this.angleTime(this.asrAngle(params.asr, times.asr), times.asr), sunset: this.angleTime(horizon, times.sunset), maghrib: this.angleTime(params.maghrib, times.maghrib), isha: this.angleTime(params.isha, times.isha), midnight: this.midDay(times.midnight) + 12 } } // update times updateTimes(times) { const params = this.settings; if (this.isMin(params.maghrib)) times.maghrib = times.sunset + this.value(params.maghrib) / 60; if (this.isMin(params.isha)) times.isha = times.maghrib + this.value(params.isha) / 60; if (params.midnight == 'Jafari') { const nextFajr = this.angleTime(params.fajr, 29, -1) + 24; times.midnight = (times.sunset + (this.adjusted ? times.fajr + 24 : nextFajr)) / 2; } times.dhuhr += this.value(params.dhuhr) / 60; } // tune times tuneTimes(times) { const mins = this.settings.tune for (let i in times) if (i in mins) times[i] += mins[i] / 60; } // convert times convertTimes(times) { const lng = this.settings.location[1]; for (let i in times) { const time = times[i] - lng / 15; const timestamp = this.utcTime + Math.floor(time * 36e5); times[i] = this.roundTime(timestamp); } } // round time roundTime(timestamp) { const rounding = { up: 'ceil', down: 'floor', nearest: 'round' }[this.settings.rounding]; if (!rounding) return timestamp; const OneMinute = 6e4; return Math[rounding](timestamp / OneMinute) * OneMinute; } //---------------------- Calculation Functions ----------------------- // compute sun position sunPosition(time) { const lng = this.settings.location[1]; const D = this.utcTime / 864e5 - 10957.5 + this.value(time) / 24 - lng / 360; const g = this.mod(357.529 + 0.98560028 * D, 360); const q = this.mod(280.459 + 0.98564736 * D, 360); const L = this.mod(q + 1.915 * this.sin(g) + 0.020 * this.sin(2 * g), 360); const e = 23.439 - 0.00000036 * D; const RA = this.mod(this.arctan2(this.cos(e) * this.sin(L), this.cos(L)) / 15, 24); return { declination: this.arcsin(this.sin(e) * this.sin(L)), equation: q / 15 - RA, } } // compute mid-day midDay(time) { const eqt = this.sunPosition(time).equation; const noon = this.mod(12 - eqt, 24); return noon; } // compute the time when sun reaches a specific angle below horizon angleTime(angle, time, direction = 1) { const lat = this.settings.location[0]; const decl = this.sunPosition(time).declination; const numerator = -this.sin(angle) - this.sin(lat) * this.sin(decl); const diff = this.arccos(numerator / (this.cos(lat) * this.cos(decl))) / 15; return this.midDay(time) + diff * direction; } // compute asr angle asrAngle(asrParam, time) { const shadowFactor = { Standard: 1, Hanafi: 2 }[asrParam] || this.value(asrParam); const lat = this.settings.location[0]; const decl = this.sunPosition(time).declination; return -this.arccot(shadowFactor + this.tan(Math.abs(lat - decl))); } //---------------------- Higher Latitudes ----------------------- // adjust times for higher latitudes adjustHighLats(times) { const params = this.settings; if (params.highLats == 'None') return; this.adjusted = false; const night = 24 + times.sunrise - times.sunset; Object.assign(times, { fajr: this.adjustTime(times.fajr, times.sunrise, params.fajr, night, -1), isha: this.adjustTime(times.isha, times.sunset, params.isha, night), maghrib: this.adjustTime(times.maghrib, times.sunset, params.maghrib, night) }); } // adjust time in higher latitudes adjustTime(time, base, angle, night, direction = 1) { const factors = { NightMiddle: 1 / 2, OneSeventh: 1 / 7, AngleBased: 1 / 60 * this.value(angle) }; const portion = factors[this.settings.highLats] * night; const timeDiff = (time - base) * direction; if (isNaN(time) || timeDiff > portion) { time = base + portion * direction; this.adjusted = true; } return time; } //---------------------- Formatting Functions --------------------- // format times formatTimes(times) { for (let i in times) times[i] = this.formatTime(times[i]); } // format time formatTime(timestamp) { const format = this.settings.format; const InvalidTime = '-----'; if (isNaN(timestamp)) return InvalidTime; if (typeof format === 'function') return format(timestamp); if (format.toLowerCase() == 'x') return Math.floor(timestamp / ((format == 'X') ? 1000 : 1)); return this.timeToString(timestamp, format); } // convert time to string timeToString(timestamp, format) { const utcOffset = this.settings.utcOffset; const date = new Date(timestamp + (utcOffset == 'auto' ? 0 : utcOffset) * 6e4); const str = date.toLocaleTimeString('en-US', { timeZone: this.settings.timezone, hour12: format == '24h' ? false : true, hour: format == '24h' ? '2-digit' : 'numeric', minute: '2-digit' }); return format == '12H' ? str.replace(/ ?[AP]M/, '') : str; } //---------------------- Misc Functions ----------------------- // convert string to number value(str) { return +String(str).split(/[^0-9.+-]/)[0]; } // detect if input contains 'min' isMin(str) { return String(str).indexOf('min') != -1; } // positive modulo mod(a, b) { return ((a % b) + b) % b; } //--------------------- Degree-Based Trigonometry ----------------- dtr = (d) => d * Math.PI / 180; rtd = (r) => r * 180 / Math.PI; sin = (d) => Math.sin(this.dtr(d)); cos = (d) => Math.cos(this.dtr(d)); tan = (d) => Math.tan(this.dtr(d)); arcsin = (d) => this.rtd(Math.asin(d)); arccos = (d) => this.rtd(Math.acos(d)); arctan = (d) => this.rtd(Math.atan(d)); arccot = (x) => this.rtd(Math.atan(1 / x)); arctan2 = (y, x) => this.rtd(Math.atan2(y, x)); } //------------------------- Export ------------------------ if (typeof module !== 'undefined' && module.exports) { module.exports = { PrayTime }; }