This commit is contained in:
SpaceNerd0717
2025-10-20 20:04:50 -04:00
2 changed files with 82 additions and 20 deletions

92
main.py
View File

@@ -1,12 +1,16 @@
import tkinter as tk import tkinter as tk
import time import time
import threading import threading
from datetime import datetime from datetime import datetime, timedelta
import requests import requests
import csv import csv
import io import io
import os import os
import json import json
try:
from zoneinfo import ZoneInfo
except Exception:
ZoneInfo = None
# Get the user's Documents folder (cross-platform) # Get the user's Documents folder (cross-platform)
@@ -19,9 +23,9 @@ os.makedirs(app_folder, exist_ok=True)
# Define file paths # Define file paths
COUNTDOWN_HTML = os.path.join(app_folder, "countdown.html") COUNTDOWN_HTML = os.path.join(app_folder, "countdown.html")
GONOGO_HTML = os.path.join(app_folder, "gonogo.html") GONOGO_HTML = os.path.join(app_folder, "gonogo.html")
SHEET_LINK = "https://docs.google.com/spreadsheets/d/1UPJTW8vH2mgEzispjg_Y_zSqYTFaLoxuoZnqleVlSZ0/export?format=csv&gid=855477916" SHEET_LINK = ""
session = requests.Session() session = requests.Session()
appVersion = "0.3.0" appVersion = "0.4.0"
SETTINGS_FILE = os.path.join(app_folder, "settings.json") SETTINGS_FILE = os.path.join(app_folder, "settings.json")
# Default settings # Default settings
@@ -34,6 +38,14 @@ DEFAULT_SETTINGS = {
"vehicle_row": 4, "vehicle_row": 4,
"column": 12 # 1-based column (default column 12 -> index 11) "column": 12 # 1-based column (default column 12 -> index 11)
} }
# default timezone: 'local' uses system local tz, otherwise an IANA name or 'UTC'
DEFAULT_SETTINGS.setdefault('timezone', 'local')
# A small list of common timezone choices.
TIMEZONE_CHOICES = [
'local', 'UTC', 'US/Eastern', 'US/Central', 'US/Mountain', 'US/Pacific',
'Europe/London', 'Europe/Paris', 'Asia/Tokyo', 'Australia/Sydney'
]
def load_settings(): def load_settings():
@@ -276,13 +288,21 @@ class CountdownApp:
self.seconds_entry.insert(0, "0") self.seconds_entry.insert(0, "0")
self.seconds_entry.pack(side="left", padx=2) self.seconds_entry.pack(side="left", padx=2)
# Clock time input # Clock time input (separate HH, MM, SS boxes)
frame_clock = tk.Frame(root, bg="black") frame_clock = tk.Frame(root, bg="black")
frame_clock.pack(pady=5) frame_clock.pack(pady=5)
tk.Label(frame_clock, text="HH:MM", fg="white", bg="black").pack(side="left") tk.Label(frame_clock, text="Clock (HH:MM:SS)", fg="white", bg="black").pack(side="left")
self.clock_entry = tk.Entry(frame_clock, width=7, font=("Arial", 18)) self.clock_hours_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
self.clock_entry.insert(0, "14:00") self.clock_hours_entry.insert(0, "14")
self.clock_entry.pack(side="left", padx=5) self.clock_hours_entry.pack(side="left", padx=2)
tk.Label(frame_clock, text=":", fg="white", bg="black").pack(side="left")
self.clock_minutes_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
self.clock_minutes_entry.insert(0, "00")
self.clock_minutes_entry.pack(side="left", padx=2)
tk.Label(frame_clock, text=":", fg="white", bg="black").pack(side="left")
self.clock_seconds_entry = tk.Entry(frame_clock, width=3, font=("Arial", 18), fg='white', bg='#111', insertbackground='white')
self.clock_seconds_entry.insert(0, "00")
self.clock_seconds_entry.pack(side="left", padx=2)
# Control buttons # Control buttons
frame_buttons = tk.Frame(root, bg="black") frame_buttons = tk.Frame(root, bg="black")
@@ -363,7 +383,7 @@ class CountdownApp:
win = tk.Toplevel(self.root) win = tk.Toplevel(self.root)
win.config(bg="black") win.config(bg="black")
win.title("Settings") win.title("Settings")
win.geometry("560x200") win.geometry("560x250")
win.transient(self.root) win.transient(self.root)
# Mode selection # Mode selection
@@ -453,6 +473,16 @@ class CountdownApp:
frame_buttons_cfg.config(bg='black') frame_buttons_cfg.config(bg='black')
frame_buttons_cfg.pack(fill='x', padx=8, pady=6) frame_buttons_cfg.pack(fill='x', padx=8, pady=6)
# Timezone selector
tz_frame = tk.Frame(frame_sheet, bg='black')
tz_frame.pack(fill='x', padx=6, pady=4)
tk.Label(tz_frame, text='Timezone:', fg='white', bg='black').pack(side='left')
tz_var = tk.StringVar(value=settings.get('timezone', DEFAULT_SETTINGS.get('timezone', 'local')))
# OptionMenu with a few choices, but user may edit the text to any IANA name
tz_menu = tk.OptionMenu(tz_frame, tz_var, *TIMEZONE_CHOICES)
tz_menu.config(fg='white', bg='#222', activebackground='#333')
tz_menu.pack(side='left', padx=6)
def set_manual(val_type, val): def set_manual(val_type, val):
# store on fetch_gonogo func for now # store on fetch_gonogo func for now
if val_type == 'range': if val_type == 'range':
@@ -523,7 +553,8 @@ class CountdownApp:
# persist manual values if present # persist manual values if present
'manual_range': getattr(fetch_gonogo, 'manual_range', None), 'manual_range': getattr(fetch_gonogo, 'manual_range', None),
'manual_weather': getattr(fetch_gonogo, 'manual_weather', None), 'manual_weather': getattr(fetch_gonogo, 'manual_weather', None),
'manual_vehicle': getattr(fetch_gonogo, 'manual_vehicle', None) 'manual_vehicle': getattr(fetch_gonogo, 'manual_vehicle', None),
'timezone': tz_var.get()
} }
save_settings(new_settings) save_settings(new_settings)
# update immediately # update immediately
@@ -551,12 +582,16 @@ class CountdownApp:
self.hours_entry.config(state="normal") self.hours_entry.config(state="normal")
self.minutes_entry.config(state="normal") self.minutes_entry.config(state="normal")
self.seconds_entry.config(state="normal") self.seconds_entry.config(state="normal")
self.clock_entry.config(state="disabled") self.clock_hours_entry.config(state="disabled")
self.clock_minutes_entry.config(state="disabled")
self.clock_seconds_entry.config(state="disabled")
else: else:
self.hours_entry.config(state="disabled") self.hours_entry.config(state="disabled")
self.minutes_entry.config(state="disabled") self.minutes_entry.config(state="disabled")
self.seconds_entry.config(state="disabled") self.seconds_entry.config(state="disabled")
self.clock_entry.config(state="normal") self.clock_hours_entry.config(state="normal")
self.clock_minutes_entry.config(state="normal")
self.clock_seconds_entry.config(state="normal")
# ---------------------------- # ----------------------------
# Manual controls & helpers # Manual controls & helpers
@@ -632,12 +667,35 @@ class CountdownApp:
total_seconds = h * 3600 + m * 60 + s total_seconds = h * 3600 + m * 60 + s
else: else:
now = datetime.now() now = datetime.now()
parts = [int(p) for p in self.clock_entry.get().split(":")] # read separate HH, MM, SS boxes
h, m = parts[0], parts[1] h = int(self.clock_hours_entry.get() or 0)
s = parts[2] if len(parts) == 3 else 0 m = int(self.clock_minutes_entry.get() or 0)
s = int(self.clock_seconds_entry.get() or 0)
# determine timezone from settings
ssettings = load_settings()
tzname = ssettings.get('timezone', DEFAULT_SETTINGS.get('timezone', 'local'))
if ZoneInfo is None or tzname in (None, '', 'local'):
# naive local time handling (existing behavior) — use timedelta to roll day
target_today = now.replace(hour=h, minute=m, second=s, microsecond=0) target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
if target_today < now: if target_today <= now:
target_today = target_today.replace(day=now.day + 1) target_today = target_today + timedelta(days=1)
total_seconds = (target_today - now).total_seconds()
else:
try:
tz = ZoneInfo(tzname)
# construct aware "now" in that timezone and create the target time
now_tz = datetime.now(tz)
target = now_tz.replace(hour=h, minute=m, second=s, microsecond=0)
# if target already passed in that tz, roll to next day
if target <= now_tz:
target = target + timedelta(days=1)
# compute total seconds using aware-datetime subtraction to avoid epoch mixing
total_seconds = (target - now_tz).total_seconds()
except Exception:
# fallback to naive local behavior
target_today = now.replace(hour=h, minute=m, second=s, microsecond=0)
if target_today <= now:
target_today = target_today + timedelta(days=1)
total_seconds = (target_today - now).total_seconds() total_seconds = (target_today - now).total_seconds()
except Exception: except Exception:
self.text.config(text="Invalid time") self.text.config(text="Invalid time")

View File

@@ -1,6 +1,10 @@
RocketLaunchCountdown is a python and HTML system to operate a launch countdown and go/nogo indicator. The go/nogo indicator is operated either by a spreadsheet, using a sharelink inserted in the settings windows. Or there are buttons in the app that can manage it as well. Which mode is used is dictated by a simple radio button in the settings window. RocketLaunchCountdown is a python and HTML system to operate a launch countdown and go/nogo indicator. The go/nogo indicator is operated either by a spreadsheet, using a sharelink inserted in the settings windows. Or there are buttons in the app that can manage it as well. Which mode is used is dictated by a simple radio button in the settings window.
Latest release: [RocketLaunchCountdown 0.3.0](https://github.com/HamsterSpaceNerd3000/RocketLaunchCountdown/releases/tag/Prealpha_030) Latest release: [RocketLaunchCountdown 0.4.0 THE CLOCKS UPDATE](https://github.com/HamsterSpaceNerd3000/RocketLaunchCountdown/releases/tag/Prealpha_040)
Features Added:
Full rework of the clock system (countdown method based on clock time, not how long until).
Minor background fixes
INSTALL INSTRUCTIONS INSTALL INSTRUCTIONS