import math
import customtkinter as ctk
from datetime import datetime
from tkinter import messagebox
# Konfigurasi Tema Utama
ctk.set_appearance_mode("System")
ctk.set_default_color_theme("blue")
class AstroCalcApp(ctk.CTk):
def __init__(self):
super().__init__()
self.title("AstroCalc Pro - Modern Edition")
self.geometry("550x700")
# Konfigurasi Grid Utama agar responsif
self.grid_rowconfigure(0, weight=1)
self.grid_columnconfigure(0, weight=1)
# --- Frame Utama dengan Fitur Scroll ---
self.main_scroll = ctk.CTkScrollableFrame(self, corner_radius=0, fg_color="transparent")
self.main_scroll.grid(row=0, column=0, sticky="nsew", padx=5, pady=5)
self.main_scroll.grid_columnconfigure(0, weight=1)
# --- Header ---
self.header_frame = ctk.CTkFrame(self.main_scroll, fg_color="transparent")
self.header_frame.pack(pady=(30, 20))
self.header_label = ctk.CTkLabel(self.header_frame, text="ASTROCALC PRO",
font=ctk.CTkFont(size=28, weight="bold"))
self.header_label.pack()
self.sub_header = ctk.CTkLabel(self.header_frame, text="Solusi Astronomi Presisi Tinggi",
text_color="#3b8ed0", font=ctk.CTkFont(size=14))
self.sub_header.pack()
# --- Bagian Input ---
self.container_input = ctk.CTkFrame(self.main_scroll)
self.container_input.pack(pady=10, padx=30, fill="x")
self.create_label_input(self.container_input, "Tanggal Pengamatan (YYYY-MM-DD):",
datetime.now().strftime("%Y-%m-%d"), "entry_date")
self.create_label_input(self.container_input, "Garis Lintang (Latitude):", "-7.0", "entry_lat")
self.create_label_input(self.container_input, "Garis Bujur (Longitude):", "110.4", "entry_lon")
self.create_label_input(self.container_input, "Zona Waktu (GMT Offset):", "7", "entry_tz")
# Tombol Analisis
self.btn_calculate = ctk.CTkButton(self.main_scroll, text="MULAI ANALISIS DATA",
command=self.process_calculation, height=50,
font=ctk.CTkFont(size=15, weight="bold"),
corner_radius=10)
self.btn_calculate.pack(pady=25, padx=30, fill="x")
# --- Bagian Hasil (Grid Card) ---
self.result_container = ctk.CTkFrame(self.main_scroll, fg_color="transparent")
self.result_container.pack(pady=(0, 40), padx=30, fill="x")
self.result_container.grid_columnconfigure((0, 1), weight=1)
self.res_sunrise = self.create_card(self.result_container, "MATAHARI TERBIT", "00:00", 0, 0)
self.res_sunset = self.create_card(self.result_container, "MATAHARI TERBENAM", "00:00", 0, 1)
self.res_day = self.create_card(self.result_container, "DURASI SIANG", "0j 0m", 1, 0)
self.res_night = self.create_card(self.result_container, "DURASI MALAM", "0j 0m", 1, 1)
def create_label_input(self, parent, text, placeholder, attr_name):
lbl = ctk.CTkLabel(parent, text=text, font=ctk.CTkFont(size=12, weight="bold"))
lbl.pack(pady=(15, 0), padx=25, anchor="w")
entry = ctk.CTkEntry(parent, placeholder_text=placeholder, height=35)
entry.pack(pady=(5, 10), padx=20, fill="x")
if attr_name == "entry_date": entry.insert(0, placeholder)
setattr(self, attr_name, entry)
def create_card(self, parent, label, value, row, col):
card = ctk.CTkFrame(parent, corner_radius=15, border_width=1, border_color="#3b8ed0")
card.grid(row=row, column=col, padx=8, pady=8, sticky="nsew")
lbl = ctk.CTkLabel(card, text=label, font=ctk.CTkFont(size=10, weight="bold"), text_color="gray")
lbl.pack(pady=(15, 0))
val = ctk.CTkLabel(card, text=value, font=ctk.CTkFont(size=20, weight="bold"))
val.pack(pady=(0, 15))
return val
def calculate_logic(self, lat, lon, tz, date_str):
try:
date_obj = datetime.strptime(date_str, "%Y-%m-%d")
rad, deg = math.pi / 180, 180 / math.pi
jd = (date_obj.timestamp() / 86400.0) + 2440587.5
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
dec_rad = math.asin(math.sin(e * rad) * math.sin(L * rad))
ra = (math.atan2(math.cos(e * rad) * math.sin(L * rad), math.cos(L * rad)) * deg + 360) % 360
eqt = (q / 15.0) - (ra / 15.0)
h_rad, lat_rad = -0.8333 * rad, lat * rad
cos_t = (math.sin(h_rad) - math.sin(lat_rad) * math.sin(dec_rad)) / (math.cos(lat_rad) * math.cos(dec_rad))
if cos_t > 1: return "Polar Night"
if cos_t < -1: return "Midnight Sun"
t_hour = (math.acos(cos_t) * deg) / 15.0
transit = 12 + tz - (lon / 15.0) - eqt
return {
"sunrise": (transit - t_hour + 24) % 24,
"sunset": (transit + t_hour + 24) % 24,
"day": t_hour * 2,
"night": 24 - (t_hour * 2)
}
except: return None
def format_time(self, decimal_hours):
hrs = int(decimal_hours)
mins = round((decimal_hours - hrs) * 60)
if mins == 60: hrs += 1; mins = 0
return f"{hrs:02d}:{mins:02d}"
def process_calculation(self):
try:
res = self.calculate_logic(float(self.entry_lat.get()), float(self.entry_lon.get()),
float(self.entry_tz.get()), self.entry_date.get())
if res in ["Polar Night", "Midnight Sun"]:
messagebox.showinfo("Info", res)
elif res:
self.res_sunrise.configure(text=self.format_time(res['sunrise']))
self.res_sunset.configure(text=self.format_time(res['sunset']))
self.res_day.configure(text=f"{int(res['day'])}j {round((res['day']%1)*60)}m")
self.res_night.configure(text=f"{int(res['night'])}j {round((res['night']%1)*60)}m")
else:
messagebox.showerror("Error", "Gagal menghitung data!")
except ValueError:
messagebox.showerror("Error", "Harap masukkan angka yang valid!")
if __name__ == "__main__":
app = AstroCalcApp()
app.mainloop()