[Scheduling] Campaign-anchored timezone correctness #95
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context / Motivation
The app stores
user.timezone(webapp/backend/app/models/user.py:34) andcampaign.timezone(webapp/backend/app/models/campaign.py:41) — both nullable free-text — but the frontend uses neither.DateTimePicker'scombine()(webapp/frontend/src/components/DateTimePicker.jsx:34-40) emits a naive"YYYY-MM-DDTHH:MM"string, and every submit path runs it throughnew Date(...).toISOString(), which interprets the naive string in the browser's zone:webapp/frontend/src/pages/SessionDetail.jsx:551(reschedule_time),:582(vote slot times)webapp/frontend/src/pages/CampaignDetail.jsx:658,:682(milestone dates),:716(proposed times)Every display then uses
toLocaleStringin the browser zone (16 occurrences across 8 files; main ones:SessionDetail.jsx,CampaignDetail.jsx,CampaignPlanning.jsx,components/VotingGrid.jsx). There is no date library inwebapp/frontend/package.json— nativeDateonly.Consequence: a GM whose browser zone differs from the campaign zone schedules a different wall-clock time than intended, and players in other zones see other times with no shared anchor.
Spec
campaign.timezoneis the canonical scheduling zone.Intl.DateTimeFormatwithtimeZone(ordate-fns-tzif a dependency is preferred — keep bundle impact small; no moment/luxon).user.timezonewhen set, otherwise the browser zone.DateTime(timezone=True)UTC (models/session.py:89confirmed_time,:92end_time) — verify all write paths receive proper UTC instants. The bot currently formats times as Discord dynamic timestamps<t:UNIX:F>(bot/questboard_bot/cogs/notifications.py_add_time_field~614-620;cogs/sessions.py:75-89), which render in each Discord viewer's own zone — that behavior is acceptable and should be kept, but any plain-text time strings the bot emits must use the campaign zone. Fix any path that treats naive datetimes as local.campaign.timezoneis unset, fall back to the browser zone but surface a settings nudge on the campaign page. Campaign settings should offer an IANA zone picker (validate againstIntl.supportedValuesOf('timeZone')).CampaignDetail.jsx:658/:682) are date-only — decide and document whether they are zone-less dates (recommended: store as plain date, stop running them throughtoISOString).Out of scope
Acceptance criteria
TZenv /vi.setSystemTime+ explicit zone fixtures).new Date(naiveString).toISOString()on scheduling inputs.<t:>timestamps); any plain-text times use campaign zone.References
webapp/frontend/src/components/DateTimePicker.jsx:34-40webapp/frontend/src/pages/SessionDetail.jsx:51,:551,:582webapp/frontend/src/pages/CampaignDetail.jsx:658,:682,:716webapp/backend/app/models/user.py:34,webapp/backend/app/models/campaign.py:41webapp/backend/app/models/session.py:89-92bot/questboard_bot/cogs/notifications.py(_add_time_field),bot/questboard_bot/cogs/sessions.py:75-89Filed from the July 2026 full-project review.