koala73/worldmonitor · error · ConvexError

digestTimezone must be a valid IANA timezone (e.g…

Error message

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

What it means

setDigestSettings validates the optional digestTimezone by constructing Intl.DateTimeFormat(undefined, { timeZone: args.digestTimezone }) (convex/alertRules.ts:291). If the string is not an IANA time-zone identifier, the Intl constructor throws a RangeError which is caught and rethrown as ConvexError 'digestTimezone must be a valid IANA timezone (e.g. America/New_York)'. The check runs on the server's ICU database, so exotic zone names that work in one runtime may still fail in another.

Solutions

  1. Send IANA zone identifiers only: 'America/New_York', 'Europe/Berlin', 'Asia/Kolkata', 'UTC'.
  2. Default to the user's detected zone: Intl.DateTimeFormat().resolvedOptions().timeZone instead of hardcoding or asking.
  3. Validate with the exact same Intl.DateTimeFormat construction client-side before submitting.
  4. If importing legacy data, map Windows tz names to IANA (unicode CLDR mappings) before writing.

Example fix

// before
await mutateAPI.alertRules.setDigestSettings({ variant: 'default', digestMode: 'daily', digestHour: 8, digestTimezone: 'EST' });
// ConvexError: digestTimezone must be a valid IANA timezone (e.g. America/New_York)

// after
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone; // e.g. 'America/New_York'
await mutateAPI.alertRules.setDigestSettings({ variant: 'default', digestMode: 'daily', digestHour: 8, digestTimezone: tz });
Defensive patterns

Strategy: validation

Validate before calling

// Exact server check, client-side.
function isValidTimezone(tz: string): boolean {
  try {
    Intl.DateTimeFormat(undefined, { timeZone: tz });
    return true;
  } catch {
    return false;
  }
}
if (!isValidTimezone(digestTimezone)) {
  setFieldError('digestTimezone', 'Use an IANA zone such as America/New_York');
  return;
}
await mutateAPI.alertRules.setDigestSettings({ variant, digestMode, digestTimezone });

Type guard

const isIanaTimezone = (tz: string): boolean => {
  try {
    Intl.DateTimeFormat(undefined, { timeZone: tz });
    return true;
  } catch {
    return false;
  }
};

Try / catch

try {
  await mutateAPI.alertRules.setDigestSettings(args);
} catch (err) {
  if (err instanceof ConvexError && typeof err.data === 'string' && err.data.includes('digestTimezone')) {
    setFieldError('digestTimezone', 'Use an IANA zone such as America/New_York');
    return;
  }
  throw err;
}

Prevention

When it happens

Trigger: setDigestSettings with digestTimezone: 'EST' / 'PST' / 'UTC+2' / 'GMT-5' (abbreviations and offsets are not IANA IDs); a typo like 'America/New_York.' or 'america/new_york' with unexpected casing; a zone name from a stale OS/browser list that the server runtime does not know.

Common situations: Forms letting users type free-text timezones; syncing the zone from a legacy system that stores Windows tz names ('Eastern Standard Time') or fixed offsets; data imported with abbreviations; browser-resolved zones forwarded from an old client.

Related errors


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

Appendix: 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 eeab0a219f)