{"record":{"id":"9e363e137b1e05ca","repo":"apache/druid","slug":"time-zone-must-be-a-literal","errorCode":null,"errorMessage":"Time zone must be a literal","messagePattern":"Time zone must be a literal","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"processing/src/main/java/org/apache/druid/query/expression/ExprUtils.java","lineNumber":40,"sourceCode":"import org.apache.druid.error.InvalidInput;\nimport org.apache.druid.java.util.common.DateTimes;\nimport org.apache.druid.java.util.common.IAE;\nimport org.apache.druid.java.util.common.granularity.PeriodGranularity;\nimport org.apache.druid.math.expr.Expr;\nimport org.joda.time.Chronology;\nimport org.joda.time.DateTime;\nimport org.joda.time.DateTimeZone;\nimport org.joda.time.Period;\nimport org.joda.time.chrono.ISOChronology;\n\nimport javax.annotation.Nullable;\n\npublic class ExprUtils\n{\n  static DateTimeZone toTimeZone(final Expr timeZoneArg)\n  {\n    if (!timeZoneArg.isLiteral()) {\n      throw new IAE(\"Time zone must be a literal\");\n    }\n\n    final Object literalValue = timeZoneArg.getLiteralValue();\n    return literalValue == null ? DateTimeZone.UTC : DateTimes.inferTzFromString((String) literalValue);\n  }\n\n  static PeriodGranularity toPeriodGranularity(\n      final Expr wrappingExpr,\n      final Expr periodArg,\n      @Nullable final Expr originArg,\n      @Nullable final Expr timeZoneArg,\n      final Expr.ObjectBinding bindings\n  )\n  {\n    final Period period;\n    try {\n      period = new Period(periodArg.eval(bindings).asString());\n    }","sourceCodeStart":22,"sourceCodeEnd":58,"githubUrl":"https://github.com/apache/druid/blob/9b90983fd291f26935af934383ce360473179e4d/processing/src/main/java/org/apache/druid/query/expression/ExprUtils.java#L22-L58","documentation":"ExprUtils.toTimeZone() converts a time-zone argument expression (used by functions like timestamp_shift, time_floor, time_parse) into a DateTimeZone. The argument must be a literal (constant string or null); a dynamic expression such as a column reference cannot be resolved at planning time, so Druid throws this IAE.","triggerScenarios":"Passing a non-literal (column, arithmetic expression, nested function) as the timezone argument of a time function, e.g. TIME_FLOOR(ts, 'P1D', NULL, tzColumn).","commonSituations":"Trying to make the time zone configurable per-row; forgetting quotes so a timezone string is parsed as an identifier/expression.","solutions":["Pass the time zone as a quoted literal: 'America/Los_Angeles' or '+08:00'","If per-row zones are required, compute shifted timestamps in application code or pre-compute at ingestion","Use NULL literal explicitly if UTC is desired"],"exampleFix":"// before\nTIME_FLOOR(__time, 'P1D', NULL, tz_col)\n// after\nTIME_FLOOR(__time, 'P1D', NULL, 'America/Los_Angeles')","handlingStrategy":"validation","validationCode":"if (tzArg != null && !tzArg.matches(\"^([A-Za-z/_+-]+|[+-]\\d\\d:?\\d\\d)$\")) {\n  throw new IllegalArgumentException(\"timezone must be a literal id or offset\");\n}","typeGuard":"boolean isTzLiteral = expr != null && expr.isLiteral() && (expr.getLiteralValue() == null || expr.getLiteralValue() instanceof String);","tryCatchPattern":"try {\n  expr = \"timestamp_shift(__time, 'P1D', 1, 'UTC')\";\n} catch (IAE e) {\n  if (e.getMessage().equals(\"Time zone must be a literal\")) { /* inline the tz value */ }\n}","preventionTips":["Always pass time zones as quoted string literals","Validate timezone ids with DateTimeZone.forID before building the query","Avoid column references in time-function timezone arguments"],"tags":["expression","timezone","query"],"backgroundTag":"invalid-argument-value","analyzedSha":"9b90983fd291f26935af934383ce360473179e4d","analyzedAt":"2026-09-07T13:32:30.957Z","contentChangedAt":"2026-09-07T13:32:30.957Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}