Skip to content

Instantly share code, notes, and snippets.

@ismailezzaki96
Created July 7, 2024 11:18
Show Gist options
  • Save ismailezzaki96/f0b7f1e49459fdb809c61c6ffbd4fff7 to your computer and use it in GitHub Desktop.
Save ismailezzaki96/f0b7f1e49459fdb809c61c6ffbd4fff7 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
# tible with python 2.x and 3.x
import math
import os
import re
import subprocess
import sys
from datetime import datetime, timedelta
"""
--------------------- Copyright Block ----------------------
praytimes.py: Prayer Times Calculator (ver 2.3)
Copyright (C) 2007-2011 PrayTimes.org
Python Code: Berkin İlkan Seçkin, Saleem Shafi, Hamid Zarrabi-Zadeh and Khaled alHabache
Original js Code: 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 credit is given to the original work
with a link back to PrayTimes.org.
This program is distributed in the hope that it will
be useful, but WITHOUT ANY WARRANTY.
PLEASE DO NOT REMOVE THIS COPYRIGHT BLOCK.
--------------------- Help and Manual ----------------------
User's Manual:
http://praytimes.org/manual
Calculation Formulas:
http://praytimes.org/calculation
------------------------ User Interface -------------------------
getTimes (date, coordinates, timeZone [, dst [, timeFormat]])
setMethod (method) // set calculation method
adjust (parameters) // adjust calculation parameters
tune (offsets) // tune times by given offsets
getMethod () // get calculation method
getSetting () // get current calculation parameters
getOffsets () // get current time offsets
------------------------- Sample Usage --------------------------
>>> PT = PrayTimes('ISNA')
>>> times = PT.getTimes((2011, 2, 9), (43, -80), -5)
>>> times['sunrise']
07:26
"""
# ----------------------- PrayTimes Class ------------------------
class PrayTimes:
# ------------------------ Constants --------------------------
# Time Names
timeNames = {
"imsak": "Imsak",
"fajr": "Fajr",
"sunrise": "Sunrise",
"dhuhr": "Dhuhr",
"asr": "Asr",
"sunset": "Sunset",
"maghrib": "Maghrib",
"isha": "Isha",
"midnight": "Midnight",
}
# Calculation Methods
methods = {
"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"},
}, # fajr was 19 degrees before 1430 hijri
"Morocco": {
"name": "Ministry of islamic affairs, Morocco",
"params": {
"fajr": 19.1,
"dhuhr": "5 min",
"isha": 17,
"highLats": "AngleBased",
},
},
"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"},
},
# isha is not explicitly specified in this method
"Jafari": {
"name": "Shia Ithna-Ashari, Leva Institute, Qum",
"params": {"fajr": 16, "isha": 14, "maghrib": 4, "midnight": "Jafari"},
},
}
# Default Parameters in Calculation Methods
defaultParams = {"maghrib": "0 min", "midnight": "Standard"}
# ---------------------- Default Settings --------------------
calcMethod = "MWL"
# do not change anything here; use adjust method instead
settings = {
"imsak": "10 min",
"dhuhr": "0 min",
"asr": "Standard",
"highLats": "NightMiddle",
}
timeFormat = "24h"
timeSuffixes = ["am", "pm"]
invalidTime = "-----"
numIterations = 1
offset = {}
# ---------------------- Initialization -----------------------
def __init__(self, calMethod="MWL"):
# set methods defaults
global method
for method, config in self.methods.items():
for name, value in self.defaultParams.items():
if name not in config["params"] or config["params"][name] is None:
config["params"][name] = value
# initialize settings
self.calcMethod = calMethod if method in self.methods else "MWL"
params = self.methods[self.calcMethod]["params"]
value: int
for name, value in params.items():
self.settings[name] = value
# init time offsets
for name in self.timeNames:
self.offset[name] = 0
# -------------------- Interface Functions --------------------
def setMethod(self, method):
if method in self.methods:
self.adjust(self.methods[method].params)
self.calcMethod = method
def adjust(self, params):
self.settings.update(params)
def tune(self, timeOffsets):
self.offset.update(timeOffsets)
def getMethod(self):
return self.calcMethod
def getSettings(self):
return self.settings
def getOffsets(self):
return self.offset
def getDefaults(self):
return self.methods
# return prayer times for a given date
def getTimes(self, date, coords, timezone, dst=0, format=None):
self.lat = coords[0]
self.lng = coords[1]
self.elv = coords[2] if len(coords) > 2 else 0
if format != None:
self.timeFormat = format
if type(date).__name__ == "date":
date = (date.year, date.month, date.day)
self.timeZone = timezone + (1 if dst else 0)
self.jDate = self.julian(date[0], date[1], date[2]) - self.lng / (15 * 24.0)
return self.computeTimes()
# convert float time to the given format (see timeFormats)
def getFormattedTime(self, time, format, suffixes=None):
if math.isnan(time):
return self.invalidTime
if format == "Float":
return time
if suffixes == None:
suffixes = self.timeSuffixes
time = self.fixhour(time + 0.5 / 60) # add 0.5 minutes to round
hours = math.floor(time)
minutes = math.floor((time - hours) * 60)
suffix = suffixes[0 if hours < 12 else 1] if format == "12h" else ""
formattedTime = (
"%02d:%02d" % (hours, minutes)
if format == "24h"
else "%d:%02d" % ((hours + 11) % 12 + 1, minutes)
)
return formattedTime + suffix
# ---------------------- Calculation Functions -----------------------
# compute mid-day time
def midDay(self, time):
eqt = self.sunPosition(self.jDate + time)[1]
return self.fixhour(12 - eqt)
# compute the time at which sun reaches a specific angle below horizon
def sunAngleTime(self, angle, time, direction=None):
try:
decl = self.sunPosition(self.jDate + time)[0]
noon = self.midDay(time)
t = (
1
/ 15.0
* self.arccos(
(-self.sin(angle) - self.sin(decl) * self.sin(self.lat))
/ (self.cos(decl) * self.cos(self.lat))
)
)
return noon + (-t if direction == "ccw" else t)
except ValueError:
return float("nan")
# compute asr time
def asrTime(self, factor, time):
decl = self.sunPosition(self.jDate + time)[0]
angle = -self.arccot(factor + self.tan(abs(self.lat - decl)))
return self.sunAngleTime(angle, time)
# compute declination angle of sun and equation of time
# Ref: http://aa.usno.navy.mil/faq/docs/SunApprox.php
def sunPosition(self, jd):
D = jd - 2451545.0
g = self.fixangle(357.529 + 0.98560028 * D)
q = self.fixangle(280.459 + 0.98564736 * D)
L = self.fixangle(q + 1.915 * self.sin(g) + 0.020 * self.sin(2 * g))
R = 1.00014 - 0.01671 * self.cos(g) - 0.00014 * self.cos(2 * g)
e = 23.439 - 0.00000036 * D
RA = self.arctan2(self.cos(e) * self.sin(L), self.cos(L)) / 15.0
eqt = q / 15.0 - self.fixhour(RA)
decl = self.arcsin(self.sin(e) * self.sin(L))
return (decl, eqt)
# convert Gregorian date to Julian day
# Ref: Astronomical Algorithms by Jean Meeus
def julian(self, year, month, day):
if month <= 2:
year -= 1
month += 12
A = math.floor(year / 100)
B = 2 - A + math.floor(A / 4)
return (
math.floor(365.25 * (year + 4716))
+ math.floor(30.6001 * (month + 1))
+ day
+ B
- 1524.5
)
# ---------------------- Compute Prayer Times -----------------------
# compute prayer times at given julian date
def computePrayerTimes(self, times):
times = self.dayPortion(times)
params = self.settings
imsak = self.sunAngleTime(self.eval(params["imsak"]), times["imsak"], "ccw")
fajr = self.sunAngleTime(self.eval(params["fajr"]), times["fajr"], "ccw")
sunrise = self.sunAngleTime(
self.riseSetAngle(self.elv), times["sunrise"], "ccw"
)
dhuhr = self.midDay(times["dhuhr"])
asr = self.asrTime(self.asrFactor(params["asr"]), times["asr"])
sunset = self.sunAngleTime(self.riseSetAngle(self.elv), times["sunset"])
maghrib = self.sunAngleTime(self.eval(params["maghrib"]), times["maghrib"])
isha = self.sunAngleTime(self.eval(params["isha"]), times["isha"])
return {
"imsak": imsak,
"fajr": fajr,
"sunrise": sunrise,
"dhuhr": dhuhr,
"asr": asr,
"sunset": sunset,
"maghrib": maghrib,
"isha": isha,
}
# compute prayer times
def computeTimes(self):
times = {
"imsak": 5,
"fajr": 5,
"sunrise": 6,
"dhuhr": 12,
"asr": 13,
"sunset": 18,
"maghrib": 18,
"isha": 18,
}
# main iterations
for i in range(self.numIterations):
times = self.computePrayerTimes(times)
times = self.adjustTimes(times)
# add midnight time
if self.settings["midnight"] == "Jafari":
times["midnight"] = (
times["sunset"] + self.timeDiff(times["sunset"], times["fajr"]) / 2
)
else:
times["midnight"] = (
times["sunset"] + self.timeDiff(times["sunset"], times["sunrise"]) / 2
)
times = self.tuneTimes(times)
return self.modifyFormats(times)
# adjust times in a prayer time array
def adjustTimes(self, times):
params = self.settings
tzAdjust = self.timeZone - self.lng / 15.0
for t, v in times.items():
times[t] += tzAdjust
if params["highLats"] != "None":
times = self.adjustHighLats(times)
if self.isMin(params["imsak"]):
times["imsak"] = times["fajr"] - self.eval(params["imsak"]) / 60.0
# need to ask about 'min' settings
if self.isMin(params["maghrib"]):
times["maghrib"] = times["sunset"] + self.eval(params["maghrib"]) / 60.0
if self.isMin(params["isha"]):
times["isha"] = times["maghrib"] + self.eval(params["isha"]) / 60.0
times["dhuhr"] += self.eval(params["dhuhr"]) / 60.0
return times
# get asr shadow factor
def asrFactor(self, asrParam):
methods = {"Standard": 1, "Hanafi": 2}
return methods[asrParam] if asrParam in methods else self.eval(asrParam)
# return sun angle for sunset/sunrise
def riseSetAngle(self, elevation=0):
elevation = 0 if elevation == None else elevation
return 0.833 + 0.0347 * math.sqrt(elevation) # an approximation
# apply offsets to the times
def tuneTimes(self, times):
for name, value in times.items():
times[name] += self.offset[name] / 60.0
return times
# convert times to given time format
def modifyFormats(self, times):
for name, value in times.items():
times[name] = self.getFormattedTime(times[name], self.timeFormat)
return times
# adjust times for locations in higher latitudes
def adjustHighLats(self, times):
params = self.settings
nightTime = self.timeDiff(
times["sunset"], times["sunrise"]
) # sunset to sunrise
times["imsak"] = self.adjustHLTime(
times["imsak"],
times["sunrise"],
self.eval(params["imsak"]),
nightTime,
"ccw",
)
times["fajr"] = self.adjustHLTime(
times["fajr"], times["sunrise"], self.eval(params["fajr"]), nightTime, "ccw"
)
times["isha"] = self.adjustHLTime(
times["isha"], times["sunset"], self.eval(params["isha"]), nightTime
)
times["maghrib"] = self.adjustHLTime(
times["maghrib"], times["sunset"], self.eval(params["maghrib"]), nightTime
)
return times
# adjust a time for higher latitudes
def adjustHLTime(self, time, base, angle, night, direction=None):
portion = self.nightPortion(angle, night)
diff = (
self.timeDiff(time, base)
if direction == "ccw"
else self.timeDiff(base, time)
)
if math.isnan(time) or diff > portion:
time = base + (-portion if direction == "ccw" else portion)
return time
# the night portion used for adjusting times in higher latitudes
def nightPortion(self, angle, night):
method = self.settings["highLats"]
portion = 1 / 2.0 # midnight
if method == "AngleBased":
portion = 1 / 60.0 * angle
if method == "OneSeventh":
portion = 1 / 7.0
return portion * night
# convert hours to day portions
def dayPortion(self, times):
for i in times:
times[i] /= 24.0
return times
# ---------------------- Misc Functions -----------------------
# compute the difference between two times
def timeDiff(self, time1, time2):
return self.fixhour(time2 - time1)
# convert given string into a number
def eval(self, st):
val = re.split("[^0-9.+-]", str(st), 1)[0]
return float(val) if val else 0
# detect if input contains 'min'
def isMin(self, arg):
return isinstance(arg, str) and arg.find("min") > -1
# ----------------- Degree-Based Math Functions -------------------
def sin(self, d):
return math.sin(math.radians(d))
def cos(self, d):
return math.cos(math.radians(d))
def tan(self, d):
return math.tan(math.radians(d))
def arcsin(self, x):
return math.degrees(math.asin(x))
def arccos(self, x):
return math.degrees(math.acos(x))
def arctan(self, x):
return math.degrees(math.atan(x))
def arccot(self, x):
return math.degrees(math.atan(1.0 / x))
def arctan2(self, y, x):
return math.degrees(math.atan2(y, x))
def fixangle(self, angle):
return self.fix(angle, 360.0)
def fixhour(self, hour):
return self.fix(hour, 24.0)
def fix(self, a, mode):
if math.isnan(a):
return a
a = a - mode * (math.floor(a / mode))
return a + mode if a < 0 else a
# ---------------------- prayTimes Object -----------------------
prayTimes = PrayTimes("Morocco")
summer = 1
longitude = float(os.getenv("LONGITUDE"))
latitude = float(os.getenv("LATITUDE"))
eleveation = float(os.getenv("ELEVATION"))
city = (latitude, longitude, eleveation)
now = datetime.now()
times = prayTimes.getTimes([now.year, now.month, now.day], city, summer)
today_prayers = ""
for i in ["Fajr", "sunrise", "Dhuhr", "Asr", "Maghrib", "Isha"]:
today_prayers += f"{i} : {times[i.lower()]}\n"
if len(sys.argv) > 1 and sys.argv[1] == "-m":
for j in range(30):
new_date = now + timedelta(days=j)
date = f"{new_date.year}-{new_date.month}-{new_date.day}"
today_prayers = f"{date}\t"
times = prayTimes.getTimes([now.year, now.month, now.day + j], city, summer)
for i in ["Fajr", "sunrise", "Dhuhr", "Asr", "Maghrib", "Isha"]:
today_prayers += f"{times[i.lower()]}\t"
print(today_prayers)
print()
elif len(sys.argv) > 1 and sys.argv[1] == "-a":
print(today_prayers)
else:
button = os.getenv("BLOCK_BUTTON")
if button != None:
command = "st -T 'stfloat' -g 85x25+350+100 -e sh -c ".split()
command.append(f"echo '{today_prayers}' | less -srfc")
out = subprocess.run(command, capture_output=True, text=True)
start_time = str(now.hour) + ":" + str(now.minute)
t1 = datetime.strptime(start_time, "%H:%M")
for i in ["Fajr", "sunrise", "Dhuhr", "Asr", "Maghrib", "Isha"]:
t2 = datetime.strptime(times[i.lower()], "%H:%M")
delta = t2 - t1
delta = round(delta.total_seconds() // 60)
if delta > 0:
print(f"{times[i.lower()]}")
break
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment