{"record":{"id":"e1d5553a619d38ca","repo":"prestodb/presto","slug":"not-supported-e1d555","errorCode":"NOT_SUPPORTED","errorMessage":"Parquet timestamp must be 12 bytes, actual ","messagePattern":"Parquet timestamp must be 12 bytes, actual ","errorType":"error_code","errorClass":"PrestoException","httpStatus":null,"severity":"error","filePath":"presto-parquet/src/main/java/com/facebook/presto/parquet/ParquetTimestampUtils.java","lineNumber":47,"sourceCode":" */\npublic final class ParquetTimestampUtils\n{\n    private static final int JULIAN_EPOCH_OFFSET_DAYS = 2_440_588;\n    private static final long MILLIS_IN_DAY = TimeUnit.DAYS.toMillis(1);\n    private static final long NANOS_PER_MILLISECOND = TimeUnit.MILLISECONDS.toNanos(1);\n\n    private ParquetTimestampUtils() {}\n\n    /**\n     * Returns GMT timestamp from binary encoded parquet timestamp (12 bytes - julian date + time of day nanos).\n     *\n     * @param timestampBinary INT96 parquet timestamp\n     * @return timestamp in millis, GMT timezone\n     */\n    public static long getTimestampMillis(Binary timestampBinary)\n    {\n        if (timestampBinary.length() != 12) {\n            throw new PrestoException(NOT_SUPPORTED, \"Parquet timestamp must be 12 bytes, actual \" + timestampBinary.length());\n        }\n        byte[] bytes = timestampBinary.getBytes();\n\n        // little endian encoding - need to invert byte order\n        long timeOfDayNanos = Longs.fromBytes(bytes[7], bytes[6], bytes[5], bytes[4], bytes[3], bytes[2], bytes[1], bytes[0]);\n        int julianDay = Ints.fromBytes(bytes[11], bytes[10], bytes[9], bytes[8]);\n\n        return julianDayToMillis(julianDay) + (timeOfDayNanos / NANOS_PER_MILLISECOND);\n    }\n\n    public static long getTimestampMillis(byte[] byteBuffer, int offset)\n    {\n        long timeOfDayNanos = BytesUtils.getLong(byteBuffer, offset);\n        int julianDay = BytesUtils.getInt(byteBuffer, offset + 8);\n\n        return julianDayToMillis(julianDay) + (timeOfDayNanos / NANOS_PER_MILLISECOND);\n    }\n","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/prestodb/presto/blob/55bb57d202de3b926896fa966c2c4a44c779634e/presto-parquet/src/main/java/com/facebook/presto/parquet/ParquetTimestampUtils.java#L29-L65","documentation":"ParquetTimestampUtils.getTimestampMillis only supports INT96 timestamps, which are always 12 bytes: 8 bytes for time-of-day nanos plus 4 bytes for the Julian day. If the supplied Binary has any other length the input is not an INT96 timestamp, and the library throws NOT_SUPPORTED rather than guessing the layout. Usually the column is actually INT64/INT32 (TIMESTAMP_MILLIS/MICROS or DATE) being routed through the INT96 decode path.","triggerScenarios":"Calling getTimestampMillis with a Binary whose length() != 12 — e.g. an 8-byte INT64 timestamp-micros/millis value or a 4-byte DATE value passed to the INT96 decoder.","commonSituations":"Files written with useDeprecatedLogicalTimestamp=false (modern writers emit INT64 timestamps); spark.sql.parquet.int96TimestampConversion / writer version differences; schema mapping config that routes an INT64 timestamp column into the INT96 read path.","solutions":["Check the Parquet schema's physical type for the timestamp column; if it is INT64, decode it via the INT64 timestamp path instead of getTimestampMillis.","If you control the writer, configure it to emit INT96 timestamps, or migrate the reader to support INT64 (TIMESTAMP_MILLIS/MICROS) annotations.","Ensure the Presto/Parquet type mapping maps the column to the right timestamp type so the correct decoder is selected.","Validate binary length before calling: only pass 12-byte binaries to getTimestampMillis."],"exampleFix":"// before\nlong millis = ParquetTimestampUtils.getTimestampMillis(binary); // 8-byte INT64 value -> throws\n// after\nif (binary.length() == 12) {\n    millis = ParquetTimestampUtils.getTimestampMillis(binary);\n} else {\n    millis = decodeInt64Timestamp(binary, isAdjustedToUTC); // use INT64 path\n}","handlingStrategy":"type-guard","validationCode":"// check physical type before decoding\nif (columnDescriptor.getPrimitiveType().getPrimitiveTypeName() != PrimitiveTypeName.INT96) {\n    throw new IllegalArgumentException(\"Column is not INT96: \" + columnDescriptor.getPrimitiveType().getPrimitiveTypeName());\n}","typeGuard":"boolean isInt96Timestamp(Binary b) { return b != null && b.length() == 12; }","tryCatchPattern":"try {\n    return ParquetTimestampUtils.getTimestampMillis(binary);\n} catch (PrestoException e) {\n    if (e.getErrorCode().getCode() == NOT_SUPPORTED.toErrorCode().getCode()) {\n        return decodeInt64Timestamp(binary); // fallback path\n    }\n    throw e;\n}","preventionTips":["Inspect the Parquet schema's physical type for timestamp columns before choosing a decoder.","Set writer config explicitly (INT96 vs INT64) and mirror it in reader type mapping.","Unit-test timestamp columns written by each writer version in your fleet."],"tags":["parquet","timestamp","int96","not-supported"],"backgroundTag":"unsupported-timestamp-encoding","analyzedSha":"55bb57d202de3b926896fa966c2c4a44c779634e","analyzedAt":"2026-09-04T12:50:26.162Z","contentChangedAt":"2026-09-04T12:50:26.162Z","schemaVersion":2},"datasetVersion":"2026-09-11T21:17:09.523Z"}