koala73/worldmonitor · error · ConvexError
quietHoursTimezone must be a valid IANA timezone (e.g. Ameri
Error message
quietHoursTimezone must be a valid IANA timezone (e.g. America/New_York)
What it means
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.
Source
Thrown at convex/alertRules.ts:434
quietHoursOverride: v.optional(quietHoursOverrideValidator),
} as const;
function validateQuietHoursArgs(args: {
quietHoursStart?: number;
quietHoursEnd?: number;
quietHoursTimezone?: string;
}) {
if (args.quietHoursStart !== undefined && (args.quietHoursStart < 0 || args.quietHoursStart > 23 || !Number.isInteger(args.quietHoursStart))) {
throw new ConvexError("quietHoursStart must be an integer 0–23");
}
if (args.quietHoursEnd !== undefined && (args.quietHoursEnd < 0 || args.quietHoursEnd > 23 || !Number.isInteger(args.quietHoursEnd))) {
throw new ConvexError("quietHoursEnd must be an integer 0–23");
}
if (args.quietHoursTimezone !== undefined) {
try {
Intl.DateTimeFormat(undefined, { timeZone: args.quietHoursTimezone });
} catch {
throw new ConvexError("quietHoursTimezone must be a valid IANA timezone (e.g. America/New_York)");
}
}
}
export const setQuietHours = mutation({
args: QUIET_HOURS_ARGS,
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new ConvexError("UNAUTHENTICATED");
const userId = identity.subject;
await assertProEntitlement(ctx, userId);
validateQuietHoursArgs(args);
const existing = await ctx.db
.query("alertRules")
.withIndex("by_user_variant", (q) =>
q.eq("userId", userId).eq("variant", args.variant),
)View on GitHub (pinned to ffec79ac33)
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.
Example fix
// before
await setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 7, quietHoursTimezone: 'PST' });
// after
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
await setQuietHours({ variant, quietHoursEnabled: true, quietHoursStart: 22, quietHoursEnd: 7, quietHoursTimezone: tz }); Defensive patterns
Strategy: validation
Validate before calling
function isValidIanaTimezone(tz) {
if (typeof tz !== 'string') return false;
try {
Intl.DateTimeFormat(undefined, { timeZone: tz });
return true;
} catch { return false; }
}
if (args.quietHoursTimezone && !isValidIanaTimezone(args.quietHoursTimezone)) {
// reject before submit
} Type guard
function isIanaTimezone(tz): tz is string {
if (typeof tz !== 'string') return false;
try { Intl.DateTimeFormat(undefined, { timeZone: tz }); return true; }
catch { return false; }
} Try / catch
try {
await setQuietHours(args);
} catch (e) {
if (e instanceof ConvexError && /valid IANA timezone/.test(String(e.message))) {
// fall back to browser tz
} else throw e;
} Prevention
- Derive timezone from Intl.DateTimeFormat().resolvedOptions().timeZone.
- Validate user input against Intl.supportedValuesOf('timeZone').
- Trim whitespace before submission.
When it happens
Trigger: Calling setQuietHours or setQuietHoursForUser with quietHoursTimezone set to an abbreviation ('PST'), a fixed offset ('UTC+1'), a misspelled zone, or any non-IANA string.
Common situations: Client deriving timezone from a UTC offset instead of the IANA name; trailing whitespace; user-typed timezone; copy from a non-IANA source.
Related errors
- digestTimezone must be a valid IANA timezone (e.g. America/N
- quietHoursStart must be an integer 0–23
- quietHoursEnd must be an integer 0–23
- quietHoursStart and quietHoursEnd must differ (same value =
- INCOMPATIBLE_DELIVERY
AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12).
Data as JSON: /api/errors/e857f6094ba2d669.
Report an issue: GitHub.