jadwal-shalat.py

Download
import math
import time
import datetime
import customtkinter as ctk
from tkinter import messagebox

# ==========================================
# KONFIGURASI TEMA UI MODERN
# ==========================================
ctk.set_appearance_mode("Light")
ctk.set_default_color_theme("blue")

class PrayerTimeApp(ctk.CTk):
    def __init__(self):
        super().__init__()

        # Setup Jendela Utama (Lebih Compact)
        self.title("Dashboard Jadwal Shalat v1.0")
        self.geometry("400x550")  # Dipersempit agar pas 1 layar
        self.minsize(380, 500)
        self.configure(fg_color="#f8fafc")

        self.grid_columnconfigure(0, weight=1)
        
        self.setup_ui()
        self.update_schedule()

    def setup_ui(self):
        # --- HEADER ---
        header_frame = ctk.CTkFrame(self, fg_color="transparent")
        # pady dikurangi signifikan
        header_frame.grid(row=0, column=0, padx=20, pady=(15, 5), sticky="ew")
        
        title_label = ctk.CTkLabel(header_frame, text="PRAYER TIMES", font=ctk.CTkFont(family="Segoe UI", size=22, weight="bold"), text_color="#2563eb")
        title_label.pack()
        
        self.lbl_date = ctk.CTkLabel(header_frame, text="Memuat Tanggal...", font=ctk.CTkFont(size=12), text_color="#64748b")
        self.lbl_date.pack(pady=0)

        # --- FORM INPUT ---
        input_frame = ctk.CTkFrame(self, corner_radius=12, fg_color="#f1f5f9", border_width=1, border_color="#e2e8f0")
        input_frame.grid(row=1, column=0, padx=25, pady=5, sticky="ew")
        input_frame.grid_columnconfigure((0, 1), weight=1)

        # Baris 1: Lintang & Bujur (Jarak dipersempit)
        ctk.CTkLabel(input_frame, text="LINTANG (LAT)", font=ctk.CTkFont(size=10, weight="bold"), text_color="#475569").grid(row=0, column=0, padx=(15, 5), pady=(10, 0), sticky="w")
        self.inp_lat = ctk.CTkEntry(input_frame, height=28, justify="center")
        self.inp_lat.grid(row=1, column=0, padx=(15, 5), pady=(2, 5), sticky="ew")
        self.inp_lat.insert(0, "-7.0")

        ctk.CTkLabel(input_frame, text="BUJUR (LON)", font=ctk.CTkFont(size=10, weight="bold"), text_color="#475569").grid(row=0, column=1, padx=(5, 15), pady=(10, 0), sticky="w")
        self.inp_lon = ctk.CTkEntry(input_frame, height=28, justify="center")
        self.inp_lon.grid(row=1, column=1, padx=(5, 15), pady=(2, 5), sticky="ew")
        self.inp_lon.insert(0, "110.4")

        # Baris 2: TZ & Elevasi
        ctk.CTkLabel(input_frame, text="TZ (GMT+)", font=ctk.CTkFont(size=10, weight="bold"), text_color="#475569").grid(row=2, column=0, padx=(15, 5), pady=0, sticky="w")
        self.inp_tz = ctk.CTkEntry(input_frame, height=28, justify="center")
        self.inp_tz.grid(row=3, column=0, padx=(15, 5), pady=(2, 10), sticky="ew")
        self.inp_tz.insert(0, "7")

        ctk.CTkLabel(input_frame, text="ELEVASI (m)", font=ctk.CTkFont(size=10, weight="bold"), text_color="#475569").grid(row=2, column=1, padx=(5, 15), pady=0, sticky="w")
        self.inp_elv = ctk.CTkEntry(input_frame, height=28, justify="center")
        self.inp_elv.grid(row=3, column=1, padx=(5, 15), pady=(2, 10), sticky="ew")
        self.inp_elv.insert(0, "10")

        # Tombol Update (Tinggi dikurangi)
        self.btn_calc = ctk.CTkButton(input_frame, text="UPDATE JADWAL", height=32, font=ctk.CTkFont(weight="bold", size=12), fg_color="#2563eb", hover_color="#1d4ed8", command=self.update_schedule)
        self.btn_calc.grid(row=4, column=0, columnspan=2, padx=15, pady=(0, 10), sticky="ew")

        # --- KARTU HASIL ---
        self.result_frame = ctk.CTkFrame(self, fg_color="transparent")
        self.result_frame.grid(row=2, column=0, padx=25, pady=5, sticky="nsew")
        self.result_frame.grid_columnconfigure(0, weight=1)

        self.prayer_labels = {}
        prayers = ["Subuh", "Zuhur", "Ashar", "Maghrib", "Isya"]
        
        for i, name in enumerate(prayers):
            # Tinggi kartu dikurangi, jarak antar kartu (pady) dikurangi
            card = ctk.CTkFrame(self.result_frame, height=36, corner_radius=8, fg_color="#ffffff", border_width=1, border_color="#e2e8f0")
            card.grid(row=i, column=0, pady=2, sticky="ew")
            card.grid_columnconfigure(1, weight=1)
            
            lbl_name = ctk.CTkLabel(card, text=name, font=ctk.CTkFont(size=14, weight="bold"), text_color="#1e293b")
            lbl_name.grid(row=0, column=0, padx=15, pady=6, sticky="w")
            
            lbl_time = ctk.CTkLabel(card, text="--:--", font=ctk.CTkFont(family="Courier New", size=16, weight="bold"), text_color="#2563eb")
            lbl_time.grid(row=0, column=1, padx=15, pady=6, sticky="e")
            
            self.prayer_labels[name] = lbl_time

    # ==========================================
    # LOGIKA ASTRONOMI & ALGORITMA SHALAT
    # ==========================================
    def get_julian_date(self):
        return (time.time() / 86400.0) + 2440587.5

    def format_time(self, decimal_hours):
        h = (decimal_hours + 24.0) % 24.0
        hours = int(h)
        minutes = int((h - hours) * 60)
        return f"{hours:02d}:{minutes:02d}"

    def get_T(self, h, lat, dec):
        rad = math.pi / 180.0
        deg = 180.0 / math.pi
        
        val = (math.sin(h * rad) - math.sin(lat * rad) * math.sin(dec * rad)) / (math.cos(lat * rad) * math.cos(dec * rad))
        if val < -1.0: val = -1.0
        if val > 1.0: val = 1.0
        
        return math.acos(val) * deg / 15.0

    def update_schedule(self):
        try:
            lat = float(self.inp_lat.get())
            lon = float(self.inp_lon.get())
            tz = float(self.inp_tz.get())
            elv = float(self.inp_elv.get())

            now = datetime.datetime.now()
            days = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Minggu"]
            months = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"]
            date_str = f"{days[now.weekday()]}, {now.day} {months[now.month-1]} {now.year}"
            self.lbl_date.configure(text=date_str)

            rad = math.pi / 180.0
            deg = 180.0 / math.pi
            jd = self.get_julian_date()

            d = jd - 2451545.0
            g = (357.529 + 0.98560028 * d) % 360
            q = (280.459 + 0.98564736 * d) % 360
            L = (q + 1.915 * math.sin(g * rad) + 0.020 * math.sin(2 * g * rad)) % 360
            e = 23.439 - 0.00000036 * d
            
            ra = math.atan2(math.cos(e * rad) * math.sin(L * rad), math.cos(L * rad)) * deg / 15.0
            dec = math.asin(math.sin(e * rad) * math.sin(L * rad)) * deg
            eqt = (q / 15.0) - ra

            transit = 12.0 + tz - (lon / 15.0) - eqt

            val_ashar = math.sin(math.atan(1.0 / (1.0 + math.tan(abs(lat - dec) * rad))))
            cos_t_ashar = (val_ashar - math.sin(lat * rad) * math.sin(dec * rad)) / (math.cos(lat * rad) * math.cos(dec * rad))
            if cos_t_ashar < -1.0: cos_t_ashar = -1.0
            elif cos_t_ashar > 1.0: cos_t_ashar = 1.0
            t_ashar = math.acos(cos_t_ashar) * deg / 15.0

            times = {
                "Subuh": transit - self.get_T(-20.0, lat, dec),
                "Zuhur": transit + (2.0 / 60.0),
                "Ashar": transit + t_ashar,
                "Maghrib": transit + self.get_T(-0.8333 - 0.0347 * math.sqrt(elv), lat, dec),
                "Isya": transit + self.get_T(-18.0, lat, dec)
            }

            for name, time_val in times.items():
                self.prayer_labels[name].configure(text=self.format_time(time_val))

        except ValueError:
            messagebox.showerror("Kesalahan", "Pastikan semua input berupa angka (desimal).")

if __name__ == "__main__":
    app = PrayerTimeApp()
    app.mainloop()