{"record":{"id":"e857f6094ba2d669","repo":"koala73/worldmonitor","slug":"quiethourstimezone-must-be-a-valid-iana-timezone","errorCode":null,"errorMessage":"quietHoursTimezone must be a valid IANA timezone (e.g. America/New_York)","messagePattern":"quietHoursTimezone must be a valid IANA timezone \\(e\\.g\\. America/New_York\\)","errorType":"validation","errorClass":"ConvexError","httpStatus":null,"severity":"error","filePath":"convex/alertRules.ts","lineNumber":434,"sourceCode":"  quietHoursOverride: v.optional(quietHoursOverrideValidator),\n} as const;\n\nfunction validateQuietHoursArgs(args: {\n  quietHoursStart?: number;\n  quietHoursEnd?: number;\n  quietHoursTimezone?: string;\n}) {\n  if (args.quietHoursStart !== undefined && (args.quietHoursStart < 0 || args.quietHoursStart > 23 || !Number.isInteger(args.quietHoursStart))) {\n    throw new ConvexError(\"quietHoursStart must be an integer 0–23\");\n  }\n  if (args.quietHoursEnd !== undefined && (args.quietHoursEnd < 0 || args.quietHoursEnd > 23 || !Number.isInteger(args.quietHoursEnd))) {\n    throw new ConvexError(\"quietHoursEnd must be an integer 0–23\");\n  }\n  if (args.quietHoursTimezone !== undefined) {\n    try {\n      Intl.DateTimeFormat(undefined, { timeZone: args.quietHoursTimezone });\n    } catch {\n      throw new ConvexError(\"quietHoursTimezone must be a valid IANA timezone (e.g. America/New_York)\");\n    }\n  }\n}\n\nexport const setQuietHours = mutation({\n  args: QUIET_HOURS_ARGS,\n  handler: async (ctx, args) => {\n    const identity = await ctx.auth.getUserIdentity();\n    if (!identity) throw new ConvexError(\"UNAUTHENTICATED\");\n    const userId = identity.subject;\n    await assertProEntitlement(ctx, userId);\n    validateQuietHoursArgs(args);\n\n    const existing = await ctx.db\n      .query(\"alertRules\")\n      .withIndex(\"by_user_variant\", (q) =>\n        q.eq(\"userId\", userId).eq(\"variant\", args.variant),\n      )","sourceCodeStart":416,"sourceCodeEnd":452,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/convex/alertRules.ts#L416-L452","documentation":"Thrown by validateQuietHoursArgs when args.quietHoursTimezone is present but Intl.DateTimeFormat rejects it as a timeZone option — i.e. not a valid IANA timezone identifier. Uses the runtime Intl database for authority.","triggerScenarios":"Calling setQuietHours or setQuietHoursForUser with quietHoursTimezone set to an abbreviation ('PST'), a fixed offset ('UTC+1'), a misspelled zone, or any non-IANA string.","commonSituations":"Client deriving timezone from a UTC offset instead of the IANA name; trailing whitespace; user-typed timezone; copy from a non-IANA source.","solutions":["Resolve the IANA timezone client-side via Intl.DateTimeFormat().resolvedOptions().timeZone.","Validate against Intl.supportedValuesOf('timeZone') if accepting user input.","Trim whitespace before submission."],"exampleFix":"// before\nawait setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 7, quietHoursTimezone: 'PST' });\n// after\nconst tz = Intl.DateTimeFormat().resolvedOptions().timeZone;\nawait setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 7, quietHoursTimezone: tz });","handlingStrategy":"validation","validationCode":"function isValidIanaTimezone(tz) {\n  if (typeof tz !== 'string') return false;\n  try {\n    Intl.DateTimeFormat(undefined, { timeZone: tz });\n    return true;\n  } catch { return false; }\n}\nif (args.quietHoursTimezone && !isValidIanaTimezone(args.quietHoursTimezone)) {\n  // reject before submit\n}","typeGuard":"function isIanaTimezone(tz): tz is string {\n  if (typeof tz !== 'string') return false;\n  try { Intl.DateTimeFormat(undefined, { timeZone: tz }); return true; }\n  catch { return false; }\n}","tryCatchPattern":"try {\n  await setQuietHours(args);\n} catch (e) {\n  if (e instanceof ConvexError && /valid IANA timezone/.test(String(e.message))) {\n    // fall back to browser tz\n  } else throw e;\n}","preventionTips":["Derive timezone from Intl.DateTimeFormat().resolvedOptions().timeZone.","Validate user input against Intl.supportedValuesOf('timeZone').","Trim whitespace before submission."],"tags":["convex","validation","notifications","timezone","quiet-hours"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}