koala73/worldmonitor · error · ConvexError

digestTimezone must be a valid IANA timezone (e.g. America/N

Error message

digestTimezone must be a valid IANA timezone (e.g. America/New_York)

What it means

Thrown by setDigestSettings when args.digestTimezone is present but Intl.DateTimeFormat rejects it as a timeZone option — i.e. it is not a valid IANA timezone identifier (e.g. 'America/New_York', 'Europe/London'). The check uses the runtime's Intl database, so the validation is as authoritative as the host environment.

Source

Thrown at convex/alertRules.ts:293

    variant: v.string(),
    digestMode: digestModeValidator,
    digestHour: v.optional(v.number()),
    digestTimezone: v.optional(v.string()),
  },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new ConvexError("UNAUTHENTICATED");
    const userId = identity.subject;
    await assertProEntitlement(ctx, userId);

    if (args.digestHour !== undefined && (args.digestHour < 0 || args.digestHour > 23 || !Number.isInteger(args.digestHour))) {
      throw new ConvexError("digestHour must be an integer 0–23");
    }
    if (args.digestTimezone !== undefined) {
      try {
        Intl.DateTimeFormat(undefined, { timeZone: args.digestTimezone });
      } catch {
        throw new ConvexError("digestTimezone must be a valid IANA timezone (e.g. America/New_York)");
      }
    }

    const existing = await ctx.db
      .query("alertRules")
      .withIndex("by_user_variant", (q) =>
        q.eq("userId", userId).eq("variant", args.variant),
      )
      .unique();

    const pair = resolveEffectivePair({
      incomingDigestMode: args.digestMode,
      existing: existing ?? undefined,
    });
    assertCompatibleDeliveryMode(pair);

    const now = Date.now();
    const patch = {

View on GitHub (pinned to ffec79ac33)

Solutions

  1. Resolve the IANA timezone client-side via Intl.DateTimeFormat().resolvedOptions().timeZone and pass that value.
  2. If accepting user input, validate against a known IANA list (e.g. Intl.supportedValuesOf('timeZone')) before submitting.
  3. Trim whitespace before submission.

Example fix

// before
await setDigestSettings({ variant, digestMode: 'daily', digestTimezone: 'EST' });
// after
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; // e.g. 'America/New_York'
await setDigestSettings({ variant, digestMode: 'daily', digestTimezone: 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.digestTimezone && !isValidIanaTimezone(args.digestTimezone)) {
  // 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 setDigestSettings(args);
} catch (e) {
  if (e instanceof ConvexError && /valid IANA timezone/.test(String(e.message))) {
    // fall back to browser tz: args.digestTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone
  } else throw e;
}

Prevention

When it happens

Trigger: Calling setDigestSettings with digestTimezone set to an abbreviation ('EST', 'PST'), a fixed offset ('UTC-5', '+05:30'), a misspelled zone ('America/New_York '), or any non-IANA string.

Common situations: Client deriving timezone from Date.getTimezoneOffset() and converting to an offset string instead of resolving the IANA name; user typing a custom timezone; trailing whitespace; copy-paste from a non-IANA source.

Related errors


AI-assisted analysis of koala73/worldmonitor@ffec79ac33 (2026-08-12). Data as JSON: /api/errors/0ab8213294691c0a. Report an issue: GitHub.