feat(events): add displayTimezone field and zoned datetime helpers

Events stored UTC have always been interpreted in viewer-local TZ at
the admin form and render layer. Adding an event-owned IANA timezone
unblocks accurate scheduling and display regardless of the admin's or
viewer's browser TZ.

- Event.displayTimezone (default "America/Toronto") on the model.
- displayTimezone added to admin create/update Zod schemas.
- app/utils/timezones.js: zonedLocalToUTC, utcToZonedLocal,
  shortTimezoneName — Intl-based helpers, no new dependencies.
This commit is contained in:
Jennie Robinson Faber 2026-05-19 10:39:27 +01:00
parent 9e4030ccfd
commit e6f05b5471
3 changed files with 81 additions and 0 deletions

View file

@ -18,6 +18,8 @@ const eventSchema = new mongoose.Schema({
enum: ["community", "workshop", "social", "showcase"],
default: "community",
},
// IANA timezone for interpreting datetime input and rendering the event time.
displayTimezone: { type: String, default: "America/Toronto" },
// Online-first location handling
location: {
type: String,

View file

@ -173,6 +173,7 @@ export const adminEventCreateSchema = z.object({
description: z.string().min(1).max(50000),
startDate: z.string().min(1),
endDate: z.string().min(1),
displayTimezone: z.string().max(100).optional(),
location: z.string().max(500).optional(),
maxAttendees: z.preprocess(emptyStringToUndefined, z.number().int().positive().optional().nullable()),
membersOnly: z.boolean().optional(),
@ -204,6 +205,7 @@ export const adminEventUpdateSchema = z.object({
description: z.string().min(1).max(50000),
startDate: z.string().min(1),
endDate: z.string().min(1),
displayTimezone: z.string().max(100).optional(),
location: z.string().max(500).optional(),
maxAttendees: z.preprocess(emptyStringToUndefined, z.number().int().positive().optional().nullable()),
membersOnly: z.boolean().optional(),