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:
@@ -1,12 +1,11 @@
|
|||||||
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
import FontAwesome from '@expo/vector-icons/FontAwesome';
|
||||||
import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';
|
import { DarkTheme, ThemeProvider } from '@react-navigation/native';
|
||||||
import { useFonts } from 'expo-font';
|
import { useFonts } from 'expo-font';
|
||||||
import { Stack } from 'expo-router';
|
import { Stack } from 'expo-router';
|
||||||
import * as SplashScreen from 'expo-splash-screen';
|
import * as SplashScreen from 'expo-splash-screen';
|
||||||
import React, { useEffect } from 'react';
|
import React, { useEffect } from 'react';
|
||||||
import 'react-native-reanimated';
|
import 'react-native-reanimated';
|
||||||
|
|
||||||
import { useColorScheme } from '@/components/useColorScheme';
|
|
||||||
import { initializeLanguage } from '@/i18n';
|
import { initializeLanguage } from '@/i18n';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
@@ -55,10 +54,8 @@ export default function RootLayout() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function RootLayoutNav() {
|
function RootLayoutNav() {
|
||||||
const colorScheme = useColorScheme();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
|
<ThemeProvider value={DarkTheme}>
|
||||||
<Stack>
|
<Stack>
|
||||||
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
|
||||||
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
|
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { View, Text, StyleSheet, ImageBackground, Pressable } from 'react-native';
|
import { View, Text, StyleSheet, ImageBackground, Pressable } from 'react-native';
|
||||||
import Colors from '@/constants/Colors';
|
import Colors from '@/constants/Colors';
|
||||||
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
|
||||||
import prayerTimeCalculator, { cities } from '@/utils/prayerTimeCalculator';
|
import { getPrayerTimes, cities } from '@/utils/prayerTimeCalculator';
|
||||||
import i18n from '@/i18n';
|
import i18n from '@/i18n';
|
||||||
|
|
||||||
type Prayer = {
|
type Prayer = {
|
||||||
@@ -24,14 +24,12 @@ export default function PrayerTimeCard() {
|
|||||||
const [prayerTimes, setPrayerTimes] = useState<Prayer[]>([]);
|
const [prayerTimes, setPrayerTimes] = useState<Prayer[]>([]);
|
||||||
const [nextPrayer, setNextPrayer] = useState<{ name: string; time: string } | null>(null);
|
const [nextPrayer, setNextPrayer] = useState<{ name: string; time: string } | null>(null);
|
||||||
const [remainingTime, setRemainingTime] = useState('');
|
const [remainingTime, setRemainingTime] = useState('');
|
||||||
const [selectedCity, setSelectedCity] = useState<'Makkah' | 'Medina' | 'Jeddah'>('Makkah');
|
const [selectedCity, setSelectedCity] = useState<keyof typeof cities>('Makkah');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const city = cities[selectedCity];
|
const times = getPrayerTimes(selectedCity);
|
||||||
const times = prayerTimeCalculator.getTimes(new Date(), city.coords, city.timezone, 0, '24h');
|
|
||||||
|
|
||||||
const prayers: Prayer[] = Object.keys(prayerIconMapping).map(key => ({
|
const prayers: Prayer[] = Object.keys(prayerIconMapping).map(key => ({
|
||||||
name: key.charAt(0).toUpperCase() + key.slice(1),
|
name: i18n.t(key),
|
||||||
time: times[key] || '-----',
|
time: times[key] || '-----',
|
||||||
icon: prayerIconMapping[key],
|
icon: prayerIconMapping[key],
|
||||||
}));
|
}));
|
||||||
@@ -56,7 +54,6 @@ export default function PrayerTimeCard() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
setNextPrayer(nextPrayerInfo);
|
setNextPrayer(nextPrayerInfo);
|
||||||
|
|
||||||
}, [selectedCity]);
|
}, [selectedCity]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -90,9 +87,9 @@ export default function PrayerTimeCard() {
|
|||||||
<View style={styles.overlay}>
|
<View style={styles.overlay}>
|
||||||
<View style={styles.header}>
|
<View style={styles.header}>
|
||||||
<View style={styles.citySelector}>
|
<View style={styles.citySelector}>
|
||||||
{Object.keys(cities).map(city => (
|
{(Object.keys(cities) as Array<keyof typeof cities>).map(city => (
|
||||||
<Pressable key={city} onPress={() => setSelectedCity(city as any)} style={[styles.cityButton, selectedCity === city && styles.activeCity]}>
|
<Pressable key={city} onPress={() => setSelectedCity(city)} style={[styles.cityButton, selectedCity === city && styles.activeCity]}>
|
||||||
<Text style={styles.cityButtonText}>{city}</Text>
|
<Text style={styles.cityButtonText}>{i18n.t(city)}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
))}
|
))}
|
||||||
</View>
|
</View>
|
||||||
|
|||||||
@@ -15,6 +15,15 @@
|
|||||||
"dua": "Doga",
|
"dua": "Doga",
|
||||||
"adhkar": "Zikir",
|
"adhkar": "Zikir",
|
||||||
"hisnAlMuslim": "Musulmanyň goragy",
|
"hisnAlMuslim": "Musulmanyň goragy",
|
||||||
|
"Makkah": "Mekke",
|
||||||
|
"Medina": "Medine",
|
||||||
|
"Jeddah": "Jidda",
|
||||||
|
"fajr": "Ertir",
|
||||||
|
"sunrise": "Gün",
|
||||||
|
"dhuhr": "Öýle",
|
||||||
|
"asr": "Ikindi",
|
||||||
|
"maghrib": "Agşam",
|
||||||
|
"isha": "Ýatsy",
|
||||||
"morningEveningThikr": "Ertir we agşam aýdylýan zikir",
|
"morningEveningThikr": "Ertir we agşam aýdylýan zikir",
|
||||||
"beforeSleepingThikr": "Uklamazdan öňki zikir",
|
"beforeSleepingThikr": "Uklamazdan öňki zikir",
|
||||||
"afterSalamThikr": "Salamdan soňky zikir",
|
"afterSalamThikr": "Salamdan soňky zikir",
|
||||||
|
|||||||
12
package-lock.json
generated
12
package-lock.json
generated
@@ -11,6 +11,7 @@
|
|||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
"@react-native-async-storage/async-storage": "2.1.2",
|
"@react-native-async-storage/async-storage": "2.1.2",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
|
"adhan": "^4.4.3",
|
||||||
"expo": "~53.0.20",
|
"expo": "~53.0.20",
|
||||||
"expo-font": "~13.3.2",
|
"expo-font": "~13.3.2",
|
||||||
"expo-linking": "~7.1.7",
|
"expo-linking": "~7.1.7",
|
||||||
@@ -21,7 +22,6 @@
|
|||||||
"expo-system-ui": "~5.0.10",
|
"expo-system-ui": "~5.0.10",
|
||||||
"expo-updates": "~0.28.17",
|
"expo-updates": "~0.28.17",
|
||||||
"expo-web-browser": "~14.2.0",
|
"expo-web-browser": "~14.2.0",
|
||||||
"hijri-date": "^0.2.2",
|
|
||||||
"i18n-js": "^4.5.1",
|
"i18n-js": "^4.5.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
@@ -3328,6 +3328,11 @@
|
|||||||
"node": ">=0.4.0"
|
"node": ">=0.4.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/adhan": {
|
||||||
|
"version": "4.4.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/adhan/-/adhan-4.4.3.tgz",
|
||||||
|
"integrity": "sha512-568KkQd8OMLUj7o7+d2FDcm6vZHWQrE7vsm/Evssh8sfUDpPyaboj3PVsScZAr7L7sNRgPrtLMmDZZfM7VeAYw=="
|
||||||
|
},
|
||||||
"node_modules/agent-base": {
|
"node_modules/agent-base": {
|
||||||
"version": "6.0.2",
|
"version": "6.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
|
||||||
@@ -5666,11 +5671,6 @@
|
|||||||
"hermes-estree": "0.25.1"
|
"hermes-estree": "0.25.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/hijri-date": {
|
|
||||||
"version": "0.2.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/hijri-date/-/hijri-date-0.2.2.tgz",
|
|
||||||
"integrity": "sha512-LiuslQTPqePwh/KguBB2KH/hzISQd7nQtfVPwdCKgfFv0GDzwjIeTQX/LkBKLI8rPeuXasr3sQMuJL35g8HEAw=="
|
|
||||||
},
|
|
||||||
"node_modules/hosted-git-info": {
|
"node_modules/hosted-git-info": {
|
||||||
"version": "7.0.2",
|
"version": "7.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-7.0.2.tgz",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@expo/vector-icons": "^14.1.0",
|
"@expo/vector-icons": "^14.1.0",
|
||||||
"@react-native-async-storage/async-storage": "2.1.2",
|
"@react-native-async-storage/async-storage": "2.1.2",
|
||||||
"@react-navigation/native": "^7.1.6",
|
"@react-navigation/native": "^7.1.6",
|
||||||
|
"adhan": "^4.4.3",
|
||||||
"expo": "~53.0.20",
|
"expo": "~53.0.20",
|
||||||
"expo-font": "~13.3.2",
|
"expo-font": "~13.3.2",
|
||||||
"expo-linking": "~7.1.7",
|
"expo-linking": "~7.1.7",
|
||||||
@@ -26,7 +27,6 @@
|
|||||||
"expo-system-ui": "~5.0.10",
|
"expo-system-ui": "~5.0.10",
|
||||||
"expo-updates": "~0.28.17",
|
"expo-updates": "~0.28.17",
|
||||||
"expo-web-browser": "~14.2.0",
|
"expo-web-browser": "~14.2.0",
|
||||||
"hijri-date": "^0.2.2",
|
|
||||||
"i18n-js": "^4.5.1",
|
"i18n-js": "^4.5.1",
|
||||||
"react": "19.0.0",
|
"react": "19.0.0",
|
||||||
"react-dom": "19.0.0",
|
"react-dom": "19.0.0",
|
||||||
@@ -36,10 +36,10 @@
|
|||||||
"react-native-safe-area-context": "5.4.0",
|
"react-native-safe-area-context": "5.4.0",
|
||||||
"react-native-screens": "~4.11.1",
|
"react-native-screens": "~4.11.1",
|
||||||
"react-native-vector-icons": "^10.3.0",
|
"react-native-vector-icons": "^10.3.0",
|
||||||
"react-native-web": "~0.20.0"
|
"react-native-web": "~0.19.6"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.25.2",
|
"@babel/core": "^7.20.0",
|
||||||
"@types/react": "~19.0.10",
|
"@types/react": "~19.0.10",
|
||||||
"@types/react-native-vector-icons": "^6.4.18",
|
"@types/react-native-vector-icons": "^6.4.18",
|
||||||
"jest": "^29.2.1",
|
"jest": "^29.2.1",
|
||||||
|
|||||||
@@ -1,364 +1,53 @@
|
|||||||
// PrayTimes-JS: Prayer Times Calculator (ver 2.3)
|
import { CalculationMethod, Coordinates, PrayerTimes, Qibla } from 'adhan';
|
||||||
// https://github.com/abodeo/prayertimes
|
|
||||||
|
|
||||||
//--------------------- Copyright Block ----------------------
|
type Prayer = {
|
||||||
/*
|
[key: string]: string;
|
||||||
|
|
||||||
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];
|
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 = {
|
const formatTime = (time: Date) => {
|
||||||
year: number;
|
return time.toLocaleTimeString('en-US', {
|
||||||
month: number;
|
hour: '2-digit',
|
||||||
day: number;
|
minute: '2-digit',
|
||||||
};
|
hour12: false,
|
||||||
|
timeZone: timezone,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
type CalculationMethod = {
|
return {
|
||||||
name: string;
|
fajr: formatTime(prayerTimes.fajr),
|
||||||
params: {
|
sunrise: formatTime(prayerTimes.sunrise),
|
||||||
fajr: number | string;
|
dhuhr: formatTime(prayerTimes.dhuhr),
|
||||||
isha: number | string;
|
asr: formatTime(prayerTimes.asr),
|
||||||
maghrib?: number | string;
|
maghrib: formatTime(prayerTimes.maghrib),
|
||||||
midnight?: string;
|
isha: formatTime(prayerTimes.isha),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
type TimeNames =
|
export const getQiblaDirection = (latitude: number, longitude: number): number => {
|
||||||
| 'imsak'
|
const coordinates = new Coordinates(latitude, longitude);
|
||||||
| 'fajr'
|
return Qibla(coordinates);
|
||||||
| '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 = {
|
export const cities = {
|
||||||
Makkah: {
|
Makkah: {
|
||||||
coords: [21.4225, 39.8262] as Coordinates,
|
coords: [21.4225, 39.8262],
|
||||||
timezone: 3,
|
timezone: 'Asia/Riyadh',
|
||||||
},
|
},
|
||||||
Medina: {
|
Medina: {
|
||||||
coords: [24.4667, 39.6] as Coordinates,
|
coords: [24.4667, 39.6],
|
||||||
timezone: 3,
|
timezone: 'Asia/Riyadh',
|
||||||
},
|
},
|
||||||
Jeddah: {
|
Jeddah: {
|
||||||
coords: [21.5433, 39.1728] as Coordinates,
|
coords: [21.5433, 39.1728],
|
||||||
timezone: 3,
|
timezone: 'Asia/Riyadh',
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default new PrayTimes('Makkah');
|
|
||||||
|
|||||||
Reference in New Issue
Block a user