Update prayer time calculation logic and integrate new 'adhan' package for improved accuracy. Remove 'hijri-date' dependency and enhance localization support for city names in multiple languages. Adjust package versions in package.json and package-lock.json.

This commit is contained in:
2025-08-17 19:44:48 +05:00
parent e431c42df1
commit 68aa0cd6fa
6 changed files with 63 additions and 371 deletions

View File

@@ -1,364 +1,53 @@
// PrayTimes-JS: Prayer Times Calculator (ver 2.3)
// https://github.com/abodeo/prayertimes
import { CalculationMethod, Coordinates, PrayerTimes, Qibla } from 'adhan';
//--------------------- 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 Prayer = {
[key: string]: string;
};
type Coordinates = [number, number];
export const getPrayerTimes = (
city: keyof typeof cities,
date: Date = new Date()
): Prayer => {
const { coords, timezone } = cities[city];
const coordinates = new Coordinates(coords[0], coords[1]);
const params = CalculationMethod.UmmAlQura();
const prayerTimes = new PrayerTimes(coordinates, date, params);
type DateComponents = {
year: number;
month: number;
day: number;
};
const formatTime = (time: Date) => {
return time.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: false,
timeZone: timezone,
});
};
type CalculationMethod = {
name: string;
params: {
fajr: number | string;
isha: number | string;
maghrib?: number | string;
midnight?: string;
return {
fajr: formatTime(prayerTimes.fajr),
sunrise: formatTime(prayerTimes.sunrise),
dhuhr: formatTime(prayerTimes.dhuhr),
asr: formatTime(prayerTimes.asr),
maghrib: formatTime(prayerTimes.maghrib),
isha: formatTime(prayerTimes.isha),
};
};
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 getQiblaDirection = (latitude: number, longitude: number): number => {
const coordinates = new Coordinates(latitude, longitude);
return Qibla(coordinates);
};
export const cities = {
Makkah: {
coords: [21.4225, 39.8262] as Coordinates,
timezone: 3,
coords: [21.4225, 39.8262],
timezone: 'Asia/Riyadh',
},
Medina: {
coords: [24.4667, 39.6] as Coordinates,
timezone: 3,
coords: [24.4667, 39.6],
timezone: 'Asia/Riyadh',
},
Jeddah: {
coords: [21.5433, 39.1728] as Coordinates,
timezone: 3,
coords: [21.5433, 39.1728],
timezone: 'Asia/Riyadh',
},
};
export default new PrayTimes('Makkah');