资讯动态

如何在 PyArrow、Pandas 与 Spark 之间转换时间戳并理解时区丢失和精度截断

发布时间:2026/9/15 19:36:00 来源:尧图企业网站定制
如何在 PyArrow、Pandas 与 Spark 之间转换时间戳并理解时区丢失和精度截断【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow当你在 PyArrow/Pandas 与 PySpark 之间来回传递包含时间列的 DataFrame 时转换后的时间戳会失去时区信息并被截断到微秒精度如果会话时区设置不当数值本身还会发生偏移。这篇文章基于 Apache Arrow 官方文档 docs/source/python/timestamps.rst 中的完整示例带你走一遍 Pandas ⇄ Spark经由 Apache Arrow的时间戳转换流程并解释每一步输出为什么是这样。适用的前提是你使用 PySpark 的 Arrow 集成即spark.sql.execution.arrow.enabled配置为true并且关心 aware带时区时间戳在往返转换中的行为。先看三方的时间戳存储模型理解转换结果的前提是知道各引擎内部怎么存时间Arrow时间戳存为 64 位整数靠列元数据关联时间单位毫秒ms、微秒us或纳秒ns并附带一个可选的时区。PandasTimestamp使用表示纳秒的 64 位整数同样带可选时区。Spark时间戳存为表示 UNIX 纪元以来微秒的 64 位整数不保存任何时区元数据。Spark 按session 时区即spark.sql.session.timeZone解释这些时间戳如果未设置该配置则回退到系统默认时区。由此可以推出往返转换的三个必然结果文档原文归纳时区信息丢失——从 Spark 转换回 Arrow/Pandas 的所有时间戳都是 time zone naive时间戳被截断到微秒Spark 内部精度会话时区的不同设置会对时间戳值的换算产生非直觉的影响。另外两个术语在文档中反复出现先对齐一下不带时区的时间戳类型称为Time Zone Naive带时区的称为Time Zone Aware。第一步准备一个包含 naive 和 aware 两列的 Pandas DataFrame用下面这段文档中的示例代码构造测试数据naive列是不带时区的datetimeaware列是带UTC-08:00时区的pandas.Timestamp且纳秒位为 500用来观察截断import pandas as pd from datetime import datetime, timedelta, timezone pdf pd.DataFrame({naive: [datetime(2019, 1, 1, 0)], aware: [pd.Timestamp(year2019, month1, day1, nanosecond500, tztimezone(timedelta(hours-8)))]}) pdf文档示例输出naive aware 0 2019-01-01 2019-01-01 00:00:00.000000500-08:00Pandas 时间列在转 Arrow 时对应TimestampArray带时区的时间戳会保留时区信息例如pandas.rst中的示例里datetime64[us, UTC]会转成timestamp[us, tzUTC]也就是说 Arrow 侧是能记住时区的——时区丢失发生在进入 Spark 之后。第二步设置会话时区并创建 Spark DataFrame在 Spark 侧创建 DataFrame 前需要先确认两件事spark.sql.execution.arrow.enabled已设为true本文所有 Spark 示例都基于该配置会话时区spark.sql.session.timeZone已显式设置否则 Spark 会用系统默认时区解释时间戳行为取决于运行环境。先以 UTC 会话时区为例以下show()输出均为文档示例输出from pyspark.sql import SparkSession spark SparkSession.builder.appName(MyApp).getOrCreate() spark.conf.set(spark.sql.session.timeZone, UTC) utc_df spark.createDataFrame(pdf) utc_df.show()-------------------------------------- | naive| aware| -------------------------------------- |2019-01-01 00:00:00|2019-01-01 08:00:00| --------------------------------------注意两点行为aware 列被平移显示00:00:00-08:00显示成了08:00:00。这不是数据变了而是同一时刻在 UTC 下的表示——时区换算到会话时区后时刻本身不变。naive 列按会话时区处理Spark 把 naive 时间戳当作系统本地时区的时间并换算到 UTC 存储。此时会话时区是 UTC所以显示不变这个行为在下一步换时区后会显现出影响。再切换到美西时区PST创建第二个 DataFramespark.conf.set(spark.sql.session.timeZone, US/Pacific) pst_df spark.createDataFrame(pdf) pst_df.show()-------------------------------------- | naive| aware| -------------------------------------- |2019-01-01 00:00:00|2019-01-01 00:00:00| --------------------------------------此时 aware 列不再显示平移同一时刻在 US/Pacific 下就是原始墙钟时间。但这时如果重新查看刚才在 UTC 会话时区下创建的utc_df会得到文档示例输出-------------------------------------- | naive| aware| -------------------------------------- |2018-12-31 16:00:00|2019-01-01 00:00:00| --------------------------------------这就是文档强调的会话时区对值换算的非直觉影响utc_df的 naive 列当初是按 UTC 解释的同一数值换到 PST 会话时区下显示时比pst_df的 naive 时刻早了 8 小时。也就是说naive 时间戳在不同会话时区下创建的 DataFrame 里实际代表不同的时刻。第三步转回 Pandas 并验证时区丢失在会话时区仍为 PST 的情况下把pst_df转回 Pandaspst_df.toPandas()文档示例输出naive aware 0 2019-01-01 2019-01-01用info()检查两列的 dtypepst_df.toPandas().info()文档示例输出class pandas.core.frame.DataFrame RangeIndex: 1 entries, 0 to 0 Data columns (total 2 columns): # Column Non-Null Count Dtype --- ------ -------------- ----- 0 naive 1 non-null datetime64[ns] 1 aware 1 non-null datetime64[ns] dtypes: datetime64ns这就是第一个可核对的结论两列都变成了不带时区的datetime64[ns]。原始aware列在 Pandas 里是带UTC-08:00的Timestamp往返后时区信息已经不存在。进一步对比 epoch 偏移Spark 先转换到会话时区再 localise 掉时区信息结果是该时间戳比原始时刻早了 8 小时pst_df.toPandas()[aware][0]文档示例输出Timestamp(2019-01-01 00:00:00)与原始值对比pdf[aware][0]Timestamp(2019-01-01 00:00:00.000000500-0800, tzUTC-08:00)计算两者 epoch 差值小时(pst_df.toPandas()[aware][0].timestamp()-pdf[aware][0].timestamp())/3600文档示例输出-8.0也就是说会话时区为 PST 时aware 时间戳转回 Pandas 后比原始时刻少了 8 小时。而在会话时区为 UTC 时同一检查得到的差值是0.0文档示例输出——此时 aware 列不会发生这个意外平移但注意它同样变成了 time zone naivespark.conf.set(spark.sql.session.timeZone, UTC) pst_df.toPandas()[aware][0]Timestamp(2019-01-01 08:00:00)所以判断方法可以归纳为转换后先用info()确认列是否变成不带时区的datetime64[ns]再用上面的 epoch 差值公式确认数值偏移量是否符合会话时区与原始时区的差。精度截断在哪里发生示例中aware值带有nanosecond500原始值显示为00:00:00.000000500-08:00但进入 Spark 后show()与转回 Pandas 的结果中都只剩下00:00:00——纳秒部分被丢掉了这正是Timestamps are truncated to microseconds的具体体现Spark 内部精度是微秒任何微秒以下的分量在往返后不可恢复。文档对 Spark 往返部分没有提供额外的截断告警或绕过选项需要纳秒精度时应避免经过 Spark。如果你不走 Spark 的内存通道而是用 Parquet 文件在框架间交换数据PyArrow 文档 parquet_type_handling.rst 对时间戳写入给出了对应的控制项可以作为可选分支了解默认写 Parquet 1.0 文件时纳秒会被 cast 到微秒用coerce_timestampsms可指定目标精度pq.write_table(table, example.parquet, coerce_timestampsms)低精度 cast 可能丢数据时默认抛异常传allow_truncated_timestampsTrue可抑制pq.write_table(table, example.parquet, coerce_timestampsms, allow_truncated_timestampsTrue)Parquet 2.6 格式可以不 cast 保存纳秒时间戳但文档同时提醒许多 Parquet 读取器尚不支持该版本跨框架兼容时推荐默认的 1.0部分旧版 Spark以及 Impala使用已废弃的INT96存储时间戳如需向这些读取器写文件在write_table中设置use_deprecated_int96_timestampsTruepq.write_table(table, example.parquet, use_deprecated_int96_timestampsTrue)限制与结论结合 timestamps.rst 的说明这条集成路径有明确的边界经 Spark 往返后时间戳必然变成 time zone naive且被截断到微秒这两点无法通过配置规避。数值是否偏移、偏移多少取决于spark.sql.session.timeZone与原始时区的关系。示例中 PST 会话时区得到-8.0小时的 epoch 差UTC 会话时区得到0.0naive 时间戳在不同会话时区下创建时甚至代表不同时刻跨时区环境下尤其要小心。若需要保留时区或纳秒精度应绕过 Spark 的 Arrow 通道在 PyArrow/Pandas 侧直接处理或用支持timestamp[us, tz...]的 Parquet 2.6 格式做文件级交换注意读取器支持范围。【免费下载链接】arrowApache Arrow is the universal columnar format and multi-language toolbox for fast data interchange and in-memory analytics项目地址: https://gitcode.com/GitHub_Trending/arrow3/arrow创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价