Hive3与Spark3处理早期时间戳异常及跨版本兼容方案问询
问题背景
我有一份包含1970-01-01 00:00:00至0000-01-01 00:00:00新年日期的基础CSV,已在Hive中创建外部表test.ny(dt string),所有机器时区为Europe/Moscow。
Hive 2中创建Parquet表
执行以下SQL创建Parquet表test.ny2:
create table test.ny2 stored as parquet as select dt, unix_timestamp(dt||' 00:00:00') dt2, cast(dt as timestamp) dt3 from test.ny --this is my csv
设置set spark.sql.legacy.parquet.int96RebaseModeInRead=LEGACY后,通过spark-sql可正确读取所有dt3值为YYYY-01-01 00:00:00。
Hive 3读取test.ny2的异常
通过Hive 3读取该表时,出现两处异常:
- 1900年及更早的时间戳偏移:
dt dt2 dt3 1901-01-01 00:00:00 -2177461817 1901-01-01 00:00:00.000 1900-01-01 00:00:00 -2208999600 1899-12-31 23:30:17.000
- 0000-0003年的时间戳错误:
dt dt2 dt3 0003-01-01 00:00:00 -62072708400 0002-12-29 23:30:17.000 0002-01-01 00:00:00 -62104244400 0001-12-29 23:30:17.000 0001-01-01 00:00:00 -62135780400 0001-12-29 23:30:17.000 0000-01-01 00:00:00 -62167402800 0002-12-29 23:30:17.000
Hive 3.1.3重新创建表test.ny3后的问题
在Hive 3.1.3中重新创建同名Parquet表test.ny3后,查询仍存在0000-0001年的时间戳错误:
dt dt2 dt3 0003-01-01 00:00:00 -62072697600 0003-01-01 00:00:00.000 0002-01-01 00:00:00 -62104233600 0002-01-01 00:00:00.000 0001-01-01 00:00:00 -62135769600 0002-01-01 00:00:00.000 0000-01-01 00:00:00 -62167392000 0002-01-01 00:00:00.000
Spark SQL读取的问题
使用spark-sql读取时,无论设置LEGACY还是CORRECTED模式,均无法完全正确获取0000年的时间戳:
- LEGACY模式:
dt dt2 dt3 0003-01-01 00:00:00 -62072697600 0003-01-03 00:29:43 0002-01-01 00:00:00 -62104233600 0002-01-03 00:29:43 0001-01-01 00:00:00 -62135769600 0001-01-03 00:29:43 0000-01-01 00:00:00 -62167392000 0001-01-03 00:29:43
- CORRECTED模式:
dt dt2 dt3 0003-01-01 00:00:00 -62072697600 0003-01-01 00:00:00 0002-01-01 00:00:00 -62104233600 0002-01-01 00:00:00 0001-01-01 00:00:00 -62135769600 0001-01-01 00:00:00 0000-01-01 00:00:00 -62167392000 0001-01-01 00:00:00 --notice the year!
疑问与解答
疑问1:Hive3为何无法正确处理0000、0001年左右的时间戳?
核心原因是Hive 3升级了JDK时区数据与日历实现:
- 旧版Hive(2.x)依赖的JDK使用旧版时区数据库,对莫斯科时区的历史偏移计算和新版存在差异,尤其是1900年前后的时区变更记录不同,导致时间戳转换出现偏移。
- 对于0000-0001年这类公元元年前后的日期,JDK新版日历调整了儒略历与格里高利历的转换逻辑、公元元年前后的日期计算规则,和旧版Hive的逻辑不一致,直接引发日期转换错误。另外Hive 3对timestamp类型的范围校验、解析逻辑也做了调整,旧版允许的边缘日期在新版中处理逻辑改变。
疑问2:如何在同一Spark会话中读取Hive2和Hive3生成的表?
提供两种实现路径:
路径1:让Hive3使用旧版日历与时区数据库逻辑,以LEGACY模式读取所有表
Hive端配置:
在Hive 3的hive-site.xml中添加以下配置,强制使用旧版逻辑:<property> <name>hive.parquet.int96.rebase.mode</name> <value>LEGACY</value> </property> <property> <name>hive.datetime.format.legacy</name> <value>true</value> </property>或者在Hive会话中临时设置:
set hive.parquet.int96.rebase.mode=LEGACY; set hive.datetime.format.legacy=true;这两个配置分别控制Parquet INT96时间戳的重基模式、日期时间格式的旧版解析逻辑,让Hive 3兼容Hive 2生成的表。
Spark端配合:
保持Spark的spark.sql.legacy.parquet.int96RebaseModeInRead=LEGACY配置不变,同时添加:set spark.sql.legacy.timeParserPolicy=LEGACY;确保Spark的日期解析逻辑与Hive 3的旧版模式对齐。
路径2:在Hive3中修正Hive2生成的表,适配新版日历与时区逻辑,以Spark CORRECTED模式读取
- 修正Hive2生成的表test.ny2:
在Hive 3中创建新的Parquet表,手动对时间戳进行重基修正:
或者直接从原始CSV重新生成适配Hive 3的表:create table test.ny2_corrected stored as parquet as select dt, dt2, -- 基于正确的时区与日历逻辑重新转换日期字符串为timestamp from_utc_timestamp(to_utc_timestamp(cast(dt as timestamp), 'Europe/Moscow'), 'Europe/Moscow') as dt3_corrected from test.ny2;create table test.ny2_corrected stored as parquet as select dt, unix_timestamp(dt||' 00:00:00') dt2, cast(dt as timestamp) dt3 from test.ny; - Spark端配置:
设置Spark为CORRECTED模式:
这样Spark就能正确读取Hive 3生成的修正表,若需兼容Hive 2旧表,可临时切换配置读取,或提前将所有旧表转换为修正版本。set spark.sql.legacy.parquet.int96RebaseModeInRead=CORRECTED; set spark.sql.timeZone=Europe/Moscow;
内容的提问来源于stack exchange,提问作者Alexey
相关产品推荐
相关产品推荐

