menu12.py

Download
import os, datetime, math
import numpy as np
import customtkinter as ctk
from skyfield.api import Loader, wgs84
from tkinter import messagebox

BASE_DIR = os.path.dirname(os.path.abspath(__file__))

def safe_monthrange(year, month):
    is_leap = year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
    days_in_month = [31, 29 if is_leap else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
    return 0, days_in_month[month - 1]

class Menu12App(ctk.CTk):
    def __init__(self):
        super().__init__()
        self.title("Prayer Times (Modul 12)")
        self.geometry("1100x600")
        ctk.set_appearance_mode("Dark")
        
        self.load_obj = Loader(BASE_DIR)
        self.eph = self.load_obj('de421.bsp')
        self.ts = self.load_obj.timescale()
        self.setup_ui()

    def setup_ui(self):
        self.sidebar = ctk.CTkFrame(self, width=250)
        self.sidebar.pack(side="left", fill="y", padx=10, pady=10)
        
        now = datetime.datetime.now()
        self.entry_y = ctk.CTkEntry(self.sidebar, placeholder_text="Tahun"); self.entry_y.insert(0, str(now.year)); self.entry_y.pack(pady=2)
        self.entry_m = ctk.CTkEntry(self.sidebar, placeholder_text="Bulan"); self.entry_m.insert(0, str(now.month)); self.entry_m.pack(pady=2)
        self.entry_d = ctk.CTkEntry(self.sidebar, placeholder_text="Tanggal"); self.entry_d.insert(0, str(now.day)); self.entry_d.pack(pady=2)
        
        self.entry_lat = ctk.CTkEntry(self.sidebar, placeholder_text="Latitude"); self.entry_lat.insert(0, "-7.0667"); self.entry_lat.pack(pady=2)
        self.entry_lon = ctk.CTkEntry(self.sidebar, placeholder_text="Longitude"); self.entry_lon.insert(0, "110.4100"); self.entry_lon.pack(pady=2)
        self.entry_tz = ctk.CTkEntry(self.sidebar, placeholder_text="Timezone"); self.entry_tz.insert(0, "7.0"); self.entry_tz.pack(pady=2)
        self.entry_elev = ctk.CTkEntry(self.sidebar, placeholder_text="Elevasi"); self.entry_elev.insert(0, "230"); self.entry_elev.pack(pady=2)
        
        self.combo_method = ctk.CTkOptionMenu(self.sidebar, values=["Kemenag RI (Fajr 20°, Isha 18°)", "MUHAMMADIYAH (Fajr 18°, Isha 18°)"])
        self.combo_method.pack(pady=5)
        
        ctk.CTkButton(self.sidebar, text="▶ HITUNG JADWAL", command=self.hitung).pack(pady=15)
        self.textbox = ctk.CTkTextbox(self, font=("Consolas", 12), wrap="none")
        self.textbox.pack(side="right", fill="both", expand=True, padx=10, pady=10)

    def hitung(self):
        self.textbox.delete("1.0", "end")
        try:
            y, m, d = int(self.entry_y.get()), int(self.entry_m.get()), int(self.entry_d.get())
            lat, lon, tz, elev = float(self.entry_lat.get()), float(self.entry_lon.get()), float(self.entry_tz.get()), float(self.entry_elev.get())
        except ValueError:
            return

        method = self.combo_method.get()
        if "Kemenag" in method: f_ang, i_ang = -20.0, -18.0
        else: f_ang, i_ang = -18.0, -18.0

        loc = wgs84.latlon(lat, lon, elevation_m=elev)
        earth, sun = self.eph['earth'], self.eph['sun']
        
        days_to_calc = [d] # Mode Harian
        pad = 8
        self.textbox.insert("end", f"{'Date':<12} {'Fajer':^{pad}} {'Shuroq':^{pad}} {'Dhuha':^{pad}} {'Dhohur':^{pad}} {'Aser':^{pad}} {'Maghreb':^{pad}} {'Isha':^{pad}}\n{'-'*90}\n")
        
        def find_crossing(alt_arr, tt_arr, target_alt, direction='up'):
            diffs = alt_arr - target_alt
            for i in range(len(diffs)-1):
                if direction == 'up' and diffs[i] <= 0 and diffs[i+1] > 0:
                    f = abs(diffs[i]) / (abs(diffs[i]) + abs(diffs[i+1]) + 1e-9)
                    return tt_arr[i] + f * (tt_arr[i+1] - tt_arr[i])
                elif direction == 'down' and diffs[i] >= 0 and diffs[i+1] < 0:
                    f = abs(diffs[i]) / (abs(diffs[i]) + abs(diffs[i+1]) + 1e-9)
                    return tt_arr[i] + f * (tt_arr[i+1] - tt_arr[i])
            return None

        for d_c in days_to_calc:
            t0 = self.ts.utc(y, m, d_c, -int(tz))
            t1 = self.ts.utc(y, m, d_c, 24 - int(tz))
            tt_array = np.linspace(t0.tt, t1.tt, 2880) # Resolusi tinggi per menit
            
            alt_deg = (earth + loc).at(self.ts.tt_jd(tt_array)).observe(sun).apparent().altaz(temperature_C=25, pressure_mbar=1010)[0].degrees
            
            idx_noon = np.argmax(alt_deg)
            t_dhohur = tt_array[idx_noon]
            
            alt_am, tt_am = alt_deg[:idx_noon], tt_array[:idx_noon]
            alt_pm, tt_pm = alt_deg[idx_noon:], tt_array[idx_noon:]
            
            shadow_asr = 1.0 + math.tan(math.radians(max(0, 90.0 - alt_deg[idx_noon]))) # Standard Shafii
            alt_asr_tgt = math.degrees(math.atan(1.0 / shadow_asr))
            
            v_fajr = find_crossing(alt_am, tt_am, f_ang, 'up')
            v_shu = find_crossing(alt_am, tt_am, -0.833, 'up')
            v_dhu = find_crossing(alt_am, tt_am, 4.5, 'up')
            v_asr = find_crossing(alt_pm, tt_pm, alt_asr_tgt, 'down')
            v_mag = find_crossing(alt_pm, tt_pm, -0.833, 'down')
            v_ish = find_crossing(alt_pm, tt_pm, i_ang, 'down')
            
            def fmt(val, is_sh=False):
                if val is None: return "----"
                adj = (16 / 86400.0) # Ikhtiyat 16 detik
                val += (-adj if is_sh else adj)
                dt = self.ts.tt_jd(val + (tz/24.0))
                return f"{int(dt.utc[3]):02d}:{int(dt.utc[4]):02d}:{int(dt.utc[5]):02d}"

            line = f"{d_c:02d}/{m:02d}/{y}   {fmt(v_fajr):^{pad}} {fmt(v_shu, True):^{pad}} {fmt(v_dhu):^{pad}} {fmt(t_dhohur):^{pad}} {fmt(v_asr):^{pad}} {fmt(v_mag):^{pad}} {fmt(v_ish):^{pad}}\n"
            self.textbox.insert("end", line)

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