From 0030acc5265f21b09390fe18d14c6ec8ee20d4de Mon Sep 17 00:00:00 2001 From: ScreenTinker Date: Tue, 28 Jul 2026 09:41:03 -0500 Subject: [PATCH] Store a schedule in the timezone its screen runs in MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Creation and playback disagreed about which clock a schedule's hours are on. The player evaluated blocks in the device's zone — an operator override, else whatever the player's OS reported. Creation defaulted to a bare 'UTC', because the dialog never asked for a zone and the server filled the silence with one. So hours typed as "09:00 to 17:00" were stored as UTC and evaluated somewhere else. For anyone outside UTC the schedule was correct and appeared to do nothing, opening hours later than intended, with nothing on screen to explain why. A user in Asia/Tokyo hit exactly this and reported it as "I added something and it didn't appear". Both sides now resolve through lib/device-timezone, so they cannot drift: an explicit device override wins, then the OS-reported zone, then null. A legacy 'UTC' override counts as unset, since that was the old default rather than a deliberate choice and a genuine UTC deployment is indistinguishable from an unconfigured one. A new schedule inherits its target's zone — the device's, or for a group its leader's, falling back to the oldest member that reports one. A zone named explicitly by the caller still wins; this only fills the silence. A target that has never reported one still lands on UTC, which is the previous behaviour made explicit rather than assumed. The dialog now states which clock the hours are on, and says so differently when that clock is not the operator's own. Stating it is the other half of the fix: the server can pick the right zone, but the user still has to be able to see it. Tests pin both directions and, most importantly, that creation and playback resolve identically from the same device row. Co-Authored-By: Claude Opus 5 (1M context) --- frontend/js/i18n/en.js | 3 + frontend/js/views/schedule.js | 29 ++++ server/lib/device-timezone.js | 27 ++++ server/routes/schedules.js | 23 ++- .../schedule-timezone-inheritance.test.js | 137 ++++++++++++++++++ server/ws/deviceSocket.js | 6 +- 6 files changed, 220 insertions(+), 5 deletions(-) create mode 100644 server/lib/device-timezone.js create mode 100644 server/test/schedule-timezone-inheritance.test.js diff --git a/frontend/js/i18n/en.js b/frontend/js/i18n/en.js index 61115b0..6dd3686 100644 --- a/frontend/js/i18n/en.js +++ b/frontend/js/i18n/en.js @@ -1295,6 +1295,9 @@ export default { 'schedule.content_none': '— None —', 'schedule.title_label': 'Title (optional)', 'schedule.title_placeholder': 'e.g., Morning Playlist', + 'schedule.tz_device': 'Times are in {zone} — the screen\u2019s timezone, not your {local}.', + 'schedule.tz_same': 'Times are in {zone}.', + 'schedule.tz_unknown': 'Times use the screen\u2019s own timezone once it reports one.', 'schedule.start_time': 'Start Time', 'schedule.end_time': 'End Time', 'schedule.repeat': 'Repeat', diff --git a/frontend/js/views/schedule.js b/frontend/js/views/schedule.js index dd3820d..45abb06 100644 --- a/frontend/js/views/schedule.js +++ b/frontend/js/views/schedule.js @@ -95,6 +95,11 @@ export async function render(container) {
+ +