diff options
| author | kj-sh604 | 2024-08-01 03:20:35 -0400 |
|---|---|---|
| committer | kj-sh604 | 2024-08-01 03:20:35 -0400 |
| commit | 801c4121f4770bbbe2f77bdbe7b570deb67f6c1f (patch) | |
| tree | d2076f9896e9faa9a28d658a6c65ed27c752a56c | |
| parent | 8e2acc3fb6f03738d94667c39eb7a38e86e5e4ff (diff) | |
kj-gitbot: dateTimeSetter.py
| -rw-r--r-- | dateTimeSetter.py | 362 |
1 files changed, 362 insertions, 0 deletions
diff --git a/dateTimeSetter.py b/dateTimeSetter.py new file mode 100644 index 0000000..5fa9fda --- /dev/null +++ b/dateTimeSetter.py | |||
| @@ -0,0 +1,362 @@ | |||
| 1 | #!/usr/bin/python | ||
| 2 | |||
| 3 | import tkinter as tk | ||
| 4 | from tkinter import ttk, messagebox | ||
| 5 | import subprocess | ||
| 6 | import requests | ||
| 7 | import datetime | ||
| 8 | import pytz | ||
| 9 | import time | ||
| 10 | |||
| 11 | |||
| 12 | class DateTimeSetter(tk.Tk): | ||
| 13 | def __init__(self): | ||
| 14 | super().__init__() | ||
| 15 | |||
| 16 | self.title("dateTimeSetter") | ||
| 17 | self.geometry("640x480") | ||
| 18 | self.resizable(True, True) | ||
| 19 | self.attributes("-type", "dialog") # make the window floating | ||
| 20 | |||
| 21 | self.create_widgets() | ||
| 22 | self.populate_fields() | ||
| 23 | |||
| 24 | def create_widgets(self): | ||
| 25 | # automatic time setting checkbox | ||
| 26 | automatic_time_label = ttk.Label( | ||
| 27 | self, text="Set the date, time, and timezone automatically?") | ||
| 28 | automatic_time_label.pack(pady=5) | ||
| 29 | |||
| 30 | self.automatic_time_check = ttk.Checkbutton( | ||
| 31 | self, text="Yes", command=self.on_automatic_time_toggled) | ||
| 32 | self.automatic_time_check.pack(pady=5) | ||
| 33 | |||
| 34 | # date entry fields | ||
| 35 | date_frame = ttk.LabelFrame(self, text="Date") | ||
| 36 | date_frame.pack(pady=10, padx=10, fill="x") | ||
| 37 | |||
| 38 | ttk.Label(date_frame, text="YYYY:").grid( | ||
| 39 | row=0, column=0, padx=5, pady=5) | ||
| 40 | self.year_var = tk.StringVar() | ||
| 41 | self.year_combo = ttk.Combobox(date_frame, textvariable=self.year_var, values=[str( | ||
| 42 | year) for year in range(1970, 2039)], state="normal") | ||
| 43 | self.year_combo.grid(row=0, column=1, padx=5, pady=5) | ||
| 44 | |||
| 45 | ttk.Label(date_frame, text="MM:").grid(row=0, column=2, padx=5, pady=5) | ||
| 46 | self.month_var = tk.StringVar() | ||
| 47 | self.month_combo = ttk.Combobox(date_frame, textvariable=self.month_var, values=[str( | ||
| 48 | month) for month in range(1, 13)], state="normal") | ||
| 49 | self.month_combo.grid(row=0, column=3, padx=5, pady=5) | ||
| 50 | |||
| 51 | ttk.Label(date_frame, text="DD:").grid(row=0, column=4, padx=5, pady=5) | ||
| 52 | self.day_var = tk.StringVar() | ||
| 53 | self.day_combo = ttk.Combobox( | ||
| 54 | date_frame, textvariable=self.day_var, values=[str(day) for day in range(1, 32)], state="normal") | ||
| 55 | self.day_combo.grid(row=0, column=5, padx=5, pady=5) | ||
| 56 | |||
| 57 | # time entry fields | ||
| 58 | time_frame = ttk.LabelFrame(self, text="Time (24hr)") | ||
| 59 | time_frame.pack(pady=10, padx=10, fill="x") | ||
| 60 | |||
| 61 | ttk.Label(time_frame, text=" HH:").grid( | ||
| 62 | row=0, column=0, padx=5, pady=5) | ||
| 63 | self.hour_var = tk.StringVar() | ||
| 64 | self.hour_combo = ttk.Combobox(time_frame, textvariable=self.hour_var, values=[str( | ||
| 65 | hour) for hour in range(0, 24)], state="normal") | ||
| 66 | self.hour_combo.grid(row=0, column=1, padx=5, pady=5) | ||
| 67 | |||
| 68 | ttk.Label(time_frame, text="MM:").grid(row=0, column=2, padx=5, pady=5) | ||
| 69 | self.minute_var = tk.StringVar() | ||
| 70 | self.minute_combo = ttk.Combobox(time_frame, textvariable=self.minute_var, values=[str( | ||
| 71 | minute) for minute in range(0, 60)], state="normal") | ||
| 72 | self.minute_combo.grid(row=0, column=3, padx=5, pady=5) | ||
| 73 | |||
| 74 | ttk.Label(time_frame, text="SS:").grid(row=0, column=4, padx=5, pady=5) | ||
| 75 | self.second_var = tk.StringVar() | ||
| 76 | self.second_combo = ttk.Combobox(time_frame, textvariable=self.second_var, values=[str( | ||
| 77 | second) for second in range(0, 60)], state="normal") | ||
| 78 | self.second_combo.grid(row=0, column=5, padx=5, pady=5) | ||
| 79 | |||
| 80 | # timezone entry field | ||
| 81 | timezone_frame = ttk.LabelFrame(self, text="Timezone") | ||
| 82 | timezone_frame.pack(pady=10, padx=10, fill="x") | ||
| 83 | |||
| 84 | ttk.Label(timezone_frame, text="Timezone:").grid( | ||
| 85 | row=0, column=0, padx=5, pady=5) | ||
| 86 | self.timezone_var = tk.StringVar() | ||
| 87 | self.timezone_combo = ttk.Combobox( | ||
| 88 | timezone_frame, textvariable=self.timezone_var, values=pytz.all_timezones, state="normal") | ||
| 89 | self.timezone_combo.grid(row=0, column=1, padx=5, pady=5) | ||
| 90 | |||
| 91 | # set-local-rtc checkbox | ||
| 92 | self.local_rtc_check = ttk.Checkbutton( | ||
| 93 | timezone_frame, text="Set local RTC") | ||
| 94 | self.local_rtc_check.grid( | ||
| 95 | row=1, column=0, columnspan=2, padx=5, pady=5) | ||
| 96 | |||
| 97 | # apply button | ||
| 98 | button_frame = ttk.Frame(self) | ||
| 99 | button_frame.pack(pady=20) | ||
| 100 | |||
| 101 | self.apply_button = ttk.Button( | ||
| 102 | button_frame, text="Apply All", command=self.on_apply_clicked) | ||
| 103 | self.apply_button.grid(row=0, column=0, padx=5) | ||
| 104 | |||
| 105 | self.timezone_apply_button = ttk.Button( | ||
| 106 | button_frame, text="Apply Timezone", command=self.on_timezone_apply_clicked) | ||
| 107 | self.timezone_apply_button.grid(row=0, column=1, padx=5) | ||
| 108 | |||
| 109 | self.date_time_apply_button = ttk.Button( | ||
| 110 | button_frame, text="Apply Date & Time", command=self.on_date_time_apply_clicked) | ||
| 111 | self.date_time_apply_button.grid(row=0, column=2, padx=5) | ||
| 112 | |||
| 113 | self.local_rtc_apply_button = ttk.Button( | ||
| 114 | button_frame, text="Apply Local RTC", command=self.on_local_rtc_apply_clicked) | ||
| 115 | self.local_rtc_apply_button.grid(row=0, column=3, padx=5) | ||
| 116 | |||
| 117 | self.update_apply_button_state() | ||
| 118 | |||
| 119 | # Set trace on all variables to call update_apply_button_state when they change | ||
| 120 | self.year_var.trace_add("write", self.update_apply_button_state) | ||
| 121 | self.month_var.trace_add("write", self.update_apply_button_state) | ||
| 122 | self.day_var.trace_add("write", self.update_apply_button_state) | ||
| 123 | self.hour_var.trace_add("write", self.update_apply_button_state) | ||
| 124 | self.minute_var.trace_add("write", self.update_apply_button_state) | ||
| 125 | self.second_var.trace_add("write", self.update_apply_button_state) | ||
| 126 | self.timezone_var.trace_add("write", self.update_apply_button_state) | ||
| 127 | |||
| 128 | def on_automatic_time_toggled(self): | ||
| 129 | if self.automatic_time_check.instate(['selected']): | ||
| 130 | self.year_combo.state(['disabled']) | ||
| 131 | self.month_combo.state(['disabled']) | ||
| 132 | self.day_combo.state(['disabled']) | ||
| 133 | self.hour_combo.state(['disabled']) | ||
| 134 | self.minute_combo.state(['disabled']) | ||
| 135 | self.second_combo.state(['disabled']) | ||
| 136 | self.timezone_combo.state(['disabled']) | ||
| 137 | else: | ||
| 138 | self.year_combo.state(['!disabled']) | ||
| 139 | self.month_combo.state(['!disabled']) | ||
| 140 | self.day_combo.state(['!disabled']) | ||
| 141 | self.hour_combo.state(['!disabled']) | ||
| 142 | self.minute_combo.state(['!disabled']) | ||
| 143 | self.second_combo.state(['!disabled']) | ||
| 144 | self.timezone_combo.state(['!disabled']) | ||
| 145 | self.update_apply_button_state() | ||
| 146 | |||
| 147 | def update_apply_button_state(self, *_): | ||
| 148 | if self.automatic_time_check.instate(['selected']): | ||
| 149 | self.apply_button.state(['!disabled']) | ||
| 150 | self.timezone_apply_button.state(['disabled']) | ||
| 151 | self.date_time_apply_button.state(['disabled']) | ||
| 152 | self.local_rtc_apply_button.state(['disabled']) | ||
| 153 | else: | ||
| 154 | all_date_filled = all([ | ||
| 155 | self.year_combo.get(), | ||
| 156 | self.month_combo.get(), | ||
| 157 | self.day_combo.get(), | ||
| 158 | ]) | ||
| 159 | all_time_filled = all([ | ||
| 160 | self.hour_combo.get(), | ||
| 161 | self.minute_combo.get(), | ||
| 162 | self.second_combo.get(), | ||
| 163 | ]) | ||
| 164 | timezone_filled = self.timezone_combo.get() | ||
| 165 | all_filled = all_date_filled and all_time_filled and timezone_filled | ||
| 166 | |||
| 167 | if all_filled: | ||
| 168 | self.apply_button.state(['!disabled']) | ||
| 169 | else: | ||
| 170 | self.apply_button.state(['disabled']) | ||
| 171 | |||
| 172 | if timezone_filled and self.validate_timezone(timezone_filled): | ||
| 173 | self.timezone_apply_button.state(['!disabled']) | ||
| 174 | else: | ||
| 175 | self.timezone_apply_button.state(['disabled']) | ||
| 176 | |||
| 177 | if all_date_filled and all_time_filled: | ||
| 178 | self.date_time_apply_button.state(['!disabled']) | ||
| 179 | else: | ||
| 180 | self.date_time_apply_button.state(['disabled']) | ||
| 181 | |||
| 182 | self.local_rtc_apply_button.state(['!disabled']) | ||
| 183 | |||
| 184 | def on_apply_clicked(self): | ||
| 185 | automatic_time = self.automatic_time_check.instate(['selected']) | ||
| 186 | local_rtc = self.local_rtc_check.instate(['selected']) | ||
| 187 | |||
| 188 | if automatic_time: | ||
| 189 | subprocess.run(["timedatectl", "set-ntp", "true"]) | ||
| 190 | |||
| 191 | try: | ||
| 192 | automatic_timezone_output = subprocess.run( | ||
| 193 | ["curl", "--fail", "https://ipinfo.io/timezone"], capture_output=True, text=True) | ||
| 194 | automatic_timezone = automatic_timezone_output.stdout.strip() | ||
| 195 | |||
| 196 | if automatic_timezone: | ||
| 197 | subprocess.run( | ||
| 198 | ["timedatectl", "set-timezone", automatic_timezone]) | ||
| 199 | messagebox.showinfo( | ||
| 200 | "Info", "Automatic date, time, and timezone setting complete.") | ||
| 201 | else: | ||
| 202 | messagebox.showwarning( | ||
| 203 | "Warning", "Automatic date, time, and timezone setting failed. Please try again or use timedatectl.") | ||
| 204 | except requests.RequestException: | ||
| 205 | messagebox.showwarning( | ||
| 206 | "Warning", "Failed to fetch date, time, and timezone information. Please try again or use timedatectl.") | ||
| 207 | else: | ||
| 208 | try: | ||
| 209 | date_input = f"{self.year_combo.get()}-{int(self.month_combo.get()):02d}-{int(self.day_combo.get()):02d}" | ||
| 210 | time_input = f"{int(self.hour_combo.get()):02d}:{int(self.minute_combo.get()):02d}:{int(self.second_combo.get()):02d}" | ||
| 211 | timezone_input = self.timezone_combo.get() | ||
| 212 | |||
| 213 | # check for invalid numbers in fields | ||
| 214 | if not all([ | ||
| 215 | self.validate_number(self.year_combo.get(), 1970, 2038), | ||
| 216 | self.validate_number(self.month_combo.get(), 1, 12), | ||
| 217 | self.validate_number(self.day_combo.get(), 1, 31), | ||
| 218 | self.validate_number(self.hour_combo.get(), 0, 23), | ||
| 219 | self.validate_number(self.minute_combo.get(), 0, 59), | ||
| 220 | self.validate_number(self.second_combo.get(), 0, 59), | ||
| 221 | self.validate_timezone(timezone_input) | ||
| 222 | ]): | ||
| 223 | messagebox.showerror( | ||
| 224 | "Error", "Invalid number entered in one or more fields.") | ||
| 225 | return | ||
| 226 | |||
| 227 | subprocess.run(["timedatectl", "set-ntp", "false"]) | ||
| 228 | time.sleep(1) | ||
| 229 | subprocess.run(["timedatectl", "set-timezone", timezone_input]) | ||
| 230 | time.sleep(1) | ||
| 231 | subprocess.run(["timedatectl", "set-time", | ||
| 232 | f"{date_input} {time_input}"]) | ||
| 233 | messagebox.showinfo( | ||
| 234 | "Info", "Manual date, time, and timezone setting complete.") | ||
| 235 | except ValueError as e: | ||
| 236 | messagebox.showerror("Error", f"Error:\n{e}\n\nOne or more blank fields!") | ||
| 237 | |||
| 238 | # handle local rtc setting | ||
| 239 | if local_rtc: | ||
| 240 | subprocess.run(["timedatectl", "set-local-rtc", "1"]) | ||
| 241 | else: | ||
| 242 | subprocess.run(["timedatectl", "set-local-rtc", "0"]) | ||
| 243 | |||
| 244 | def on_timezone_apply_clicked(self): | ||
| 245 | timezone_input = self.timezone_combo.get() | ||
| 246 | |||
| 247 | if not timezone_input or not self.validate_timezone(timezone_input): | ||
| 248 | messagebox.showerror("Error", "Invalid or empty timezone field.") | ||
| 249 | return | ||
| 250 | |||
| 251 | try: | ||
| 252 | subprocess.run(["timedatectl", "set-timezone", timezone_input]) | ||
| 253 | messagebox.showinfo( | ||
| 254 | "Info", "Timezone setting complete.") | ||
| 255 | except subprocess.CalledProcessError as e: | ||
| 256 | messagebox.showerror("Error", f"Error setting timezone:\n{e}") | ||
| 257 | |||
| 258 | def on_date_time_apply_clicked(self): | ||
| 259 | try: | ||
| 260 | date_input = f"{self.year_combo.get()}-{int(self.month_combo.get()):02d}-{int(self.day_combo.get()):02d}" | ||
| 261 | time_input = f"{int(self.hour_combo.get()):02d}:{int(self.minute_combo.get()):02d}:{int(self.second_combo.get()):02d}" | ||
| 262 | |||
| 263 | # check for invalid numbers in fields | ||
| 264 | if not all([ | ||
| 265 | self.validate_number(self.year_combo.get(), 1970, 2038), | ||
| 266 | self.validate_number(self.month_combo.get(), 1, 12), | ||
| 267 | self.validate_number(self.day_combo.get(), 1, 31), | ||
| 268 | self.validate_number(self.hour_combo.get(), 0, 23), | ||
| 269 | self.validate_number(self.minute_combo.get(), 0, 59), | ||
| 270 | self.validate_number(self.second_combo.get(), 0, 59), | ||
| 271 | ]): | ||
| 272 | messagebox.showerror( | ||
| 273 | "Error", "Invalid number entered in one or more fields.") | ||
| 274 | return | ||
| 275 | |||
| 276 | subprocess.run(["timedatectl", "set-ntp", "false"]) | ||
| 277 | time.sleep(1) | ||
| 278 | subprocess.run(["timedatectl", "set-time", f"{date_input} {time_input}"]) | ||
| 279 | messagebox.showinfo( | ||
| 280 | "Info", "Manual date and time setting complete.") | ||
| 281 | except ValueError as e: | ||
| 282 | messagebox.showerror("Error", f"Error:\n{e}\n\nOne or more blank fields!") | ||
| 283 | |||
| 284 | def on_local_rtc_apply_clicked(self): | ||
| 285 | local_rtc = self.local_rtc_check.instate(['selected']) | ||
| 286 | |||
| 287 | if local_rtc: | ||
| 288 | subprocess.run(["timedatectl", "set-local-rtc", "1"]) | ||
| 289 | messagebox.showinfo("Info", "Local RTC setting enabled.") | ||
| 290 | else: | ||
| 291 | subprocess.run(["timedatectl", "set-local-rtc", "0"]) | ||
| 292 | messagebox.showinfo("Info", "Local RTC setting disabled.") | ||
| 293 | |||
| 294 | def validate_number(self, value, min_val, max_val): | ||
| 295 | try: | ||
| 296 | num = int(value) | ||
| 297 | return min_val <= num <= max_val | ||
| 298 | except ValueError: | ||
| 299 | return False | ||
| 300 | |||
| 301 | def validate_timezone(self, timezone): | ||
| 302 | return timezone in pytz.all_timezones | ||
| 303 | |||
| 304 | def populate_fields(self): | ||
| 305 | now = datetime.datetime.now() | ||
| 306 | self.year_combo.set(now.year) | ||
| 307 | self.month_combo.set(now.month) | ||
| 308 | self.day_combo.set(now.day) | ||
| 309 | self.hour_combo.set(now.hour) | ||
| 310 | self.minute_combo.set(now.minute) | ||
| 311 | self.second_combo.set(now.second) | ||
| 312 | |||
| 313 | try: | ||
| 314 | current_timezone = subprocess.check_output( | ||
| 315 | ["timedatectl", "show", "--property=Timezone"]).decode().strip().split("=")[1] | ||
| 316 | self.timezone_combo.set(current_timezone) | ||
| 317 | except subprocess.CalledProcessError: | ||
| 318 | pass | ||
| 319 | |||
| 320 | try: | ||
| 321 | ntp_status = subprocess.check_output( | ||
| 322 | ["timedatectl", "show", "--property=NTP"]).decode().strip().split("=")[1] | ||
| 323 | if ntp_status == "yes": | ||
| 324 | self.automatic_time_check.state(['selected']) | ||
| 325 | self.on_automatic_time_toggled() | ||
| 326 | else: | ||
| 327 | self.automatic_time_check.state(['!selected']) | ||
| 328 | self.on_automatic_time_toggled() | ||
| 329 | except subprocess.CalledProcessError: | ||
| 330 | pass | ||
| 331 | |||
| 332 | try: | ||
| 333 | local_rtc_status = subprocess.check_output( | ||
| 334 | ["timedatectl", "show", "--property=LocalRTC"]).decode().strip().split("=")[1] | ||
| 335 | if local_rtc_status == "yes": | ||
| 336 | self.local_rtc_check.state(['selected']) | ||
| 337 | else: | ||
| 338 | self.local_rtc_check.state(['!selected']) | ||
| 339 | except subprocess.CalledProcessError: | ||
| 340 | pass | ||
| 341 | |||
| 342 | self.year_combo.bind("<<ComboboxSelected>>", | ||
| 343 | self.update_apply_button_state) | ||
| 344 | self.month_combo.bind("<<ComboboxSelected>>", | ||
| 345 | self.update_apply_button_state) | ||
| 346 | self.day_combo.bind("<<ComboboxSelected>>", | ||
| 347 | self.update_apply_button_state) | ||
| 348 | self.hour_combo.bind("<<ComboboxSelected>>", | ||
| 349 | self.update_apply_button_state) | ||
| 350 | self.minute_combo.bind("<<ComboboxSelected>>", | ||
| 351 | self.update_apply_button_state) | ||
| 352 | self.second_combo.bind("<<ComboboxSelected>>", | ||
| 353 | self.update_apply_button_state) | ||
| 354 | self.timezone_combo.bind( | ||
| 355 | "<<ComboboxSelected>>", self.update_apply_button_state) | ||
| 356 | self.timezone_combo.bind( | ||
| 357 | "<KeyRelease>", self.update_apply_button_state) | ||
| 358 | |||
| 359 | |||
| 360 | if __name__ == "__main__": | ||
| 361 | app = DateTimeSetter() | ||
| 362 | app.mainloop() | ||
