import tkinter as tk
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
from matplotlib.patches import Wedge
from datetime import datetime, timedelta
import pytz
import math
from tzlocal import get_localzone # To fetch local timezone
from ntplib import NTPClient # For NTP synchronization
from zoneinfo import ZoneInfo # For timezone handling
# Function to synchronize time with an NTP server
def get_ntp_time():
client = NTPClient()
try:
# We are using a public NTP server
response = client.request('pool.ntp.org')
ntp_time = datetime.utcfromtimestamp(response.tx_time) # UTC time
return ntp_time.replace(tzinfo=ZoneInfo("UTC")) # Set the NTP time to UTC
except Exception as e:
print(f"Error while getting NTP time: {e}")
return datetime.now() # Fallback to system time if NTP fails
# Get the current time in the local timezone (based on NTP time)
def get_local_time():
local_tz = get_localzone() # Automatically fetch the local timezone
ntp_time = get_ntp_time() # Get the current time using NTP
return ntp_time.astimezone(local_tz) # Convert NTP time to local time
# Function to draw the clock
def draw_clock(ax, current_time, busy_slots, start_of_day, end_of_day):
ax.clear() # Clear the previous drawing on the canvas
# Set the aspect ratio of the clock to be equal (circular)
ax.set_aspect(1)
ax.set_axis_off()
# Draw the clock circle
ax.add_patch(plt.Circle((0.5, 0.5), 0.45, color='lightgray'))
# Draw the hour and minute ticks (24-hour clock)
for i in range(24):
angle = math.radians(i * 15) # Each hour is 15 degrees (360 degrees / 24 hours)
# Menggeser angka jam ke kanan sebanyak 19 jam (menggeser posisi jam)
adjusted_angle = math.radians((i + 19) % 24 * 15) # Shift by 19 hours (9 * 15 degrees)
x_start = 0.5 + 0.45 * math.cos(adjusted_angle)
y_start = 0.5 + 0.45 * math.sin(adjusted_angle)
x_end = 0.5 + 0.4 * math.cos(adjusted_angle)
y_end = 0.5 + 0.4 * math.sin(adjusted_angle)
ax.plot([x_start, x_end], [y_start, y_end], color='black', lw=2)
# Add the hour number (1-24)
hour_label = str((i + 1) % 24) if i != 23 else '00' # Show 24-hour time format (1-24)
ax.text(x_end, y_end, hour_label, color='black', ha='center', va='center', fontsize=16)
# Draw the time hands (adjusted to be proportional)
current_seconds = current_time.second + current_time.minute * 60 + current_time.hour * 3600
total_seconds_in_day = (end_of_day - start_of_day).total_seconds()
# Hour hand calculation (clockwise direction from top, 12 o'clock position)
hour_angle = (current_seconds / total_seconds_in_day) * 360 - 90 # Subtract 90 to adjust to 12 o'clock
ax.plot([0.5, 0.5 + 0.3 * math.cos(math.radians(hour_angle))],
[0.5, 0.5 + 0.3 * math.sin(math.radians(hour_angle))],
lw=6, color="black")
# Minute hand calculation (clockwise direction from top, 12 o'clock position)
#minute_angle = ((current_seconds % 3600) / 60) / 60 * 360 - 90 # Subtract 90 for 12 o'clock
#ax.plot([0.5, 0.5 + 0.4 * math.cos(math.radians(minute_angle))],
# [0.5, 0.5 + 0.4 * math.sin(math.radians(minute_angle))],
# lw=4, color="blue")
# Second hand calculation (clockwise direction from top, 12 o'clock position)
second_angle = ((current_seconds % 60) / 60) * 360 - 90 # Subtract 90 for 12 o'clock
ax.plot([0.5, 0.5 + 0.45 * math.cos(math.radians(second_angle))],
[0.5, 0.5 + 0.45 * math.sin(math.radians(second_angle))],
lw=2, color="red")
# Mark busy/free time slots (the colored wedges) and activity names
for busy_start, busy_end, color, activity in busy_slots:
# If the end time is earlier than the start time, it means the activity spans across midnight
if busy_end < busy_start:
# Adjust the end time by adding 24 hours to handle the crossing over midnight
busy_end = busy_end + timedelta(days=1)
start_angle = (busy_start - start_of_day).total_seconds() / total_seconds_in_day * 360 - 90
end_angle = (busy_end - start_of_day).total_seconds() / total_seconds_in_day * 360 - 90
ax.add_patch(Wedge((0.5, 0.5), 0.45, start_angle, end_angle, color=color, alpha=0.7))
# Calculate position for activity text
angle_mid = (start_angle + end_angle) / 2
x_text = 0.5 + 0.5 * math.cos(math.radians(angle_mid)) # Adjust to correct position
y_text = 0.5 + 0.5 * math.sin(math.radians(angle_mid)) # Adjust to correct position
# Add activity description with large font and contrasting color
ax.text(x_text, y_text, activity, color='black', ha='center', va='center', fontsize=12, fontweight='bold')
# Add time labels for activity start and finish
start_time_label = busy_start.strftime("%H:%M")
end_time_label = busy_end.strftime("%H:%M")
# Display start and end time
ax.text(x_text, y_text - 0.05, f"{start_time_label} - {end_time_label}", color='black', ha='center', va='center', fontsize=10)
# Add title and date-time info (adjusting y position to place outside the circle)
ax.text(0.5, 1.1, "Jadwal Hamba Allah", color='black', ha='center', va='center', fontsize=16, fontweight='bold')
date_text = current_time.strftime("%A, %d-%m-%Y")
ax.text(0.5, -0.1, date_text, color='black', ha='center', va='center', fontsize=14)
# Main function to update the clock
def update_clock():
current_time = get_local_time() # Get local time from NTP
# Set `start_of_day` and `end_of_day` to be timezone-aware
local_tz = get_localzone() # Fetch the local timezone
start_of_day = datetime(current_time.year, current_time.month, current_time.day, 0, 0, 0, 0)
start_of_day = start_of_day.replace(tzinfo=local_tz) # Apply local timezone to start_of_day
end_of_day = start_of_day.replace(hour=23, minute=59, second=59, microsecond=0)
# Define the busy slots and corresponding colors
busy_slots = [
(datetime(current_time.year, current_time.month, current_time.day, 5, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 7, 0, 0, tzinfo=local_tz), 'white', 'Wake up & \nMorning routine'),
(datetime(current_time.year, current_time.month, current_time.day, 7, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 8, 0, 0, tzinfo=local_tz), 'gray', 'Go to work'),
(datetime(current_time.year, current_time.month, current_time.day, 8, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 12, 0, 0, tzinfo=local_tz), 'white', 'Work'),
(datetime(current_time.year, current_time.month, current_time.day, 12, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 13, 0, 0, tzinfo=local_tz), 'gray', 'Lunch break'),
(datetime(current_time.year, current_time.month, current_time.day, 13, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 17, 0, 0, tzinfo=local_tz), 'white', 'Continue to work'),
(datetime(current_time.year, current_time.month, current_time.day, 17, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 18, 0, 0, tzinfo=local_tz), 'gray', 'Home from work'),
(datetime(current_time.year, current_time.month, current_time.day, 18, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 19, 00, 0, tzinfo=local_tz), 'white', 'Dinner'),
(datetime(current_time.year, current_time.month, current_time.day, 19, 00, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 22, 0, 0, tzinfo=local_tz), 'gray', 'Free time to relax'),
(datetime(current_time.year, current_time.month, current_time.day, 22, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day, 23, 0, 0, tzinfo=local_tz), 'white', 'Prepare for bed'),
# Adjust the sleep time slot to span across midnight
(datetime(current_time.year, current_time.month, current_time.day, 23, 0, 0, tzinfo=local_tz), datetime(current_time.year, current_time.month, current_time.day + 1, 5, 0, 0, tzinfo=local_tz), 'gray', 'Take a break to sleep'),
]
total_seconds_in_day = (end_of_day - start_of_day).total_seconds()
# Redraw the clock
draw_clock(ax, current_time, busy_slots, start_of_day, end_of_day)
canvas.draw() # Redraw the canvas with updated clock
# Update the clock every second (1000 milliseconds)
root.after(1000, update_clock)
# Initialize Tkinter window
root = tk.Tk()
root.title("Time and Schedule Clock")
# Create a figure and axis only once (not inside update_clock)
fig, ax = plt.subplots(figsize=(8, 8)) # Increased figure size for a larger clock
# Embed the plot into the Tkinter window
canvas = FigureCanvasTkAgg(fig, master=root)
canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True) # Ensure the canvas resizes with the window
# Start the clock update process
update_clock()
# Run the Tkinter event loop
root.mainloop()