diff --git a/app.py b/app.py index e6afb15..8fa95e0 100644 --- a/app.py +++ b/app.py @@ -8,7 +8,7 @@ import automation import scheduler import storage -APP_VERSION = "0.4.0" +APP_VERSION = "0.4.1" APP_VERSION_DATE = "2026-09-01" app = Flask(__name__) diff --git a/scheduler.py b/scheduler.py index f1778eb..f996b58 100644 --- a/scheduler.py +++ b/scheduler.py @@ -20,12 +20,19 @@ import storage logger = logging.getLogger("massprice.scheduler") _work_queue: "queue.Queue" = queue.Queue() -# Timezone must be set at construction time, not via a later .configure() -# call — APScheduler resolves a timezone immediately in __init__ via -# tzlocal's system lookup, which fails inside this container image for -# an arbitrary $TZ (no zoneinfo/tzdata wiring), so the explicit TZ env -# var has to win before that lookup ever runs. -_scheduler = BackgroundScheduler(timezone=pytz.timezone(os.environ.get("TZ", "UTC"))) + +# Resolved once and reused everywhere a timezone is needed. Must be +# passed explicitly at construction time, never left to be inferred — +# both BackgroundScheduler AND CronTrigger independently call tzlocal's +# get_localzone() in their own __init__ if not given one, which fails +# inside this container image for an arbitrary $TZ (no zoneinfo/tzdata +# wiring). Passing it to the scheduler alone is NOT enough — a +# CronTrigger built without its own explicit timezone still resolves +# get_localzone() itself, regardless of what the scheduler is configured +# with, and crashes the same way. Every CronTrigger(...) below must pass +# timezone=_TZ explicitly. +_TZ = pytz.timezone(os.environ.get("TZ", "UTC")) +_scheduler = BackgroundScheduler(timezone=_TZ) _started = False VALID_DAYS = ["mon", "tue", "wed", "thu", "fri", "sat", "sun"] @@ -69,6 +76,7 @@ def rebuild_jobs(): day_of_week=",".join(days), hour=int(hour), minute=int(minute), + timezone=_TZ, ) _scheduler.add_job( enqueue_run,