{"record":{"id":"4cc067c34cf98fa1","repo":"apache/iceberg","slug":"expected-value-to-be-timestamp-valuetype-catalo-4cc067","errorCode":null,"errorMessage":"Expected value to be timestamp: ${valueType.catalogString()}","messagePattern":"Expected value to be timestamp: (.+?)","errorType":"exception","errorClass":"UnsupportedOperationException","httpStatus":null,"severity":"error","filePath":"spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/functions/HoursFunction.java","lineNumber":46,"sourceCode":"import org.apache.spark.sql.types.DataTypes;\nimport org.apache.spark.sql.types.TimestampNTZType;\nimport org.apache.spark.sql.types.TimestampType;\n\n/**\n * A Spark function implementation for the Iceberg hour transform.\n *\n * <p>Example usage: {@code SELECT system.hours('source_col')}.\n */\npublic class HoursFunction extends UnaryUnboundFunction {\n\n  @Override\n  protected BoundFunction doBind(DataType valueType) {\n    if (valueType instanceof TimestampType) {\n      return new TimestampToHoursFunction();\n    } else if (valueType instanceof TimestampNTZType) {\n      return new TimestampNtzToHoursFunction();\n    } else {\n      throw new UnsupportedOperationException(\n          \"Expected value to be timestamp: \" + valueType.catalogString());\n    }\n  }\n\n  @Override\n  public String description() {\n    return name()\n        + \"(col) - Call Iceberg's hour transform\\n\"\n        + \"  col :: source column (must be timestamp)\";\n  }\n\n  @Override\n  public String name() {\n    return \"hours\";\n  }\n\n  public abstract static class BaseToHourFunction extends BaseScalarFunction<Integer>\n      implements ReducibleFunction<Integer, Integer> {","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/spark/v4.0/spark/src/main/java/org/apache/iceberg/spark/functions/HoursFunction.java#L28-L64","documentation":"Iceberg's hours() transform function binds its single argument at planning time. Only TIMESTAMP and TIMESTAMP_NTZ have a defined hour-granularity transform (hour is meaningless for bare DATE), so any other type — including DATE — throws UnsupportedOperationException with the actual type name.","triggerScenarios":"Calling iceberg.hours(col) where col is DATE, STRING, INT, BIGINT or anything other than timestamp/timestamp_ntz — e.g. hours(d) where d is a DATE column.","commonSituations":"Applying hours() to a date-only column; passing a string timestamp; passing epoch milliseconds BIGINT from event logs.","solutions":["Cast a DATE column to TIMESTAMP if hour info is meaningful: hours(cast(d AS TIMESTAMP)).","Cast a string timestamp: hours(cast(ts_str AS TIMESTAMP)).","Convert epoch millis: hours(timestamp_millis(epoch_ms_col)).","If the column is truly date-only, use iceberg.days(d) instead."],"exampleFix":"// before\nSELECT iceberg.hours(event_date) FROM t; -- DATE\n// after\nSELECT iceberg.hours(CAST(event_date AS TIMESTAMP)) FROM t;","handlingStrategy":"type-guard","validationCode":"require(col.dataType == TimestampType || col.dataType == TimestampNTZType,\n  s\"hours() requires timestamp, got ${col.dataType}\")","typeGuard":"def isTimestampLike(t: DataType): Boolean =\n  t == TimestampType || t == TimestampNTZType","tryCatchPattern":null,"preventionTips":["Use iceberg.days() instead of hours() for DATE columns","Never persist event time as VARCHAR if hour-level partitioning is planned"],"tags":["spark","sql-function-binding","type-mismatch"],"backgroundTag":"type-mismatch","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}