时间戳#
Arrow/Pandas 时间戳#
Arrow 时间戳存储为 64 位整数,带有列元数据以关联时间单位(例如,毫秒、微秒或纳秒)以及可选的时区。 Pandas (Timestamp
) 使用表示纳秒的 64 位整数和可选的时区。没有关联时区的 Python/Pandas 时间戳类型被称为“时区未识别”。具有关联时区的 Python/Pandas 时间戳类型被称为“时区已识别”。
时间戳转换#
Pandas/Arrow ⇄ Spark#
Spark 将时间戳存储为自 UNIX 纪元以来表示微秒的 64 位整数。它不会在其时间戳中存储任何有关时区的元数据。
Spark 使用会话本地时区(即 spark.sql.session.timeZone
)解释时间戳。如果该时区未定义,Spark 会使用默认系统时区。为了简便起见,以下内容始终定义会话本地时区。
这意味着在往返时间戳时会有一些影响:
时区信息会丢失(从 Spark 转换为 Arrow/Pandas 产生的所有时间戳都是“时区未识别”)。
时间戳会被截断为微秒。
会话时区可能会对时间戳值的转换产生不直观的影响。
Spark 到 Pandas(通过 Apache Arrow)#
以下案例假设 Spark 配置 spark.sql.execution.arrow.enabled
设置为 "true"
。
>>> pdf = pd.DataFrame({'naive': [datetime(2019, 1, 1, 0)],
... 'aware': [Timestamp(year=2019, month=1, day=1,
... nanosecond=500, tz=timezone(timedelta(hours=-8)))]})
>>> pdf
naive aware
0 2019-01-01 2019-01-01 00:00:00.000000500-08:00
>>> spark.conf.set("spark.sql.session.timeZone", "UTC")
>>> utc_df = sqlContext.createDataFrame(pdf)
>>> utf_df.show()
+-------------------+-------------------+
| naive| aware|
+-------------------+-------------------+
|2019-01-01 00:00:00|2019-01-01 08:00:00|
+-------------------+-------------------+
请注意,已识别时区的转换会发生偏移以反映假设为 UTC 的时间(它代表同一时间点)。对于未识别时区的时间戳,Spark 将它们视为系统本地时区,并将其转换为 UTC。回想一下,在内部,Spark 数据帧的模式不会存储任何时间戳的时区信息。
现在,如果将会话时区设置为美国太平洋时间 (PST),我们不会看到已识别时区的显示发生任何偏移(它仍然代表同一时间点)
>>> spark.conf.set("spark.sql.session.timeZone", "US/Pacific")
>>> pst_df = sqlContext.createDataFrame(pdf)
>>> pst_df.show()
+-------------------+-------------------+
| naive| aware|
+-------------------+-------------------+
|2019-01-01 00:00:00|2019-01-01 00:00:00|
+-------------------+-------------------+
再次查看 utc_df.show(),我们可以看到会话时区的影响之一。未识别时间戳最初是假设为 UTC 进行转换的,它反映的时间点实际上比从 PST 转换的数据帧的未识别时区要早
>>> utc_df.show()
+-------------------+-------------------+
| naive| aware|
+-------------------+-------------------+
|2018-12-31 16:00:00|2019-01-01 00:00:00|
+-------------------+-------------------+
Spark 到 Pandas#
我们可以观察到转换回 Arrow/Pandas 时会发生什么。假设会话时区仍然是 PST
>>> pst_df.show()
+-------------------+-------------------+
| naive| aware|
+-------------------+-------------------+
|2019-01-01 00:00:00|2019-01-01 00:00:00|
+-------------------+-------------------+
>>> pst_df.toPandas()
naive aware
0 2019-01-01 2019-01-01
>>> pst_df.toPandas().info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 1 entries, 0 to 0
Data columns (total 2 columns):
naive 1 non-null datetime64[ns]
aware 1 non-null datetime64[ns]
dtypes: datetime64[ns](2)
memory usage: 96.0 bytes
请注意,除了是“时区未识别”时间戳之外,在转换为纪元偏移量时,“已识别”值现在会有所不同。Spark 通过首先转换为会话时区(如果未设置会话时区,则为系统本地时区),然后本地化以删除时区信息来完成转换。这会导致时间戳比原始时间提前 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', tz='UTC-08:00')
>>> (pst_df.toPandas()['aware'][0].timestamp()-pdf['aware'][0].timestamp())/3600
-8.0
当会话时区为 UTC 时,转换数据帧时也会发生相同的转换类型。在这种情况下,未识别和已识别都代表不同的时间点(未识别时间点是由于创建数据帧时会话时区发生变化所致)
>>> utc_df.show()
+-------------------+-------------------+
| naive| aware|
+-------------------+-------------------+
|2018-12-31 16:00:00|2019-01-01 00:00:00|
+-------------------+-------------------+
>>> utc_df.toPandas()
naive aware
0 2018-12-31 16:00:00 2019-01-01
请注意,当会话时区为 UTC 时,已识别的意外偏移不会发生(但时间戳仍然变为“时区未识别”)
>>> spark.conf.set("spark.sql.session.timeZone", "UTC")
>>> pst_df.show()
+-------------------+-------------------+
| naive| aware|
+-------------------+-------------------+
|2019-01-01 08:00:00|2019-01-01 08:00:00|
+-------------------+-------------------+
>>> pst_df.toPandas()['aware'][0]
Timestamp('2019-01-01 08:00:00')
>>> pdf['aware'][0]
Timestamp('2019-01-01 00:00:00.000000500-0800', tz='UTC-08:00')
>>> (pst_df.toPandas()['aware'][0].timestamp()-pdf['aware'][0].timestamp())/3600
0.0