跳到主要内容

时区

与 Apache Superset 相关的时区组件有以下四种:

  1. 底层数据编码的时区。
  2. 数据库引擎的时区。
  3. Apache Superset 后端的时区。
  4. Apache Superset 客户端的时区。

其中,如果时间字段(DATETIMETIMETIMESTAMP 等)未明确定义时区,则默认使用该组件的底层时区。

为了使问题在某种程度上易于处理——考虑到 Apache Superset 无法控制数据摄取方式 (1) 或客户端时区 (4)——从一致性的角度来看,强烈建议将 (2) 和 (3) 配置为使用相同的时区,并强烈推荐使用 UTC,以确保没有明确时间戳的时间字段不会被错误地强制转换为错误的时区。实际上,Apache Superset 目前隐式假定时间戳采用 UTC,因此将 (3) 配置为非 UTC 时区可能会导致问题。

为了追求数据一致性(无论客户端的时区如何),Apache Superset 后端会尝试确保发送给客户端的任何时间戳都包含明确(或半明确,如 Epoch 时间,其始终参照 UTC)编码的时区。

然而,挑战在于 Apache Superset 支持的众多数据库引擎以及它们 Python 数据库 API (DB-API) 实现之间存在的各种不一致性,再加上我们在将 SQL 序列化为 JSON 之前使用 Pandas 将其读取到 DataFrame 中。遗憾的是,Pandas 默认忽略 DB-API 的 type_code,而是依赖 DB-API 返回的底层 Python 类型。目前,只有一部分受支持的数据库引擎能与 Pandas 正确配合使用,即确保没有明确时间戳的时间戳能够以服务器时区序列化为 JSON,从而保证客户端无论时区如何都能以一致的方式显示时间戳。

例如,以下是 MySQL 和 Presto 的比较:

import pandas as pd
from sqlalchemy import create_engine

pd.read_sql_query(
sql="SELECT TIMESTAMP('2022-01-01 00:00:00') AS ts",
con=create_engine("mysql://root@localhost:3360"),
).to_json()

pd.read_sql_query(
sql="SELECT TIMESTAMP '2022-01-01 00:00:00' AS ts",
con=create_engine("presto://localhost:8080"),
).to_json()

其分别输出 {"ts":{"0":1640995200000}}(根据 Epoch 时间定义推断为 UTC 时区)和 {"ts":{"0":"2022-01-01 00:00:00.000"}}(没有明确的时区),因此在 JavaScript 中会被区别对待。

new Date(1640995200000)
> Sat Jan 01 2022 13:00:00 GMT+1300 (New Zealand Daylight Time)

new Date("2022-01-01 00:00:00.000")
> Sat Jan 01 2022 00:00:00 GMT+1300 (New Zealand Daylight Time)