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
- Send IANA zone identifiers only: 'America/New_York', 'Europe/Berlin', 'Asia/Kolkata', 'UTC'.
- Default to the user's detected zone: Intl.DateTimeFormat().resolvedOptions().timeZone instead of hardcoding or asking.
- Validate with the exact same Intl.DateTimeFormat construction client-side before submitting.
- 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
- Default to Intl.DateTimeFormat().resolvedOptions().timeZone instead of free text.
- Offer a select/datalist of IANA zones in the UI.
- Reject abbreviations ('EST', 'PST') and offsets ('UTC+2') at the form level.
- Map Windows tz names to IANA when importing external data.
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
- quietHoursTimezone must be a valid IANA timezone (e.g…
- digestHour must be an integer 0–23
- COMPANY_MONITORING_ADMISSION_EVIDENCE_STALE
- COMPANY_MONITORING_ADMISSION_QUERY_VERSION_INVALID
- COMPANY_MONITORING_EVIDENCE_REVISION_INVALID
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)