jadwal_shalat.py

Download
import math
import datetime
from datetime import timedelta
import tkinter as tk
from tkinter import ttk, messagebox
from skyfield.api import load, wgs84

# =============================================================================
# DATABASE KOTA PENTING DI PULAU JAWA
# =============================================================================
DATA_JAWA = {
    "Jakarta": {"lat": -6.1751, "lon": 106.8650},
    "Serang": {"lat": -6.1104, "lon": 106.1605},
    "Bandung": {"lat": -6.9175, "lon": 107.6191},
    "Bekasi": {"lat": -6.2383, "lon": 106.9756},
    "Depok": {"lat": -6.4025, "lon": 106.7942},
    "Bogor": {"lat": -6.5971, "lon": 106.7952},
    "Cirebon": {"lat": -6.7320, "lon": 108.5523},
    "Semarang": {"lat": -6.9667, "lon": 110.4167},
    "Surakarta (Solo)": {"lat": -7.5707, "lon": 110.8297},
    "Pekalongan": {"lat": -6.8886, "lon": 109.6753},
    "Tegal": {"lat": -6.8676, "lon": 109.1371},
    "Yogyakarta": {"lat": -7.7956, "lon": 110.3695},
    "Surabaya": {"lat": -7.2575, "lon": 112.7521},
    "Malang": {"lat": -7.9839, "lon": 112.6214},
    "Jember": {"lat": -8.1845, "lon": 113.6681},
    "Banyuwangi": {"lat": -8.2192, "lon": 114.3691},
}

# =============================================================================
# ENGINE PERHITUNGAN
# =============================================================================

class FalakEngine:
    def __init__(self):
        self.planets = load('de421.bsp')
        self.earth = self.planets['earth']
        self.sun = self.planets['sun']
        self.ts = load.timescale()

    def get_sun_alt(self, t, pengamat):
        astrometric = pengamat.at(t).observe(self.sun).apparent()
        alt, az, d = astrometric.altaz()
        return alt.degrees

    def find_time_at_alt(self, target_alt, pengamat, t_start_utc, t_end_utc, direction="falling"):
        low, high = t_start_utc, t_end_utc
        for _ in range(20):
            mid = low + (high - low) / 2
            t_mid = self.ts.from_datetime(mid.replace(tzinfo=datetime.timezone.utc))
            current_alt = self.get_sun_alt(t_mid, pengamat)
            if direction == "falling":
                if current_alt > target_alt: low = mid
                else: high = mid
            else:
                if current_alt < target_alt: low = mid
                else: high = mid
        return low

    def calculate_all(self, lat, lon, offset, s_fajr, s_isha, ihtiyath_detik):
        lokasi = wgs84.latlon(lat, lon)
        pengamat = self.earth + lokasi
        now = datetime.datetime.now(datetime.timezone.utc)
        base_day = now.replace(hour=0, minute=0, second=0, microsecond=0) - timedelta(hours=offset)
        
        # Dzuhur
        times = [base_day + timedelta(minutes=m) for m in range(600, 900)]
        dzuhur_utc = max(times, key=lambda t: self.get_sun_alt(self.ts.from_datetime(t), pengamat))
        z_alt = self.get_sun_alt(self.ts.from_datetime(dzuhur_utc), pengamat)
        
        # Ashar, Maghrib, Isya
        ashar_alt = math.degrees(math.atan(1 / (1 + math.tan(math.radians(90 - z_alt)))))
        ashar_utc = self.find_time_at_alt(ashar_alt, pengamat, dzuhur_utc, dzuhur_utc + timedelta(hours=4), "falling")
        maghrib_utc = self.find_time_at_alt(-0.833, pengamat, dzuhur_utc, dzuhur_utc + timedelta(hours=8), "falling")
        isya_utc = self.find_time_at_alt(s_isha, pengamat, maghrib_utc, maghrib_utc + timedelta(hours=4), "falling")
        
        # Terbit, Subuh
        terbit_utc = self.find_time_at_alt(-0.833, pengamat, base_day, dzuhur_utc, "rising")
        subuh_utc = self.find_time_at_alt(s_fajr, pengamat, base_day, terbit_utc, "rising")

        # Fungsi konversi dengan Ihtiyath dalam detik
        def to_local(utc_dt, add_ihtiyath=True):
            buffer = timedelta(seconds=ihtiyath_detik) if add_ihtiyath else timedelta(0)
            return (utc_dt + timedelta(hours=offset) + buffer).strftime("%H:%M:%S")

        return {
            "Subuh": to_local(subuh_utc), 
            "Terbit": to_local(terbit_utc, add_ihtiyath=False), # Terbit biasanya tanpa ihtiyath positif
            "Dzuhur": to_local(dzuhur_utc), 
            "Ashar": to_local(ashar_utc),
            "Maghrib": to_local(maghrib_utc), 
            "Isya": to_local(isya_utc),
            "Kiblat": self.get_kiblat(lat, lon)
        }

    def get_kiblat(self, lat, lon):
        phi1, lam1 = math.radians(lat), math.radians(lon)
        phi2, lam2 = math.radians(21.4225), math.radians(39.8262)
        y = math.sin(lam2 - lam1)
        x = math.cos(phi1)*math.tan(phi2) - math.sin(phi1)*math.cos(lam2-lam1)
        return f"{(math.degrees(math.atan2(y, x)) + 360) % 360:.2f}°"

# =============================================================================
# GUI INTERFACE
# =============================================================================

class FalakAppPrecision:
    def __init__(self, root):
        self.root = root
        self.root.title("Falak Pro v3.4 - Ihtiyath Precision")
        self.root.geometry("500x720")
        self.root.configure(bg="#020617")
        self.engine = FalakEngine()
        self.setup_ui()

    def setup_ui(self):
        # Header
        top = tk.Frame(self.root, bg="#020617", pady=10)
        top.pack(fill=tk.X)
        tk.Label(top, text="JADWAL SALAT PRESISI", font=("Segoe UI", 16, "bold"), fg="#38bdf8", bg="#020617").pack()

        # Input Area
        inp = tk.Frame(self.root, bg="#020617", padx=30)
        inp.pack(fill=tk.X)

        # Kriteria
        tk.Label(inp, text="Kriteria:", fg="#94a3b8", bg="#020617").grid(row=0, column=0, sticky=tk.W, pady=2)
        self.std_var = tk.StringVar(value="Kemenag RI (Subuh -20°)")
        ttk.Combobox(inp, textvariable=self.std_var, state="readonly", values=["Kemenag RI (Subuh -20°)", "Muhammadiyah (Subuh -18°)"], width=25).grid(row=0, column=1, sticky=tk.W, pady=2)

        # Kota
        tk.Label(inp, text="Kota:", fg="#94a3b8", bg="#020617").grid(row=1, column=0, sticky=tk.W, pady=2)
        self.city_var = tk.StringVar(value="Semarang")
        ttk.Combobox(inp, textvariable=self.city_var, values=sorted(list(DATA_JAWA.keys())), state="readonly", width=25).grid(row=1, column=1, sticky=tk.W, pady=2)

        # IHTIYATH (DETIK) - Fitur Baru
        tk.Label(inp, text="Ihtiyath (Detik):", fg="#38bdf8", bg="#020617", font=("Segoe UI", 9, "bold")).grid(row=2, column=0, sticky=tk.W, pady=2)
        self.ihtiyath_var = tk.StringVar(value="60")
        self.ent_ihtiyath = ttk.Entry(inp, textvariable=self.ihtiyath_var, width=10)
        self.ent_ihtiyath.grid(row=2, column=1, sticky=tk.W, pady=2)

        # Button
        tk.Button(self.root, text="HITUNG JADWAL", command=self.process, bg="#0ea5e9", fg="white", 
                  font=("Segoe UI", 10, "bold"), relief=tk.FLAT, pady=8, cursor="hand2").pack(fill=tk.X, padx=50, pady=10)

        # Result Display (Compact Spacing 1)
        self.res_frame = tk.Frame(self.root, bg="#0f172a", bd=1, relief=tk.SOLID)
        self.res_frame.pack(fill=tk.BOTH, expand=True, padx=40, pady=5)

        self.displays = {}
        prayers = [("Subuh", "#ef4444"), ("Terbit", "#f59e0b"), ("Dzuhur", "#0ea5e9"), 
                   ("Ashar", "#818cf8"), ("Maghrib", "#ec4899"), ("Isya", "#8b5cf6")]
        
        for name, color in prayers:
            f = tk.Frame(self.res_frame, bg="#0f172a", pady=1) 
            f.pack(fill=tk.X, padx=25, pady=0)
            tk.Label(f, text=name, bg="#0f172a", fg="#94a3b8", font=("Segoe UI", 11)).pack(side=tk.LEFT)
            v = tk.StringVar(value="--:--:--")
            tk.Label(f, textvariable=v, bg="#0f172a", fg=color, font=("Consolas", 18, "bold")).pack(side=tk.RIGHT)
            self.displays[name] = v

        self.kiblat_lbl = tk.Label(self.root, text="Arah Kiblat: --", bg="#020617", fg="#38bdf8", font=("Segoe UI", 9, "bold"))
        self.kiblat_lbl.pack(pady=10)

    def process(self):
        try:
            city = self.city_var.get()
            lat, lon = DATA_JAWA[city]["lat"], DATA_JAWA[city]["lon"]
            s_fajr, s_isha = (-20, -18) if "Kemenag" in self.std_var.get() else (-18, -18)
            
            # Ambil nilai Ihtiyath dari input
            try:
                iht_sec = int(self.ihtiyath_var.get())
            except ValueError:
                iht_sec = 60
                self.ihtiyath_var.set("60")

            res = self.engine.calculate_all(lat, lon, 7, s_fajr, s_isha, iht_sec)
            
            for name, var in self.displays.items():
                var.set(res[name])
            self.kiblat_lbl.config(text=f"Arah Kiblat {city}: {res['Kiblat']}")
            
        except Exception as e:
            messagebox.showerror("Error", str(e))

if __name__ == "__main__":
    root = tk.Tk()
    app = FalakAppPrecision(root)
    root.mainloop()