import os, datetime, json
import customtkinter as ctk
from tkinter import filedialog, messagebox
from PIL import Image, ImageDraw, ImageFont
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
BULAN_MASEHI = ["Januari", "Februari", "Maret", "April", "Mei", "Juni", "Juli", "Agustus", "September", "Oktober", "November", "Desember"]
BULAN_HIJRIAH = ["Muharam", "Safar", "Rabiulawal", "Rabiulakhir", "Jumadilawal", "Jumadilakhir", "Rajab", "Syakban", "Ramadan", "Syawal", "Zulkaidah", "Zulhijah"]
# ---> SILAKAN COPAS HIJRI_DB LENGKAP DI SINI <---
HIJRI_DB = {
1447: [["Muharam", "", "26-Jun-2025", 30], ["Safar", "", "26-Jul-2025", 29], ["Rabiulawal", "", "24-Aug-2025", 30], ["Rabiulakhir", "", "23-Sep-2025", 30], ["Jumadilawal", "", "23-Oct-2025", 29], ["Jumadilakhir", "", "21-Nov-2025", 30], ["Rajab", "", "21-Dec-2025", 30], ["Syakban", "", "20-Jan-2026", 29], ["Ramadan", "", "18-Feb-2026", 30], ["Syawal", "", "20-Mar-2026", 29], ["Zulkaidah", "", "18-Apr-2026", 30], ["Zulhijah", "", "18-May-2026", 29]]
}
try:
DB_JSON_PATH = os.path.join(BASE_DIR, "db_hijriah.json")
if os.path.exists(DB_JSON_PATH):
with open(DB_JSON_PATH, "r", encoding="utf-8") as f:
HIJRI_DB.update({int(k): v for k, v in json.load(f).items()})
except Exception: pass
class Menu26App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Kalender Hijriah Berjalan (Modul 26)")
self.geometry("1100x750")
ctk.set_appearance_mode("Dark")
self.cal_h_year = 1447
self.cal_h_month = 0
self.setup_ui()
self.render_kalender()
def setup_ui(self):
self.sidebar = ctk.CTkFrame(self, width=250)
self.sidebar.pack(side="left", fill="y", padx=10, pady=10)
ctk.CTkLabel(self.sidebar, text="KALENDER HIJRIAH", font=("Segoe UI", 16, "bold"), text_color="#00E5FF").pack(pady=20)
self.combo_bln = ctk.CTkComboBox(self.sidebar, values=BULAN_HIJRIAH)
self.combo_bln.set(BULAN_HIJRIAH[self.cal_h_month])
self.combo_bln.pack(pady=10, padx=10, fill="x")
self.entry_thn = ctk.CTkEntry(self.sidebar, justify="center")
self.entry_thn.insert(0, str(self.cal_h_year))
self.entry_thn.pack(pady=10, padx=10, fill="x")
ctk.CTkButton(self.sidebar, text="🔍 Terapkan", command=self.apply_kalender).pack(pady=10, fill="x", padx=10)
# Frame Output Kanan
self.main_frame = ctk.CTkFrame(self, fg_color="transparent")
self.main_frame.pack(side="right", fill="both", expand=True, padx=10, pady=10)
header_cal = ctk.CTkFrame(self.main_frame, fg_color="#101018", corner_radius=8)
header_cal.pack(fill="x", pady=(0, 10))
ctk.CTkButton(header_cal, text="◀ Seb", width=80, command=self.prev_month).pack(side="left", padx=10, pady=10)
self.lbl_title = ctk.CTkLabel(header_cal, text="", font=("Segoe UI", 22, "bold"), text_color="#00E5FF", justify="center")
self.lbl_title.pack(side="left", expand=True)
ctk.CTkButton(header_cal, text="Ber ▶", width=80, command=self.next_month).pack(side="right", padx=10, pady=10)
# Tombol Cetak
frame_export = ctk.CTkFrame(self.main_frame, fg_color="transparent")
frame_export.pack(fill="x", pady=(0, 10))
ctk.CTkButton(frame_export, text="📄 Cetak PDF", fg_color="#D32F2F", command=lambda: self.export_kalender("pdf")).pack(side="right", padx=5)
ctk.CTkButton(frame_export, text="📸 Simpan PNG", fg_color="#F57C00", command=lambda: self.export_kalender("png")).pack(side="right", padx=5)
self.grid_cal_frame = ctk.CTkFrame(self.main_frame, fg_color="#050510", corner_radius=10)
self.grid_cal_frame.pack(fill="both", expand=True)
def apply_kalender(self):
try:
self.cal_h_year = int(self.entry_thn.get())
self.cal_h_month = BULAN_HIJRIAH.index(self.combo_bln.get())
self.render_kalender()
except ValueError: messagebox.showerror("Error", "Tahun harus angka!")
def prev_month(self):
self.cal_h_month -= 1
if self.cal_h_month < 0:
self.cal_h_month = 11; self.cal_h_year -= 1
if self.cal_h_year not in HIJRI_DB:
self.cal_h_year += 1; self.cal_h_month = 0
return messagebox.showinfo("Batas", "Batas awal database tercapai.")
self.render_kalender()
def next_month(self):
self.cal_h_month += 1
if self.cal_h_month > 11:
self.cal_h_month = 0; self.cal_h_year += 1
if self.cal_h_year not in HIJRI_DB:
self.cal_h_year -= 1; self.cal_h_month = 11
return messagebox.showinfo("Batas", "Batas akhir database tercapai.")
self.render_kalender()
def _get_islamic_event(self, h_day, h_month, weekday):
events = []
color, is_haram = None, False
if (h_month == 10 and h_day == 1) or (h_month == 12 and h_day == 10) or (h_month == 12 and h_day in [11, 12, 13]):
color, is_haram = "#FF5252", True
if not is_haram:
if h_month == 1 and h_day in [9, 10]: color = "#00E676"
if h_month == 12 and h_day in [8, 9]: color = "#00E676"
if h_month == 9: color = "#8BC34A"
if h_day in [13, 14, 15] and h_month != 9: color = "#00B0FF"
if weekday in [0, 3] and h_month != 9 and not color: color = "#FFA000"
return color
def render_kalender(self):
for widget in self.grid_cal_frame.winfo_children(): widget.destroy()
y, m = self.cal_h_year, self.cal_h_month
if y not in HIJRI_DB: return
m_data = HIJRI_DB[y][m]
nama_bulan, _, start_date_str, jumlah_hari = m_data
bulan_map = {"Jan":1, "Feb":2, "Mar":3, "Apr":4, "May":5, "Jun":6, "Jul":7, "Aug":8, "Sep":9, "Oct":10, "Nov":11, "Dec":12}
parts = start_date_str.split('-')
start_date = datetime.date(int(parts[2]), bulan_map.get(parts[1], 1), int(parts[0]))
end_date = start_date + datetime.timedelta(days=jumlah_hari-1)
self.lbl_title.configure(text=f"{nama_bulan} {y} H\n({BULAN_MASEHI[start_date.month-1]} {start_date.year})")
self.combo_bln.set(BULAN_HIJRIAH[m])
self.entry_thn.delete(0, 'end'); self.entry_thn.insert(0, str(y))
days_header = ["Ahad", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"]
for col, day_name in enumerate(days_header):
ctk.CTkLabel(self.grid_cal_frame, text=day_name, font=("Segoe UI", 14, "bold"), fg_color="#1E88E5", corner_radius=5).grid(row=0, column=col, padx=3, pady=5, sticky="nsew")
offset = (start_date.weekday() + 1) % 7
row, col = 1, offset
curr_g = start_date
for h_day in range(1, jumlah_hari + 1):
cell = ctk.CTkFrame(self.grid_cal_frame, fg_color="#1E1E1E", corner_radius=8, border_width=1, border_color="#424242")
cell.grid(row=row, column=col, padx=4, pady=4, sticky="nsew")
event_color = self._get_islamic_event(h_day, m + 1, curr_g.weekday())
color_h = event_color if event_color else "#FFD54F"
ctk.CTkLabel(cell, text=str(h_day), font=("Consolas", 32, "bold"), text_color=color_h).pack(expand=True)
m_str = f"{curr_g.day} {BULAN_MASEHI[curr_g.month-1][:3]} {curr_g.year}"
ctk.CTkLabel(cell, text=m_str, font=("Segoe UI", 11), text_color="#00E676" if col == 5 else "#B0BEC5").pack(side="bottom", anchor="e", padx=8, pady=3)
col += 1
if col > 6: col, row = 0, row + 1
curr_g += datetime.timedelta(days=1)
for i in range(7): self.grid_cal_frame.grid_columnconfigure(i, weight=1)
for i in range(7): self.grid_cal_frame.grid_rowconfigure(i, weight=1)
def _generate_image(self):
img = Image.new("RGB", (1400, 1000), "#0A0A10")
draw = ImageDraw.Draw(img)
try:
f_title = ImageFont.truetype("arialbd.ttf", 46)
f_sub = ImageFont.truetype("arial.ttf", 26)
f_day = ImageFont.truetype("arialbd.ttf", 22)
f_h = ImageFont.truetype("arialbd.ttf", 64)
f_m = ImageFont.truetype("arial.ttf", 16)
except:
f_title = f_sub = f_day = f_h = f_m = ImageFont.load_default()
y, m = self.cal_h_year, self.cal_h_month
m_data = HIJRI_DB[y][m]
nama_bulan, _, start_date_str, jumlah_hari = m_data
bulan_map = {"Jan":1, "Feb":2, "Mar":3, "Apr":4, "May":5, "Jun":6, "Jul":7, "Aug":8, "Sep":9, "Oct":10, "Nov":11, "Dec":12}
parts = start_date_str.split('-')
start_date = datetime.date(int(parts[2]), bulan_map.get(parts[1], 1), int(parts[0]))
draw.text((700, 60), f"Kalender Hijriah KHGT: {nama_bulan} {y} H", font=f_title, fill="#00E5FF", anchor="mm")
draw.text((700, 110), f"{BULAN_MASEHI[start_date.month-1]} {start_date.year}", font=f_sub, fill="#B0BEC5", anchor="mm")
start_x, start_y, cell_w, cell_h = 50, 180, 185, 120
days_header = ["Ahad", "Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu"]
for i, d_name in enumerate(days_header):
x0, y0, x1, y1 = start_x + i * cell_w, start_y, start_x + i * cell_w + cell_w - 10, start_y + 45
draw.rectangle([x0, y0, x1, y1], fill="#1E88E5", outline="#1565C0", width=2)
draw.text(((x0+x1)/2, (y0+y1)/2), d_name, font=f_day, fill="white", anchor="mm")
row, col = 0, (start_date.weekday() + 1) % 7
curr_g = start_date
grid_y_start = start_y + 60
for h_day in range(1, jumlah_hari + 1):
x0, y0 = start_x + col * cell_w, grid_y_start + row * cell_h
x1, y1 = x0 + cell_w - 10, y0 + cell_h - 10
draw.rectangle([x0, y0, x1, y1], fill="#1E1E1E", outline="#424242", width=2)
event_color = self._get_islamic_event(h_day, m + 1, curr_g.weekday())
color_hijri = event_color if event_color else "#FFD54F"
if event_color: draw.ellipse([x0+15, y0+15, x0+25, y0+25], fill=color_hijri)
draw.text(((x0+x1)/2, y0 + 40), str(h_day), font=f_h, fill=color_hijri, anchor="mm")
m_str = f"{curr_g.day} {BULAN_MASEHI[curr_g.month-1][:3]} {curr_g.year}"
draw.text((x1 - 10, y1 - 15), m_str, font=f_m, fill="#B0BEC5", anchor="rm")
col += 1
if col > 6: col, row = 0, row + 1
curr_g += datetime.timedelta(days=1)
draw.text((700, 960), "KHGT Times Engine V7.2 - By Kasmui", font=f_sub, fill="#555555", anchor="mm")
return img
def export_kalender(self, fmt):
img = self._generate_image()
fname = filedialog.asksaveasfilename(initialfile=f"Kalender_Hijriah_{self.cal_h_year}H.{fmt}", defaultextension=f".{fmt}")
if fname:
try:
img.save(fname, fmt.upper(), resolution=150.0 if fmt=="pdf" else None)
messagebox.showinfo("Sukses", f"Berhasil disimpan: {fname}")
except Exception as e: messagebox.showerror("Error", str(e))
if __name__ == "__main__":
app = Menu26App()
app.mainloop()