// PrayTimes-JS: Prayer Times Calculator (ver 2.3) // https://github.com/abodeo/prayertimes //--------------------- Copyright Block ---------------------- /* PrayTimes-JS: Prayer Times Calculator (ver 2.3) Copyright (C) 2007-2011 PrayTimes.org JS Code By: Hussain Ali Khan Original JS Code By: Hamid Zarrabi-Zadeh Original C++ Code By: Hamid Zarrabi-Zadeh License: GNU LGPL v3.0 TERMS OF USE: Permission is granted to use this code, with or without modification, in any website or application provided that this copyright block is reproduced in its entirety. This notice must be placed at the beginning of the code block and may not be altered in any way. This notice must remain as part of the code block at all times. */ import HijriDate from 'hijri-date'; type PrayerTimes = { fajr: string; sunrise: string; dhuhr: string; asr: string; sunset: string; maghrib: string; isha: string; imsak?: string; midnight?: string; [key: string]: string | undefined; }; type Coordinates = [number, number]; type DateComponents = { year: number; month: number; day: number; }; type CalculationMethod = { name: string; params: { fajr: number | string; isha: number | string; maghrib?: number | string; midnight?: string; }; }; type TimeNames = | 'imsak' | 'fajr' | 'sunrise' | 'dhuhr' | 'asr' | 'sunset' | 'maghrib' | 'isha' | 'midnight'; class PrayTimes { private lat: number = 0; private lng: number = 0; private elv: number = 0; private timeZone: number = 0; private jDate: number = 0; private calcMethod: string = 'MWL'; private setting: { [key: string]: any } = {}; private methods: { [key: string]: CalculationMethod } = { MWL: { name: 'Muslim World League', params: { fajr: 18, isha: 17 }, }, ISNA: { name: 'Islamic Society of North America (ISNA)', params: { fajr: 15, isha: 15 }, }, Egypt: { name: 'Egyptian General Authority of Survey', params: { fajr: 19.5, isha: 17.5 }, }, Makkah: { name: 'Umm Al-Qura University, Makkah', params: { fajr: 18.5, isha: '90 min' }, }, Karachi: { name: 'University of Islamic Sciences, Karachi', params: { fajr: 18, isha: 18 }, }, Tehran: { name: 'Institute of Geophysics, University of Tehran', params: { fajr: 17.7, isha: 14, maghrib: 4.5, midnight: 'Jafari' }, }, Jafari: { name: 'Shia Ithna-Ashari, Leva Institute, Qum', params: { fajr: 16, isha: 14, maghrib: 4, midnight: 'Jafari' }, }, }; private defaultParams = { maghrib: '0 min', midnight: 'Standard', }; private timeSuffixes: string[] = ['am', 'pm']; private invalidTime: string = '-----'; private numIterations: number = 1; private offset: { [key: string]: number } = {}; private timeNames: { [key in TimeNames]: string } = { imsak: 'Imsak', fajr: 'Fajr', sunrise: 'Sunrise', dhuhr: 'Dhuhr', asr: 'Asr', sunset: 'Sunset', maghrib: 'Maghrib', isha: 'Isha', midnight: 'Midnight', }; constructor(method: string = 'Makkah') { this.setMethod(method); } setMethod(method: string): void { if (this.methods[method]) { this.calcMethod = method; const params = this.methods[method].params; this.setting = { ...this.defaultParams, ...params }; } } getTimes( date: Date, coords: Coordinates, timezone: number, elv: number = 0, format: string = '24h', ): PrayerTimes { this.lat = coords[0]; this.lng = coords[1]; this.elv = elv; this.timeZone = timezone; this.jDate = this.julian(date.getFullYear(), date.getMonth() + 1, date.getDate()) - this.lng / (15 * 24); return this.computeTimes(format); } private julian(year: number, month: number, day: number): number { 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; } private computeTimes(format: string): PrayerTimes { const times: { [key: string]: number } = { imsak: 5, fajr: 5, sunrise: 6, dhuhr: 12, asr: 13, sunset: 18, maghrib: 18, isha: 18, }; const dayPortion = this.sunPosition(this.jDate + 0.5); times.dhuhr = this.midDay(this.jDate); times.sunrise = this.sunAngleTime(this.jDate, this.riseSetAngle(), 'ccw'); times.sunset = this.sunAngleTime(this.jDate, this.riseSetAngle(), 'cw'); times.asr = this.asrTime(this.jDate, 1); times.maghrib = this.sunAngleTime(this.jDate, this.eval(this.setting.maghrib), 'cw'); if (this.calcMethod === 'Makkah') { const gDate = new Date((this.jDate - 2440587.5) * 86400000); const hijriDate = new HijriDate(gDate.getTime()); const isRamadan = hijriDate.getMonth() === 9; // Ramadan is the 9th month in Hijri // For Umm al-Qura, Isha is 90 minutes after Maghrib, 120 during Ramadan times.isha = times.maghrib + (isRamadan ? 2 : 1.5); } else { times.isha = this.sunAngleTime(this.jDate, this.eval(this.setting.isha), 'cw'); } times.fajr = this.sunAngleTime(this.jDate, this.eval(this.setting.fajr), 'ccw'); const result = this.adjustTimes(times); return this.formatTimes(result, format); } private sunPosition(jd: number): [number, number] { 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.02 * this.dSin(2 * g)); const R = 1.00014 - 0.01671 * this.dCos(g) - 0.00014 * this.dCos(2 * g); const e = 23.439 - 0.00000036 * D; const RA = this.dAtan2(this.dCos(e) * this.dSin(L), this.dCos(L)) / 15; const dec = this.dAsin(this.dSin(e) * this.dSin(L)); const EqT = q / 15 - this.fixHour(RA); return [dec, EqT]; } private sunAngleTime(jd: number, angle: number, direction: string): number { const [dec, EqT] = this.sunPosition(jd); const t = (1 / 15) * this.dACos( (-this.dSin(angle) - this.dSin(dec) * this.dSin(this.lat)) / (this.dCos(dec) * this.dCos(this.lat)), ); return this.midDay(jd) + (direction === 'cw' ? t : -t) - EqT; } private asrTime(jd: number, factor: number): number { const [dec] = this.sunPosition(jd); const angle = -this.dACot(factor + this.dTan(Math.abs(dec - this.lat))); return this.sunAngleTime(jd, angle, 'cw'); } private riseSetAngle(): number { return 0.833 + 0.0347 * Math.sqrt(this.elv); } private midDay(jd: number): number { const [, EqT] = this.sunPosition(jd); return 12 - EqT; } private adjustTimes(times: { [key: string]: number }): { [key: string]: number } { const adjustedTimes = { ...times }; for (const i in adjustedTimes) { adjustedTimes[i] += this.timeZone - this.lng / 15; } return adjustedTimes; } private formatTimes(times: { [key: string]: number }, format: string): PrayerTimes { const formattedTimes: PrayerTimes = {} as PrayerTimes; for (const i in times) { if (format === '24h') { formattedTimes[i] = this.floatToTime24(times[i]); } else { formattedTimes[i] = this.floatToTime12(times[i]); } } return formattedTimes; } private floatToTime24(time: number): string { if (isNaN(time)) { return this.invalidTime; } time = this.fixHour(time + 0.5 / 60); // add 0.5 minutes to round const hours = Math.floor(time); const minutes = Math.floor((time - hours) * 60); return this.twoDigitsFormat(hours) + ':' + this.twoDigitsFormat(minutes); } private floatToTime12(time: number, noSuffix: boolean = false): string { if (isNaN(time)) { return this.invalidTime; } time = this.fixHour(time + 0.5 / 60); // add 0.5 minutes to round let hours = Math.floor(time); const minutes = Math.floor((time - hours) * 60); const suffix = hours >= 12 ? 'pm' : 'am'; hours = (hours + 12 - 1) % 12 + 1; return hours + ':' + this.twoDigitsFormat(minutes) + (noSuffix ? '' : ' ' + suffix); } private twoDigitsFormat(num: number): string { return num < 10 ? '0' + num : num.toString(); } private eval(str: string | number | undefined): number { if (str === undefined) { return 0; } if (typeof str === 'number') { return str; } const [value, unit] = str.split(' '); return unit === 'min' ? parseInt(value) / 60 : parseInt(value); } private dSin(d: number): number { return Math.sin(this.dToR(d)); } private dCos(d: number): number { return Math.cos(this.dToR(d)); } private dTan(d: number): number { return Math.tan(this.dToR(d)); } private dAsin(d: number): number { return this.rToD(Math.asin(d)); } private dACos(d: number): number { return this.rToD(Math.acos(d)); } private dAtan(d: number): number { return this.rToD(Math.atan(d)); } private dAtan2(y: number, x: number): number { return this.rToD(Math.atan2(y, x)); } private dACot(x: number): number { return this.rToD(Math.atan(1 / x)); } private dToR(d: number): number { return (d * Math.PI) / 180.0; } private rToD(r: number): number { return (r * 180.0) / Math.PI; } private fixAngle(a: number): number { a = a - 360.0 * Math.floor(a / 360.0); return a < 0 ? a + 360.0 : a; } private fixHour(a: number): number { a = a - 24.0 * Math.floor(a / 24.0); return a < 0 ? a + 24.0 : a; } } export const cities = { Makkah: { coords: [21.4225, 39.8262] as Coordinates, timezone: 3, }, Medina: { coords: [24.4667, 39.6] as Coordinates, timezone: 3, }, Jeddah: { coords: [21.5433, 39.1728] as Coordinates, timezone: 3, }, }; export default new PrayTimes('Makkah');