import os, datetime, json, calendar
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 HijriConverter:
@staticmethod
def parse_date(date_str):
try:
m = {"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 = date_str.split('-')
return datetime.date(int(parts[2]), m.get(parts[1], 1), int(parts[0]))
except: return None
@staticmethod
def get_hijri_date(today):
for year, months in HIJRI_DB.items():
for m_data in months:
start = HijriConverter.parse_date(m_data[2])
if start and start <= today < start + datetime.timedelta(days=m_data[3]):
return f"{(today - start).days + 1} {m_data[0]} {year} H"
return "N/A"
class Menu27App(ctk.CTk):
def __init__(self):
super().__init__()
self.title("Kalender Masehi Berjalan (Modul 27)")
self.geometry("1100x750")
ctk.set_appearance_mode("Dark")
now = datetime.date.today()
self.cal_m_year = now.year
self.cal_m_month = now.month
self.setup_ui()
self.render_kalmasehi()
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 MASEHI", font=("Segoe UI", 16, "bold"), text_color="#00E5FF").pack(pady=20)
self.combo_bln = ctk.CTkComboBox(self.sidebar, values=BULAN_MASEHI)
self.combo_bln.set(BULAN_MASEHI[self.cal_m_month-1])
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_m_year))
self.entry_thn.pack(pady=10, padx=10, fill="x")
ctk.CTkButton(self.sidebar, text="🔍 Terapkan", command=self.apply_kalmasehi).pack(pady=10, fill="x", padx=10)
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)
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_kalmasehi(self):
try:
self.cal_m_year = int(self.entry_thn.get())
self.cal_m_month = BULAN_MASEHI.index(self.combo_bln.get()) + 1
self.render_kalmasehi()
except ValueError: messagebox.showerror("Error", "Tahun harus angka!")
def prev_month(self):
self.cal_m_month -= 1
if self.cal_m_month < 1:
self.cal_m_month = 12; self.cal_m_year -= 1
self.render_kalmasehi()
def next_month(self):
self.cal_m_month += 1
if self.cal_m_month > 12:
self.cal_m_month = 1; self.cal_m_year += 1
self.render_kalmasehi()
def _get_islamic_event(self, h_day, h_month, weekday):
color = None
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]):
return "#FF5252" # Haram Puasa
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_kalmasehi(self):
for widget in self.grid_cal_frame.winfo_children(): widget.destroy()
y, m = self.cal_m_year, self.cal_m_month
_, jumlah_hari = calendar.monthrange(y, m)
start_date = datetime.date(y, m, 1)
self.lbl_title.configure(text=f"{BULAN_MASEHI[m-1]} {y}")
self.combo_bln.set(BULAN_MASEHI[m-1])
self.entry_thn.delete(0, 'end'); self.entry_thn.insert(0, str(y))
days_header = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Ahad"]
for col, day_name in enumerate(days_header):
color_bg = "#D84315" if col == 6 else "#E65100"
ctk.CTkLabel(self.grid_cal_frame, text=day_name, font=("Segoe UI", 14, "bold"), fg_color=color_bg, corner_radius=5).grid(row=0, column=col, padx=3, pady=5, sticky="nsew")
offset = start_date.weekday()
row, col = 1, offset
curr_g = start_date
for m_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")
color_masehi = "#FF5252" if col == 6 else "#00E5FF"
ctk.CTkLabel(cell, text=str(m_day), font=("Consolas", 32, "bold"), text_color=color_masehi).pack(expand=True)
h_str = HijriConverter.get_hijri_date(curr_g)
parts = h_str.split()
h_str_short = f"{parts[0]} {parts[1][:3]} {parts[2]} H" if len(parts) >= 4 else h_str
color_h = "#FFD54F"
if len(parts) >= 4:
try:
ev_color = self._get_islamic_event(int(parts[0]), BULAN_HIJRIAH.index(parts[1]) + 1, curr_g.weekday())
if ev_color: color_h = ev_color
except: pass
ctk.CTkLabel(cell, text=h_str_short, font=("Segoe UI", 11), text_color=color_h).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_m = ImageFont.truetype("arialbd.ttf", 64)
f_h = ImageFont.truetype("arial.ttf", 16)
except:
f_title = f_sub = f_day = f_m = f_h = ImageFont.load_default()
y, m = self.cal_m_year, self.cal_m_month
_, jumlah_hari = calendar.monthrange(y, m)
start_date = datetime.date(y, m, 1)
draw.text((700, 60), f"Kalender Masehi: {BULAN_MASEHI[m-1]} {y}", font=f_title, fill="#00E5FF", anchor="mm")
start_x, start_y, cell_w, cell_h = 50, 180, 185, 120
days_header = ["Senin", "Selasa", "Rabu", "Kamis", "Jumat", "Sabtu", "Ahad"]
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
color_bg = "#D84315" if i == 6 else "#E65100"
draw.rectangle([x0, y0, x1, y1], fill=color_bg, outline="#E65100", 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()
curr_g = start_date
grid_y_start = start_y + 60
for m_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)
color_masehi = "#FF5252" if col == 6 else "#00E5FF"
draw.text(((x0+x1)/2, y0 + 40), str(m_day), font=f_m, fill=color_masehi, anchor="mm")
h_str = HijriConverter.get_hijri_date(curr_g)
parts = h_str.split()
h_str_short = f"{parts[0]} {parts[1][:3]} {parts[2]} H" if len(parts) >= 4 else h_str
color_h = "#FFD54F"
if len(parts) >= 4:
try:
ev_color = self._get_islamic_event(int(parts[0]), BULAN_HIJRIAH.index(parts[1]) + 1, curr_g.weekday())
if ev_color:
color_h = ev_color
draw.ellipse([x1-25, y0+15, x1-15, y0+25], fill=color_h)
except: pass
draw.text((x1 - 10, y1 - 15), h_str_short, font=f_h, fill=color_h, 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_Masehi_{BULAN_MASEHI[self.cal_m_month-1]}_{self.cal_m_year}.{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 = Menu27App()
app.mainloop()