时间戳#
Arrow/Pandas 时间戳#
Arrow 时间戳存储为 64 位整数,并带有列元数据以关联时间单位(例如毫秒、微秒或纳秒)和可选时区。Pandas (Timestamp) 使用表示纳秒的 64 位整数和可选时区。没有关联时区的 Python/Pandas 时间戳类型被称为“时区无关”(Time Zone Naive)。具有关联时区的 Python/Pandas 时间戳类型被称为“时区感知”(Time Zone Aware)。
时间戳转换#
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