时间戳#

Arrow/Pandas 时间戳#

Arrow 时间戳存储为 64 位整数,带有列元数据,用于关联时间单位(例如,毫秒、微秒或纳秒),以及可选的时区。 Pandas (Timestamp) 使用 64 位整数表示纳秒和可选的时区。 没有关联时区的 Python/Pandas 时间戳类型称为“时区感知较弱”。 具有关联时区的 Python/Pandas 时间戳类型称为“时区感知较强”。

时间戳转换#

Pandas/Arrow ⇄ Spark#

Spark 将时间戳存储为 64 位整数,表示自 UNIX 纪元以来的微秒数。它不存储任何关于时间戳的时区元数据。

Spark 使用会话本地时区(即 spark.sql.session.timeZone)解释时间戳。 如果该时区未定义,Spark 将使用默认系统时区。 为了简单起见,在下面始终定义会话本地时区。

这意味着在往返时间戳时需要注意以下几点

  1. 时区信息会丢失(从 Spark 转换到 Arrow/Pandas 的所有时间戳都是“时区感知较弱”的)。

  2. 时间戳被截断为微秒。

  3. 会话时区可能对时间戳值的转换产生意想不到的影响。

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