falakpy 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- falakpy-0.1.0/PKG-INFO +16 -0
- falakpy-0.1.0/pyproject.toml +31 -0
- falakpy-0.1.0/setup.cfg +4 -0
- falakpy-0.1.0/src/falakpy/__init__.py +12 -0
- falakpy-0.1.0/src/falakpy/lunar.py +126 -0
- falakpy-0.1.0/src/falakpy/prayertime.py +514 -0
- falakpy-0.1.0/src/falakpy/qibla.py +88 -0
- falakpy-0.1.0/src/falakpy.egg-info/PKG-INFO +16 -0
- falakpy-0.1.0/src/falakpy.egg-info/SOURCES.txt +10 -0
- falakpy-0.1.0/src/falakpy.egg-info/dependency_links.txt +1 -0
- falakpy-0.1.0/src/falakpy.egg-info/requires.txt +1 -0
- falakpy-0.1.0/src/falakpy.egg-info/top_level.txt +1 -0
falakpy-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: falakpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Small Package Tool for Islamic Astronomy Related Computation and Visualization.
|
|
5
|
+
Author-email: Muhamad Syazwan Faid <syazwaned90@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/msyazwanfaid/falakpy
|
|
8
|
+
Project-URL: Issues, https://github.com/msyazwanfaid/falakpy/issues
|
|
9
|
+
Keywords: Islamic Astronomy,qibla,prayer time,hijri calendar
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: skyfield
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=69", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "falakpy"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Small Package Tool for Islamic Astronomy Related Computation and Visualization."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [{ name = "Muhamad Syazwan Faid", email = "syazwaned90@gmail.com" }]
|
|
13
|
+
keywords = ["Islamic Astronomy", "qibla", "prayer time", "hijri calendar"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Programming Language :: Python :: 3",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Topic :: Software Development :: Libraries",
|
|
18
|
+
"Typing :: Typed"
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
dependencies = ["skyfield"] # keep it stdlib-only for the example
|
|
22
|
+
|
|
23
|
+
[project.urls]
|
|
24
|
+
Homepage = "https://github.com/msyazwanfaid/falakpy"
|
|
25
|
+
Issues = "https://github.com/msyazwanfaid/falakpy/issues"
|
|
26
|
+
|
|
27
|
+
[tool.setuptools]
|
|
28
|
+
package-dir = {"" = "src"}
|
|
29
|
+
|
|
30
|
+
[tool.setuptools.packages.find]
|
|
31
|
+
where = ["src"]
|
falakpy-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""falakpy package initializer.
|
|
3
|
+
|
|
4
|
+
Provides modules for:
|
|
5
|
+
- qibla direction computation
|
|
6
|
+
- prayer time calculation
|
|
7
|
+
- lunar crescent observation
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from . import qibla, prayertime, lunar
|
|
11
|
+
|
|
12
|
+
__all__ = ["qibla", "prayertime", "lunar"]
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
def observedata(lat, lon, ele, tz, year, m, d, csv_filename="crescent_data.csv"):
|
|
2
|
+
from datetime import date, timedelta
|
|
3
|
+
import csv
|
|
4
|
+
from skyfield.api import load, wgs84
|
|
5
|
+
from skyfield import almanac
|
|
6
|
+
from skyfield.units import Angle
|
|
7
|
+
from skyfield.earthlib import refraction
|
|
8
|
+
from numpy import arccos
|
|
9
|
+
|
|
10
|
+
ts = load.timescale()
|
|
11
|
+
eph = load('de440s.bsp')
|
|
12
|
+
earth, sun, moon = eph['earth'], eph['sun'], eph['moon']
|
|
13
|
+
|
|
14
|
+
# Observer (works across Skyfield versions)
|
|
15
|
+
try:
|
|
16
|
+
location = wgs84.latlon(lat, lon, elevation_m=ele, center=earth)
|
|
17
|
+
except TypeError:
|
|
18
|
+
location = earth + wgs84.latlon(lat, lon, elevation_m=ele)
|
|
19
|
+
|
|
20
|
+
# Local-day window in UTC
|
|
21
|
+
t0 = ts.utc(year, m, d, 0 - tz, 0, 0)
|
|
22
|
+
t1 = ts.utc(year, m, d, 24 - tz, 0, 0)
|
|
23
|
+
|
|
24
|
+
# Apparent horizon for upper-limb sunset, with height & refraction
|
|
25
|
+
earth_radius_m = 6378136.6
|
|
26
|
+
side_over_hyp = earth_radius_m / (earth_radius_m + ele)
|
|
27
|
+
h_geom = Angle(radians=-arccos(side_over_hyp)) # dip from observer height
|
|
28
|
+
r = refraction(0.0, temperature_C=15.0, pressure_mbar=1013.25)
|
|
29
|
+
solar_radius_deg = 16/60
|
|
30
|
+
horizon_deg = -r + h_geom.degrees - solar_radius_deg
|
|
31
|
+
|
|
32
|
+
# Sunset within the local-day window
|
|
33
|
+
set_times_sun, _ = almanac.find_settings(location, sun, t0, t1, horizon_degrees=horizon_deg)
|
|
34
|
+
if len(set_times_sun) == 0:
|
|
35
|
+
raise RuntimeError("No sunset found in this local-day window.")
|
|
36
|
+
t_sunset = set_times_sun[0]
|
|
37
|
+
dt_sunset_local = t_sunset.utc_datetime() + timedelta(hours=tz)
|
|
38
|
+
sunset_str = dt_sunset_local.strftime("%H:%M:%S")
|
|
39
|
+
|
|
40
|
+
# Moonset within the local-day window (may be missing)
|
|
41
|
+
set_times_moon, _ = almanac.find_settings(location, moon, t0, t1, horizon_degrees=horizon_deg)
|
|
42
|
+
if len(set_times_moon) == 0:
|
|
43
|
+
t_moonset = None
|
|
44
|
+
moonset_str = "—"
|
|
45
|
+
lag_str = "—"
|
|
46
|
+
else:
|
|
47
|
+
t_moonset = set_times_moon[0]
|
|
48
|
+
dt_moonset_local = t_moonset.utc_datetime() + timedelta(hours=tz)
|
|
49
|
+
moonset_str = dt_moonset_local.strftime("%H:%M:%S")
|
|
50
|
+
|
|
51
|
+
# Lag = moonset - sunset (ensure positive same-evening)
|
|
52
|
+
lag_td = (t_moonset.utc_datetime() - t_sunset.utc_datetime())
|
|
53
|
+
if lag_td.total_seconds() < 0:
|
|
54
|
+
lag_td += timedelta(days=1)
|
|
55
|
+
hh = int(lag_td.total_seconds() // 3600)
|
|
56
|
+
mm = int((lag_td.total_seconds() % 3600) // 60)
|
|
57
|
+
ss = int(lag_td.total_seconds() % 60)
|
|
58
|
+
lag_str = f"{hh:02d}:{mm:02d}:{ss:02d}"
|
|
59
|
+
|
|
60
|
+
# Alt/Az at actual sunset
|
|
61
|
+
alt_moon, az_moon, _ = location.at(t_sunset).observe(moon).apparent().altaz()
|
|
62
|
+
alt_sun, az_sun, _ = location.at(t_sunset).observe(sun).apparent().altaz()
|
|
63
|
+
|
|
64
|
+
moon_alt_deg = float(alt_moon.degrees)
|
|
65
|
+
daz_deg = abs(float(az_moon.degrees) - float(az_sun.degrees))
|
|
66
|
+
arcv_deg = abs(moon_alt_deg - float(alt_sun.degrees)) # Arc of Vision
|
|
67
|
+
arcl_deg = float(
|
|
68
|
+
location.at(t_sunset).observe(sun).apparent()
|
|
69
|
+
.separation_from(location.at(t_sunset).observe(moon).apparent()).degrees
|
|
70
|
+
) # Arc of Light
|
|
71
|
+
|
|
72
|
+
# Moon age at sunset: (JD_sunset - JD_conjunction_before)*24
|
|
73
|
+
jd_sunset = t_sunset.tt
|
|
74
|
+
# Search ±5 days around date; pick last conjunction BEFORE sunset
|
|
75
|
+
t0c = ts.utc(year, m, d - 5)
|
|
76
|
+
t1c = ts.utc(year, m, d + 5)
|
|
77
|
+
oc_func = almanac.oppositions_conjunctions(eph, eph['Moon'])
|
|
78
|
+
times_oc, events_oc = almanac.find_discrete(t0c, t1c, oc_func)
|
|
79
|
+
jd_conj = None
|
|
80
|
+
for ti, ei in zip(times_oc, events_oc):
|
|
81
|
+
if ei == 1 and ti.tt <= jd_sunset: # 1 = conjunction
|
|
82
|
+
jd_conj = ti.tt
|
|
83
|
+
moon_age_hours = (jd_sunset - jd_conj) * 24.0 if jd_conj is not None else float('nan')
|
|
84
|
+
|
|
85
|
+
# Build row
|
|
86
|
+
date_str = date(year, m, d).strftime("%Y-%m-%d")
|
|
87
|
+
header = ["Date", "Sunset", "Moonset", "Lag Time", "Moon Age", "Moon Alt", "DAZ", "ArcV", "ArcL"]
|
|
88
|
+
row = [
|
|
89
|
+
date_str,
|
|
90
|
+
sunset_str,
|
|
91
|
+
moonset_str,
|
|
92
|
+
lag_str,
|
|
93
|
+
f"{moon_age_hours:.2f}",
|
|
94
|
+
f"{moon_alt_deg:.2f}",
|
|
95
|
+
f"{daz_deg:.2f}",
|
|
96
|
+
f"{arcv_deg:.2f}",
|
|
97
|
+
f"{arcl_deg:.2f}",
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# Pretty print
|
|
101
|
+
print("\n+------------+----------+----------+----------+----------+----------+----------+----------+----------+")
|
|
102
|
+
print("| Date | Sunset | Moonset | LagTime | MoonAge | MoonAlt | DAZ | ArcV | ArcL |")
|
|
103
|
+
print("+------------+----------+----------+----------+----------+----------+----------+----------+----------+")
|
|
104
|
+
print(f"| {date_str} | {sunset_str:8} | {moonset_str:8} | {lag_str:8} | "
|
|
105
|
+
f"{moon_age_hours:8.2f} | {moon_alt_deg:8.2f} | {daz_deg:8.2f} | {arcv_deg:8.2f} | {arcl_deg:8.2f} |")
|
|
106
|
+
print("+------------+----------+----------+----------+----------+----------+----------+----------+----------+")
|
|
107
|
+
|
|
108
|
+
# Save CSV
|
|
109
|
+
with open(csv_filename, "w", newline="") as f:
|
|
110
|
+
writer = csv.writer(f)
|
|
111
|
+
writer.writerow(header)
|
|
112
|
+
writer.writerow(row)
|
|
113
|
+
|
|
114
|
+
print(f"\n✅ Saved to CSV file: {csv_filename}")
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
"Date": date_str,
|
|
118
|
+
"Sunset": sunset_str,
|
|
119
|
+
"Moonset": moonset_str,
|
|
120
|
+
"Lag Time": lag_str,
|
|
121
|
+
"Moon Age (h)": f"{moon_age_hours:.2f}",
|
|
122
|
+
"Moon Alt (deg)": f"{moon_alt_deg:.2f}",
|
|
123
|
+
"DAZ (deg)": f"{daz_deg:.2f}",
|
|
124
|
+
"ArcV (deg)": f"{arcv_deg:.2f}",
|
|
125
|
+
"ArcL (deg)": f"{arcl_deg:.2f}",
|
|
126
|
+
}
|
|
@@ -0,0 +1,514 @@
|
|
|
1
|
+
def subuh(lat,long,ele,tz,y,m,d):
|
|
2
|
+
from datetime import datetime, timedelta, date
|
|
3
|
+
from skyfield.api import load, wgs84
|
|
4
|
+
from skyfield import almanac
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
# -------- Settings --------
|
|
8
|
+
lat_location = lat
|
|
9
|
+
long_location = long
|
|
10
|
+
ele = ele
|
|
11
|
+
timezone = tz # UTC+8 (Malaysia)
|
|
12
|
+
year = y
|
|
13
|
+
month = m
|
|
14
|
+
day = d
|
|
15
|
+
|
|
16
|
+
the_day = date(year, month, day)
|
|
17
|
+
|
|
18
|
+
target_alt_deg = -17.99 # e.g., astronomical twilight
|
|
19
|
+
tolerance_alt = 0.01 # ± degrees around target altitude
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
# -------- Skyfield setup --------
|
|
23
|
+
ts = load.timescale()
|
|
24
|
+
eph = load('de440s.bsp')
|
|
25
|
+
earth, sun = eph['earth'], eph['sun']
|
|
26
|
+
location = earth + wgs84.latlon(lat_location, long_location, elevation_m=ele)
|
|
27
|
+
|
|
28
|
+
def make_altitude_predicate(target_alt_deg: float, tolerance_alt: float):
|
|
29
|
+
"""True when Sun altitude is within [target - tol, target + tol]."""
|
|
30
|
+
lo = target_alt_deg - tolerance_alt
|
|
31
|
+
hi = target_alt_deg + tolerance_alt
|
|
32
|
+
|
|
33
|
+
def in_alt_band(t):
|
|
34
|
+
alt, az, _ = location.at(t).observe(sun).apparent().altaz()
|
|
35
|
+
return (alt.degrees >= lo) & (alt.degrees <= hi)
|
|
36
|
+
|
|
37
|
+
# hints for Skyfield’s search
|
|
38
|
+
in_alt_band.step_days = 0.00001
|
|
39
|
+
in_alt_band.rough_period = 0.5 # ≈12 h between crossings
|
|
40
|
+
return in_alt_band
|
|
41
|
+
|
|
42
|
+
pred = make_altitude_predicate(target_alt_deg, tolerance_alt)
|
|
43
|
+
|
|
44
|
+
def local_from_utc(dt_utc):
|
|
45
|
+
return dt_utc + timedelta(hours=timezone)
|
|
46
|
+
|
|
47
|
+
def sun_altitude_intervals_for_day(d: date):
|
|
48
|
+
"""Return [(entry_time_utc, exit_time_utc)] for one local day where altitude is in band."""
|
|
49
|
+
t0 = ts.utc(year, month, day, 0 - timezone, 0, 0)
|
|
50
|
+
t1 = ts.utc(year, month, day, 12 - timezone, 0, 0)
|
|
51
|
+
|
|
52
|
+
times, states = almanac.find_discrete(t0, t1, pred)
|
|
53
|
+
intervals = []
|
|
54
|
+
|
|
55
|
+
# if we start inside True state, prepend opening time
|
|
56
|
+
is_true_at_start = bool(pred(t0)) if np.isscalar(pred(t0)) else bool(pred(t0).any())
|
|
57
|
+
if is_true_at_start:
|
|
58
|
+
times = ts.tt_jd(np.insert(times.tt, 0, t0.tt))
|
|
59
|
+
states = np.insert(states, 0, True)
|
|
60
|
+
|
|
61
|
+
# build [entry, exit) pairs for True segments
|
|
62
|
+
for i in range(len(times) - 1):
|
|
63
|
+
if states[i]:
|
|
64
|
+
intervals.append((times[i], times[i + 1]))
|
|
65
|
+
|
|
66
|
+
# if last state continues to t1
|
|
67
|
+
if len(times) > 0 and states[-1]:
|
|
68
|
+
intervals.append((times[-1], t1))
|
|
69
|
+
|
|
70
|
+
return intervals
|
|
71
|
+
|
|
72
|
+
# -------- Run for one day --------
|
|
73
|
+
intervals = sun_altitude_intervals_for_day(the_day)
|
|
74
|
+
|
|
75
|
+
#print(f"Sun altitude windows for {the_day.isoformat()} (local UTC{timezone:+d}), "
|
|
76
|
+
# f"alt ≈ {target_alt_deg}° ±{tolerance_alt}°")
|
|
77
|
+
|
|
78
|
+
if not intervals:
|
|
79
|
+
print(" — none —")
|
|
80
|
+
else:
|
|
81
|
+
for idx, (entry, exit_) in enumerate(intervals, start=1):
|
|
82
|
+
utc_entry = entry.utc_datetime()
|
|
83
|
+
utc_exit = exit_.utc_datetime()
|
|
84
|
+
loc_entry = local_from_utc(utc_entry)
|
|
85
|
+
loc_exit = local_from_utc(utc_exit)
|
|
86
|
+
duration_min = (utc_exit - utc_entry).total_seconds() / 60.0
|
|
87
|
+
|
|
88
|
+
# optional: altitude & azimuth at entry
|
|
89
|
+
alt_e, az_e, _ = location.at(entry).observe(sun).apparent().altaz()
|
|
90
|
+
|
|
91
|
+
# print(f" #{idx}: {loc_entry.strftime('%H:%M:%S')} → {loc_exit.strftime('%H:%M:%S')}"
|
|
92
|
+
# f" | duration: {duration_min:.2f} min"
|
|
93
|
+
# f" | alt_entry: {alt_e.degrees:.2f}° az: {az_e.degrees:.2f}°")
|
|
94
|
+
z=loc_entry.strftime('%H:%M:%S')
|
|
95
|
+
return z
|
|
96
|
+
|
|
97
|
+
def syuruk(lat,long,ele,tz,y,m,d):
|
|
98
|
+
from datetime import datetime, timedelta, date
|
|
99
|
+
from skyfield.api import load, wgs84
|
|
100
|
+
from skyfield import almanac
|
|
101
|
+
import numpy as np
|
|
102
|
+
|
|
103
|
+
# -------- Settings --------
|
|
104
|
+
lat_location = lat
|
|
105
|
+
long_location = long
|
|
106
|
+
ele = ele
|
|
107
|
+
timezone = tz # UTC+8 (Malaysia)
|
|
108
|
+
year = y
|
|
109
|
+
month = m
|
|
110
|
+
day = d
|
|
111
|
+
|
|
112
|
+
ts = load.timescale()
|
|
113
|
+
eph = load('de440s.bsp')
|
|
114
|
+
planets = load('de440s.bsp')
|
|
115
|
+
earth = planets['earth']
|
|
116
|
+
sun = planets['sun']
|
|
117
|
+
location = earth + wgs84.latlon(lat_location, long_location, elevation_m=ele)
|
|
118
|
+
|
|
119
|
+
t0 = ts.utc(year, month, day-1)
|
|
120
|
+
t1 = ts.utc(year, month, day)
|
|
121
|
+
|
|
122
|
+
from skyfield.units import Angle
|
|
123
|
+
from numpy import arccos
|
|
124
|
+
from skyfield.earthlib import refraction
|
|
125
|
+
|
|
126
|
+
altitude_m = ele
|
|
127
|
+
earth_radius_m = 6378136.6
|
|
128
|
+
side_over_hypotenuse = earth_radius_m / (earth_radius_m + altitude_m)
|
|
129
|
+
h = Angle(radians=-arccos(side_over_hypotenuse))
|
|
130
|
+
solar_radius_degrees = 16 / 60
|
|
131
|
+
r = refraction(0.0, temperature_C=15.0, pressure_mbar=1030.0)
|
|
132
|
+
|
|
133
|
+
t, y = almanac.find_risings(location, sun, t0, t1, horizon_degrees=-r + h.degrees - solar_radius_degrees)
|
|
134
|
+
h, m, s = t.utc.hour, t.utc.minute, t.utc.second
|
|
135
|
+
syuruk_time = float(np.array(h).item() + np.array(m).item()/60 + np.array(s).item()/3600 + timezone)
|
|
136
|
+
syuruk_time %= 24 # Ensure 24-hour clock format
|
|
137
|
+
|
|
138
|
+
syuruk_time = float(syuruk_time)
|
|
139
|
+
degrees = int(syuruk_time)
|
|
140
|
+
decimal_part = syuruk_time - degrees
|
|
141
|
+
minutes_total = decimal_part * 60
|
|
142
|
+
minutes = int(minutes_total)
|
|
143
|
+
seconds = round((minutes_total - minutes) * 60)
|
|
144
|
+
sun_astro = location.at(ts.utc(year, month, day, h, m, s)).observe(sun)
|
|
145
|
+
sun_alt, _, _ = sun_astro.apparent().altaz()
|
|
146
|
+
sun_alt, _, _ = sun_astro.apparent().altaz()
|
|
147
|
+
if sun_alt.degrees >= 0:
|
|
148
|
+
syuruk = "Syuruk Does Not Occur"
|
|
149
|
+
else:
|
|
150
|
+
syuruk = f"{degrees}:{minutes}:{seconds}"
|
|
151
|
+
|
|
152
|
+
return syuruk
|
|
153
|
+
|
|
154
|
+
def zuhur(lat,long,ele,tz,y,m,d):
|
|
155
|
+
from datetime import datetime, timedelta, date
|
|
156
|
+
from skyfield.api import load, wgs84
|
|
157
|
+
from skyfield import almanac
|
|
158
|
+
import numpy as np
|
|
159
|
+
|
|
160
|
+
# -------- Settings --------
|
|
161
|
+
lat_location = lat
|
|
162
|
+
long_location = long
|
|
163
|
+
ele = ele
|
|
164
|
+
timezone = tz # UTC+8 (Malaysia)
|
|
165
|
+
year = y
|
|
166
|
+
month = m
|
|
167
|
+
day = d
|
|
168
|
+
|
|
169
|
+
ts = load.timescale()
|
|
170
|
+
eph = load('de440s.bsp')
|
|
171
|
+
planets = load('de440s.bsp')
|
|
172
|
+
earth = planets['earth']
|
|
173
|
+
sun = planets['sun']
|
|
174
|
+
location = earth + wgs84.latlon(lat_location, long_location, elevation_m=ele)
|
|
175
|
+
|
|
176
|
+
t0 = ts.utc(year, month, day-1)
|
|
177
|
+
t1 = ts.utc(year, month, day)
|
|
178
|
+
|
|
179
|
+
from skyfield.units import Angle
|
|
180
|
+
from numpy import arccos
|
|
181
|
+
from skyfield.earthlib import refraction
|
|
182
|
+
|
|
183
|
+
t = almanac.find_transits(location, sun, t0, t1)
|
|
184
|
+
h = t.utc.hour
|
|
185
|
+
m = t.utc.minute
|
|
186
|
+
s = t.utc.second
|
|
187
|
+
|
|
188
|
+
#print(hour_solar_transit)
|
|
189
|
+
#print(minutes_solar_transit )
|
|
190
|
+
#print(second_solar_transit)
|
|
191
|
+
#zuhur_time = hour_solar_transit + (minutes_solar_transit / 60) + (second_solar_transit / 3600 ) + timezone + 0.017778
|
|
192
|
+
zuhur_time = float(np.array(h).item() + np.array(m).item()/60 + np.array(s).item()/3600 + timezone+ 0.017778)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
zuhur_time = float(zuhur_time)
|
|
196
|
+
degrees = int(zuhur_time )
|
|
197
|
+
decimal_part = zuhur_time - degrees
|
|
198
|
+
minutes_total = decimal_part * 60
|
|
199
|
+
minutes = int(minutes_total)
|
|
200
|
+
|
|
201
|
+
seconds = round((minutes_total - minutes) * 60)
|
|
202
|
+
#print(f"{degrees}° {minutes}′ {seconds}″")
|
|
203
|
+
|
|
204
|
+
sun_astro = location.at(ts.utc(year, month, day, h, m, s)).observe(sun)
|
|
205
|
+
sun_alt, _, _ = sun_astro.apparent().altaz()
|
|
206
|
+
alt_deg = float(np.atleast_1d(sun_alt.degrees)[0]) # <- force scalar
|
|
207
|
+
|
|
208
|
+
# Check if the sun is above the horizon at zuhur time
|
|
209
|
+
if sun_alt.degrees <= 0:
|
|
210
|
+
zuhur = "Zuhur Does Not Occur"
|
|
211
|
+
else:
|
|
212
|
+
zuhur = f"{degrees}:{minutes}:{seconds}"
|
|
213
|
+
altitude_zuhur = alt_deg
|
|
214
|
+
|
|
215
|
+
return zuhur,altitude_zuhur
|
|
216
|
+
|
|
217
|
+
def asar(lat,long,ele,tz,y,m,d):
|
|
218
|
+
from datetime import datetime, timedelta, date
|
|
219
|
+
from skyfield.api import load, wgs84
|
|
220
|
+
from skyfield import almanac
|
|
221
|
+
import numpy as np
|
|
222
|
+
import math
|
|
223
|
+
|
|
224
|
+
# -------- Settings --------
|
|
225
|
+
lat_location = lat
|
|
226
|
+
long_location = long
|
|
227
|
+
ele = ele
|
|
228
|
+
timezone = tz # UTC+8 (Malaysia)
|
|
229
|
+
year = y
|
|
230
|
+
month = m
|
|
231
|
+
day = d
|
|
232
|
+
|
|
233
|
+
the_day = date(year, month, day)
|
|
234
|
+
|
|
235
|
+
_, altitude_zuhur = zuhur(lat,long,ele,tz,y,m,d)
|
|
236
|
+
|
|
237
|
+
# Noon shadow and Asar target altitude
|
|
238
|
+
s0 = 1.0 / math.tan(math.radians(altitude_zuhur))
|
|
239
|
+
h_asar = math.degrees(math.atan(1.0 / (1.0 + s0)))
|
|
240
|
+
|
|
241
|
+
target_alt_deg = h_asar # angle asar
|
|
242
|
+
tolerance_alt = 0.01 # ± degrees around target altitude
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# -------- Skyfield setup --------
|
|
246
|
+
ts = load.timescale()
|
|
247
|
+
eph = load('de440s.bsp')
|
|
248
|
+
earth, sun = eph['earth'], eph['sun']
|
|
249
|
+
location = earth + wgs84.latlon(lat_location, long_location, elevation_m=ele)
|
|
250
|
+
|
|
251
|
+
def make_altitude_predicate(target_alt_deg: float, tolerance_alt: float):
|
|
252
|
+
"""True when Sun altitude is within [target - tol, target + tol]."""
|
|
253
|
+
lo = target_alt_deg - tolerance_alt
|
|
254
|
+
hi = target_alt_deg + tolerance_alt
|
|
255
|
+
|
|
256
|
+
def in_alt_band(t):
|
|
257
|
+
alt, az, _ = location.at(t).observe(sun).apparent().altaz()
|
|
258
|
+
return (alt.degrees >= lo) & (alt.degrees <= hi)
|
|
259
|
+
|
|
260
|
+
# hints for Skyfield’s search
|
|
261
|
+
in_alt_band.step_days = 0.00001
|
|
262
|
+
in_alt_band.rough_period = 0.5 # ≈12 h between crossings
|
|
263
|
+
return in_alt_band
|
|
264
|
+
|
|
265
|
+
pred = make_altitude_predicate(target_alt_deg, tolerance_alt)
|
|
266
|
+
|
|
267
|
+
def local_from_utc(dt_utc):
|
|
268
|
+
return dt_utc + timedelta(hours=timezone)
|
|
269
|
+
|
|
270
|
+
def sun_altitude_intervals_for_day(d: date):
|
|
271
|
+
"""Return [(entry_time_utc, exit_time_utc)] for one local day where altitude is in band."""
|
|
272
|
+
t0 = ts.utc(year, month, day, 12 - timezone, 0, 0)
|
|
273
|
+
t1 = ts.utc(year, month, day, 24 - timezone, 0, 0)
|
|
274
|
+
|
|
275
|
+
times, states = almanac.find_discrete(t0, t1, pred)
|
|
276
|
+
intervals = []
|
|
277
|
+
|
|
278
|
+
# if we start inside True state, prepend opening time
|
|
279
|
+
is_true_at_start = bool(pred(t0)) if np.isscalar(pred(t0)) else bool(pred(t0).any())
|
|
280
|
+
if is_true_at_start:
|
|
281
|
+
times = ts.tt_jd(np.insert(times.tt, 0, t0.tt))
|
|
282
|
+
states = np.insert(states, 0, True)
|
|
283
|
+
|
|
284
|
+
# build [entry, exit) pairs for True segments
|
|
285
|
+
for i in range(len(times) - 1):
|
|
286
|
+
if states[i]:
|
|
287
|
+
intervals.append((times[i], times[i + 1]))
|
|
288
|
+
|
|
289
|
+
# if last state continues to t1
|
|
290
|
+
if len(times) > 0 and states[-1]:
|
|
291
|
+
intervals.append((times[-1], t1))
|
|
292
|
+
|
|
293
|
+
return intervals
|
|
294
|
+
|
|
295
|
+
# -------- Run for one day --------
|
|
296
|
+
intervals = sun_altitude_intervals_for_day(the_day)
|
|
297
|
+
|
|
298
|
+
#print(f"Sun altitude windows for {the_day.isoformat()} (local UTC{timezone:+d}), "
|
|
299
|
+
# f"alt ≈ {target_alt_deg}° ±{tolerance_alt}°")
|
|
300
|
+
|
|
301
|
+
if not intervals:
|
|
302
|
+
print(" — none —")
|
|
303
|
+
else:
|
|
304
|
+
for idx, (entry, exit_) in enumerate(intervals, start=1):
|
|
305
|
+
utc_entry = entry.utc_datetime()
|
|
306
|
+
utc_exit = exit_.utc_datetime()
|
|
307
|
+
loc_entry = local_from_utc(utc_entry)
|
|
308
|
+
loc_exit = local_from_utc(utc_exit)
|
|
309
|
+
duration_min = (utc_exit - utc_entry).total_seconds() / 60.0
|
|
310
|
+
|
|
311
|
+
# optional: altitude & azimuth at entry
|
|
312
|
+
alt_e, az_e, _ = location.at(entry).observe(sun).apparent().altaz()
|
|
313
|
+
|
|
314
|
+
# print(f" #{idx}: {loc_entry.strftime('%H:%M:%S')} → {loc_exit.strftime('%H:%M:%S')}"
|
|
315
|
+
# f" | duration: {duration_min:.2f} min"
|
|
316
|
+
# f" | alt_entry: {alt_e.degrees:.2f}° az: {az_e.degrees:.2f}°")
|
|
317
|
+
z=loc_entry.strftime('%H:%M:%S')
|
|
318
|
+
return z
|
|
319
|
+
|
|
320
|
+
def maghrib(lat,long,ele,tz,y,m,d):
|
|
321
|
+
from datetime import datetime, timedelta, date
|
|
322
|
+
from skyfield.api import load, wgs84
|
|
323
|
+
from skyfield import almanac
|
|
324
|
+
import numpy as np
|
|
325
|
+
|
|
326
|
+
# -------- Settings --------
|
|
327
|
+
lat_location = lat
|
|
328
|
+
long_location = long
|
|
329
|
+
ele = ele
|
|
330
|
+
timezone = tz # UTC+8 (Malaysia)
|
|
331
|
+
year = y
|
|
332
|
+
month = m
|
|
333
|
+
day = d
|
|
334
|
+
|
|
335
|
+
ts = load.timescale()
|
|
336
|
+
eph = load('de440s.bsp')
|
|
337
|
+
planets = load('de440s.bsp')
|
|
338
|
+
earth = planets['earth']
|
|
339
|
+
sun = planets['sun']
|
|
340
|
+
location = earth + wgs84.latlon(lat_location, long_location, elevation_m=ele)
|
|
341
|
+
|
|
342
|
+
t0 = ts.utc(year, month, day-1)
|
|
343
|
+
t1 = ts.utc(year, month, day)
|
|
344
|
+
|
|
345
|
+
from skyfield.units import Angle
|
|
346
|
+
from numpy import arccos
|
|
347
|
+
from skyfield.earthlib import refraction
|
|
348
|
+
|
|
349
|
+
altitude_m = ele
|
|
350
|
+
earth_radius_m = 6378136.6
|
|
351
|
+
side_over_hypotenuse = earth_radius_m / (earth_radius_m + altitude_m)
|
|
352
|
+
h = Angle(radians=-arccos(side_over_hypotenuse))
|
|
353
|
+
solar_radius_degrees = 16 / 60
|
|
354
|
+
r = refraction(0.0, temperature_C=15.0, pressure_mbar=1030.0)
|
|
355
|
+
|
|
356
|
+
t, y = almanac.find_settings(location, sun, t0, t1, horizon_degrees=-r + h.degrees - solar_radius_degrees)
|
|
357
|
+
h, m, s = t.utc.hour, t.utc.minute, t.utc.second
|
|
358
|
+
maghrib_time = float(np.array(h).item() + np.array(m).item()/60 + np.array(s).item()/3600 + timezone)
|
|
359
|
+
maghrib_time %= 24 # Ensure 24-hour clock format
|
|
360
|
+
|
|
361
|
+
maghrib_time = float(maghrib_time)
|
|
362
|
+
degrees = int(maghrib_time)
|
|
363
|
+
decimal_part = maghrib_time - degrees
|
|
364
|
+
minutes_total = decimal_part * 60
|
|
365
|
+
minutes = int(minutes_total)
|
|
366
|
+
seconds = round((minutes_total - minutes) * 60)
|
|
367
|
+
sun_astro = location.at(ts.utc(year, month, day, h, m, s)).observe(sun)
|
|
368
|
+
sun_alt, _, _ = sun_astro.apparent().altaz()
|
|
369
|
+
sun_alt, _, _ = sun_astro.apparent().altaz()
|
|
370
|
+
if sun_alt.degrees >= 0:
|
|
371
|
+
maghrib = "Maghrib Does Not Occur"
|
|
372
|
+
else:
|
|
373
|
+
maghrib = f"{degrees}:{minutes}:{seconds}"
|
|
374
|
+
|
|
375
|
+
return maghrib
|
|
376
|
+
|
|
377
|
+
def isyak(lat,long,ele,tz,y,m,d):
|
|
378
|
+
from datetime import datetime, timedelta, date
|
|
379
|
+
from skyfield.api import load, wgs84
|
|
380
|
+
from skyfield import almanac
|
|
381
|
+
import numpy as np
|
|
382
|
+
|
|
383
|
+
# -------- Settings --------
|
|
384
|
+
lat_location = lat
|
|
385
|
+
long_location = long
|
|
386
|
+
ele = ele
|
|
387
|
+
timezone = tz # UTC+8 (Malaysia)
|
|
388
|
+
year = y
|
|
389
|
+
month = m
|
|
390
|
+
day = d
|
|
391
|
+
|
|
392
|
+
the_day = date(year, month, day)
|
|
393
|
+
|
|
394
|
+
target_alt_deg = -17.99 # e.g., astronomical twilight
|
|
395
|
+
tolerance_alt = 0.01 # ± degrees around target altitude
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
# -------- Skyfield setup --------
|
|
399
|
+
ts = load.timescale()
|
|
400
|
+
eph = load('de440s.bsp')
|
|
401
|
+
earth, sun = eph['earth'], eph['sun']
|
|
402
|
+
location = earth + wgs84.latlon(lat_location, long_location, elevation_m=ele)
|
|
403
|
+
|
|
404
|
+
def make_altitude_predicate(target_alt_deg: float, tolerance_alt: float):
|
|
405
|
+
"""True when Sun altitude is within [target - tol, target + tol]."""
|
|
406
|
+
lo = target_alt_deg - tolerance_alt
|
|
407
|
+
hi = target_alt_deg + tolerance_alt
|
|
408
|
+
|
|
409
|
+
def in_alt_band(t):
|
|
410
|
+
alt, az, _ = location.at(t).observe(sun).apparent().altaz()
|
|
411
|
+
return (alt.degrees >= lo) & (alt.degrees <= hi)
|
|
412
|
+
|
|
413
|
+
# hints for Skyfield’s search
|
|
414
|
+
in_alt_band.step_days = 0.00001
|
|
415
|
+
in_alt_band.rough_period = 0.5 # ≈12 h between crossings
|
|
416
|
+
return in_alt_band
|
|
417
|
+
|
|
418
|
+
pred = make_altitude_predicate(target_alt_deg, tolerance_alt)
|
|
419
|
+
|
|
420
|
+
def local_from_utc(dt_utc):
|
|
421
|
+
return dt_utc + timedelta(hours=timezone)
|
|
422
|
+
|
|
423
|
+
def sun_altitude_intervals_for_day(d: date):
|
|
424
|
+
"""Return [(entry_time_utc, exit_time_utc)] for one local day where altitude is in band."""
|
|
425
|
+
t0 = ts.utc(year, month, day, 12 - timezone, 0, 0)
|
|
426
|
+
t1 = ts.utc(year, month, day, 24 - timezone, 0, 0)
|
|
427
|
+
|
|
428
|
+
times, states = almanac.find_discrete(t0, t1, pred)
|
|
429
|
+
intervals = []
|
|
430
|
+
|
|
431
|
+
# if we start inside True state, prepend opening time
|
|
432
|
+
is_true_at_start = bool(pred(t0)) if np.isscalar(pred(t0)) else bool(pred(t0).any())
|
|
433
|
+
if is_true_at_start:
|
|
434
|
+
times = ts.tt_jd(np.insert(times.tt, 0, t0.tt))
|
|
435
|
+
states = np.insert(states, 0, True)
|
|
436
|
+
|
|
437
|
+
# build [entry, exit) pairs for True segments
|
|
438
|
+
for i in range(len(times) - 1):
|
|
439
|
+
if states[i]:
|
|
440
|
+
intervals.append((times[i], times[i + 1]))
|
|
441
|
+
|
|
442
|
+
# if last state continues to t1
|
|
443
|
+
if len(times) > 0 and states[-1]:
|
|
444
|
+
intervals.append((times[-1], t1))
|
|
445
|
+
|
|
446
|
+
return intervals
|
|
447
|
+
|
|
448
|
+
# -------- Run for one day --------
|
|
449
|
+
intervals = sun_altitude_intervals_for_day(the_day)
|
|
450
|
+
|
|
451
|
+
# print(f"Sun altitude windows for {the_day.isoformat()} (local UTC{timezone:+d}), "
|
|
452
|
+
# f"alt ≈ {target_alt_deg}° ±{tolerance_alt}°")
|
|
453
|
+
|
|
454
|
+
if not intervals:
|
|
455
|
+
print(" — none —")
|
|
456
|
+
else:
|
|
457
|
+
for idx, (entry, exit_) in enumerate(intervals, start=1):
|
|
458
|
+
utc_entry = entry.utc_datetime()
|
|
459
|
+
utc_exit = exit_.utc_datetime()
|
|
460
|
+
loc_entry = local_from_utc(utc_entry)
|
|
461
|
+
loc_exit = local_from_utc(utc_exit)
|
|
462
|
+
duration_min = (utc_exit - utc_entry).total_seconds() / 60.0
|
|
463
|
+
|
|
464
|
+
# optional: altitude & azimuth at entry
|
|
465
|
+
alt_e, az_e, _ = location.at(entry).observe(sun).apparent().altaz()
|
|
466
|
+
|
|
467
|
+
# print(f" #{idx}: {loc_entry.strftime('%H:%M:%S')} → {loc_exit.strftime('%H:%M:%S')}"
|
|
468
|
+
# f" | duration: {duration_min:.2f} min"
|
|
469
|
+
# f" | alt_entry: {alt_e.degrees:.2f}° az: {az_e.degrees:.2f}°")
|
|
470
|
+
z=loc_entry.strftime('%H:%M:%S')
|
|
471
|
+
return z
|
|
472
|
+
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def singleday(lat, lon, ele, tz, y, m, d, csv_filename="prayer_times.csv"):
|
|
476
|
+
|
|
477
|
+
import csv
|
|
478
|
+
from datetime import date
|
|
479
|
+
"""
|
|
480
|
+
Generate prayer times for one date, print as table, and also save as CSV.
|
|
481
|
+
"""
|
|
482
|
+
# --- get each prayer time ---
|
|
483
|
+
subuh_time = subuh(lat, lon, ele, tz, y, m, d)
|
|
484
|
+
syuruk_time = syuruk(lat, lon, ele, tz, y, m, d)
|
|
485
|
+
zuhur_time, _ = zuhur(lat, lon, ele, tz, y, m, d)
|
|
486
|
+
asar_time = asar(lat, lon, ele, tz, y, m, d)
|
|
487
|
+
maghrib_time = maghrib(lat, lon, ele, tz, y, m, d)
|
|
488
|
+
isyak_time = isyak(lat, lon, ele, tz, y, m, d)
|
|
489
|
+
|
|
490
|
+
# --- Prepare data row ---
|
|
491
|
+
date_str = date(y, m, d).strftime("%Y-%m-%d")
|
|
492
|
+
header = ["Date", "Subuh", "Syuruk", "Zuhur", "Asar", "Maghrib", "Isyak"]
|
|
493
|
+
row = [date_str, subuh_time, syuruk_time, zuhur_time, asar_time, maghrib_time, isyak_time]
|
|
494
|
+
|
|
495
|
+
# --- Print table to screen ---
|
|
496
|
+
print("\n+------------+----------+----------+----------+----------+----------+----------+")
|
|
497
|
+
print("| Date | Subuh | Syuruk | Zuhur | Asar | Maghrib | Isyak |")
|
|
498
|
+
print("+------------+----------+----------+----------+----------+----------+----------+")
|
|
499
|
+
print(f"| {date_str} | {subuh_time:8} | {syuruk_time:8} | {zuhur_time:8} | "
|
|
500
|
+
f"{asar_time:8} | {maghrib_time:8} | {isyak_time:8} |")
|
|
501
|
+
print("+------------+----------+----------+----------+----------+----------+----------+")
|
|
502
|
+
|
|
503
|
+
# --- Write to CSV file ---
|
|
504
|
+
with open(csv_filename, mode="w", newline="") as f:
|
|
505
|
+
writer = csv.writer(f)
|
|
506
|
+
writer.writerow(header)
|
|
507
|
+
writer.writerow(row)
|
|
508
|
+
|
|
509
|
+
print(f"\n✅ Saved to CSV file: {csv_filename}")
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|
|
513
|
+
|
|
514
|
+
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""qibla
|
|
3
|
+
|
|
4
|
+
Automatically generated by Colab.
|
|
5
|
+
|
|
6
|
+
Original file is located at
|
|
7
|
+
https://colab.research.google.com/drive/1UK-U7P-Ab71HAPQJe0c4NNwdZ0TKaGxG
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import math
|
|
11
|
+
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True)
|
|
15
|
+
class QiblaDirection:
|
|
16
|
+
decimal: float # decimal degrees, 0..360
|
|
17
|
+
degree: str
|
|
18
|
+
|
|
19
|
+
def direction (lat : float, long: float) -> QiblaDirection:
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
φ_Location = lat
|
|
23
|
+
λ_Location = long
|
|
24
|
+
φ_Kaabah = 21.4225
|
|
25
|
+
λ_Kaabah = 39.8262
|
|
26
|
+
Difference_Longitude = abs(λ_Location-λ_Kaabah )
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#Calculation of Qibla Direction
|
|
30
|
+
import math
|
|
31
|
+
|
|
32
|
+
A = math.sin(math.radians(abs(Difference_Longitude)))
|
|
33
|
+
B = math.cos(math.radians(φ_Location))*math.tan(math.radians(φ_Kaabah))
|
|
34
|
+
C = math.sin(math.radians(φ_Location)) * math.cos(math.radians(Difference_Longitude))
|
|
35
|
+
D = A/(B-C)
|
|
36
|
+
θ = math.degrees(math.atan(D))
|
|
37
|
+
|
|
38
|
+
#Determine the Azimuth of the Qibla
|
|
39
|
+
if Difference_Longitude > 180:
|
|
40
|
+
delta_λ = 360 - Difference_Longitude
|
|
41
|
+
else:
|
|
42
|
+
delta_λ = Difference_Longitude
|
|
43
|
+
|
|
44
|
+
if θ > 0:
|
|
45
|
+
if λ_Location > λ_Kaabah:
|
|
46
|
+
quadrant = "UB" # Utara Barat
|
|
47
|
+
elif λ_Location <= λ_Kaabah:
|
|
48
|
+
quadrant = "UT" # Utara Timur
|
|
49
|
+
elif λ_Location < 0:
|
|
50
|
+
if c >= 180:
|
|
51
|
+
quadrant = "UB"
|
|
52
|
+
else:
|
|
53
|
+
quadrant = "UT"
|
|
54
|
+
elif θ < 0:
|
|
55
|
+
if λ_Location > λ_Kaabah:
|
|
56
|
+
quadrant = "SB" # Selatan Barat
|
|
57
|
+
elif λ_Location <= λ_Kaabah:
|
|
58
|
+
quadrant = "ST" # Selatan Timur
|
|
59
|
+
elif λ_Location < 0:
|
|
60
|
+
if c >= 180:
|
|
61
|
+
quadrant = "SB"
|
|
62
|
+
else:
|
|
63
|
+
quadrant = "ST"
|
|
64
|
+
|
|
65
|
+
if quadrant == "UB":
|
|
66
|
+
azimuth_kiblat = 360 - θ
|
|
67
|
+
elif quadrant == "SB":
|
|
68
|
+
azimuth_kiblat = 180 - θ
|
|
69
|
+
elif quadrant == "UT":
|
|
70
|
+
azimuth_kiblat = θ
|
|
71
|
+
elif quadrant == "ST":
|
|
72
|
+
azimuth_kiblat = 180 + θ
|
|
73
|
+
|
|
74
|
+
# To Convert in Degree Form
|
|
75
|
+
|
|
76
|
+
degrees = int(azimuth_kiblat)
|
|
77
|
+
decimal_part = azimuth_kiblat - degrees
|
|
78
|
+
minutes_total = decimal_part = 60
|
|
79
|
+
minutes = int(minutes_total)
|
|
80
|
+
seconds = round((minutes_total - minutes) * 60)
|
|
81
|
+
#print(f'The azimuth of the Qibla for Location with coordinate {φ_Location} Latitude, {λ_Location} Longitude, is {degrees}° {minutes}′ {seconds}″')
|
|
82
|
+
outputdegree = f"{degrees}° {minutes}′ {seconds}″"
|
|
83
|
+
|
|
84
|
+
return QiblaDirection(decimal=azimuth_kiblat, degree=outputdegree)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: falakpy
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Small Package Tool for Islamic Astronomy Related Computation and Visualization.
|
|
5
|
+
Author-email: Muhamad Syazwan Faid <syazwaned90@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/msyazwanfaid/falakpy
|
|
8
|
+
Project-URL: Issues, https://github.com/msyazwanfaid/falakpy/issues
|
|
9
|
+
Keywords: Islamic Astronomy,qibla,prayer time,hijri calendar
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
13
|
+
Classifier: Typing :: Typed
|
|
14
|
+
Requires-Python: >=3.9
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
Requires-Dist: skyfield
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
pyproject.toml
|
|
2
|
+
src/falakpy/__init__.py
|
|
3
|
+
src/falakpy/lunar.py
|
|
4
|
+
src/falakpy/prayertime.py
|
|
5
|
+
src/falakpy/qibla.py
|
|
6
|
+
src/falakpy.egg-info/PKG-INFO
|
|
7
|
+
src/falakpy.egg-info/SOURCES.txt
|
|
8
|
+
src/falakpy.egg-info/dependency_links.txt
|
|
9
|
+
src/falakpy.egg-info/requires.txt
|
|
10
|
+
src/falakpy.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
skyfield
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
falakpy
|