{"record":{"id":"cef7ce5b0b20e9ca","repo":"cube-js/cube","slug":"timezone-must-be-a-string-got-typeof-value","errorCode":null,"errorMessage":"Timezone must be a string, got ${typeof value}","messagePattern":"Timezone must be a string, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/cubejs-backend-shared/src/timezone.ts","lineNumber":15,"sourceCode":"import moment from 'moment-timezone';\n\n/**\n * Resolves `value` to a canonical tz-database zone name, matched case-insensitively,\n * or `null` when it is unset or not a known zone.\n *\n * @throws {TypeError} when `value` is an empty string, or is neither a string nor unset.\n */\nexport function canonicalTimezone(value?: string | null): string | null {\n  if (value === undefined || value === null) {\n    return null;\n  }\n\n  if (typeof value !== 'string') {\n    throw new TypeError(`Timezone must be a string, got ${typeof value}`);\n  }\n\n  if (value === '') {\n    throw new TypeError('Timezone must not be empty');\n  }\n\n  const zone = moment.tz.zone(value);\n\n  return zone ? zone.name : null;\n}\n","sourceCodeStart":1,"sourceCodeEnd":26,"githubUrl":"https://github.com/cube-js/cube/blob/7d981676b36392fec34088b9afab6bdcad40207c/packages/cubejs-backend-shared/src/timezone.ts#L1-L26","documentation":"canonicalTimezone in packages/cubejs-backend-shared/src/timezone.ts normalizes a timezone value to its canonical IANA zone name. Because it is called with untyped inputs (from config, requests, env), it guards its `string | null | undefined` contract at runtime: any non-string, non-nullish value (number, boolean, object) throws a TypeError naming the actual typeof.","triggerScenarios":"canonicalTimezone(123), canonicalTimezone({ tz: 'UTC' }), canonicalTimezone(true) — any caller bypassing TypeScript types, e.g. passing req.query.tz (string[] | string | ParsedQs) or a parsed number from config.","commonSituations":"Express query params (arrays under ?tz=a&tz=b), JSON config where the tz field is accidentally a number/boolean, `parseInt` on a zone offset passed instead of the name.","solutions":["Ensure the caller passes a string (or null/undefined for 'no timezone') — coerce with String(value) only if the value is genuinely a timezone name.","If the value comes from req.query, take the first element of a possible array and verify it is a string.","Fix the upstream config so the tz field holds a timezone name string like 'UTC'.","Wrap the call in a validation helper that rejects non-string values with a domain-specific error."],"exampleFix":"// before\ncanonicalTimezone(req.query.tz); // may be string[] → TypeError\n\n// after\nconst raw = Array.isArray(req.query.tz) ? req.query.tz[0] : req.query.tz;\ncanonicalTimezone(typeof raw === 'string' ? raw : null);","handlingStrategy":"type-guard","validationCode":"function toStringOrNull(v: unknown): string | null {\n  if (v === undefined || v === null) return null;\n  return typeof v === 'string' ? v : null; // reject non-strings instead of coercing\n}","typeGuard":"function isTimezoneInput(v: unknown): v is string | null | undefined {\n  return v === undefined || v === null || typeof v === 'string';\n}","tryCatchPattern":"try {\n  const tz = canonicalTimezone(raw);\n} catch (e) {\n  if (e instanceof TypeError && /Timezone must be a string/.test(e.message)) {\n    throw new ConfigError(`timezone config must be a string, got ${typeof raw}`);\n  }\n  throw e;\n}","preventionTips":["Never pass req.query values directly; normalize arrays and ParsedQs first","Validate config objects with a schema (zod/joi) enforcing string|null timezone fields","Avoid parseInt/toNumber on timezone values","Keep TS types honest at boundaries with runtime checks"],"tags":["timezone","typeerror","validation","typescript"],"backgroundTag":"timezone-type-mismatch","analyzedSha":"7d981676b36392fec34088b9afab6bdcad40207c","analyzedAt":"2026-09-02T03:45:10.400Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}