import os, datetime
import numpy as np
import customtkinter as ctk
from tkinter import messagebox
from skyfield.api import Loader, wgs84
from skyfield import almanac
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
def get_safe_events(t_obj, y_obj):
y_arr = np.atleast_1d(y_obj)
if len(y_arr) == 0: return []
events = []
for k in range(len(y_arr)):
try: t_val = t_obj[k]
except Exception: t_val = t_obj
events.append((t_val, int(y_arr[k])))
return events
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]
def format_tahun_aman(year):
return f"{year} M" if year > 0 else f"{abs(year-1)} SM"
class Menu18App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Planetary Times (Modul 18)")
self.geometry("900x600")
ctk.set_appearance_mode("Dark")
self.load_obj = Loader(BASE_DIR)
self.ephemeris_name = 'de421.bsp'
try:
self.eph = self.load_obj(self.ephemeris_name)
except Exception:
messagebox.showwarning("Peringatan", f"File {self.ephemeris_name} tidak ditemukan. Harap unduh terlebih dahulu.")
self.ts = self.load_obj.timescale()
self.setup_ui()
def auto_switch_ephemeris(self, target_year):
ephemeris_priority = [("de421.bsp", 1900, 2050), ("de442.bsp", 1550, 2650), ("de406.bsp", -3000, 3000)]
best_bsp = "de421.bsp"
for filename, min_yr, max_yr in ephemeris_priority:
if min_yr <= target_year <= max_yr and os.path.exists(os.path.join(BASE_DIR, filename)):
best_bsp = filename; break
if self.ephemeris_name != best_bsp:
try:
self.eph = self.load_obj(best_bsp)
self.ephemeris_name = best_bsp
except Exception: pass
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()
ctk.CTkLabel(self.sidebar, text="PLANETARY TIMES", font=("Segoe UI", 16, "bold")).pack(pady=20)
self.combo_target = ctk.CTkOptionMenu(self.sidebar, values=["Mercury", "Venus", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune", "Pluto"])
self.combo_target.pack(pady=5)
self.entry_y = ctk.CTkEntry(self.sidebar, placeholder_text="Tahun"); self.entry_y.insert(0, str(now.year)); self.entry_y.pack(pady=5)
self.entry_m = ctk.CTkEntry(self.sidebar, placeholder_text="Bulan"); self.entry_m.insert(0, str(now.month)); self.entry_m.pack(pady=5)
self.entry_lat = ctk.CTkEntry(self.sidebar, placeholder_text="Latitude"); self.entry_lat.insert(0, "-7.0667"); self.entry_lat.pack(pady=5)
self.entry_lon = ctk.CTkEntry(self.sidebar, placeholder_text="Longitude"); self.entry_lon.insert(0, "110.4100"); self.entry_lon.pack(pady=5)
self.entry_elev = ctk.CTkEntry(self.sidebar, placeholder_text="Elevasi (m)"); self.entry_elev.insert(0, "230"); self.entry_elev.pack(pady=5)
self.entry_tz = ctk.CTkEntry(self.sidebar, placeholder_text="Timezone"); self.entry_tz.insert(0, "7.0"); self.entry_tz.pack(pady=5)
ctk.CTkButton(self.sidebar, text="▶ HITUNG JADWAL", command=self.hitung).pack(pady=20)
self.textbox = ctk.CTkTextbox(self, font=("Consolas", 14), 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:
year, month = int(self.entry_y.get()), int(self.entry_m.get())
lat, lon = float(self.entry_lat.get()), float(self.entry_lon.get())
elev, tz = float(self.entry_elev.get()), float(self.entry_tz.get())
target_name = self.combo_target.get().lower()
except ValueError:
messagebox.showerror("Error", "Input tidak valid.")
return
self.auto_switch_ephemeris(year)
target_key = f'{target_name} barycenter' if target_name != 'moon' else 'moon'
try:
target_obj = self.eph[target_key]
except KeyError:
target_obj = self.eph[target_name] # Fallback
earth = self.eph['earth']
loc = wgs84.latlon(lat, lon, elevation_m=elev)
_, num_days = safe_monthrange(year, month)
t0 = self.ts.utc(year, month, 1, -int(tz))
t1 = self.ts.utc(year, month, num_days, 24 - int(tz))
f_rs = almanac.risings_and_settings(self.eph, target_obj, loc)
t_rs, y_rs = almanac.find_discrete(t0, t1, f_rs)
report = f"[ Planetary Times: {target_name.upper()} ]\n\n"
report += f" Bulan/Tahun : {month:02d}/{format_tahun_aman(year)}\n"
report += f" Lokasi : Lat {lat}, Lon {lon}, TZ UTC{'+' if tz >= 0 else ''}{tz}\n"
report += "="*70 + "\n"
report += " Tanggal Terbit (Rise) Terbenam (Set)\n"
report += "-"*70 + "\n"
# Loop setiap hari
for d in range(1, num_days + 1):
found_rise, found_set = "----", "----"
if t_rs is not None:
for t_val, y_val in get_safe_events(t_rs, y_rs):
t_loc = self.ts.tt_jd(t_val.tt + (tz / 24.0))
_, _, dl, hl, mnl, _ = t_loc.utc
if int(dl) == d:
time_str = f"{int(hl):02d}:{int(mnl):02d}"
if y_val == 1: found_rise = time_str
else: found_set = time_str
date_display = f"{d:02d}/{month:02d}/{format_tahun_aman(year)}"
report += f" {date_display:<18} {found_rise:^15} {found_set:^15}\n"
report += "="*70 + "\n* Calculated using JPL NASA Engine."
self.textbox.insert("end", report)
if __name__ == "__main__":
app = Menu18App()
app.mainloop()