时区
与 Apache Superset 相关的时区组件有四个:
- 底层数据编码所使用的时区。
- 数据库引擎的时区。
- Apache Superset 后端的时区。
- Apache Superset 客户端的时区。
如果时间字段(如 DATETIME
、TIME
、TIMESTAMP
等)未明确指定时区,则默认使用相应组件的底层时区。
为了使问题变得可管理——考虑到 Apache Superset 无法控制数据如何被摄取(1)或客户端的时区(4)——从一致性的角度来看,强烈建议将(2)和(3)配置为使用相同的时区,并且强烈倾向于使用 UTC,以确保没有明确时间戳的时间字段不会被错误地强制转换到错误的时区。实际上,Apache Superset 目前隐含地假设时间戳为 UTC,因此将(3)配置为非 UTC 时区可能会出现问题。
为了追求数据一致性(无论客户端的时区如何),Apache Superset 后端试图确保发送给客户端的任何时间戳都明确(或在 Epoch 时间 的情况下,总是参考 UTC 的半明确)编码了时区。
然而,挑战在于 Apache Superset 支持的众多 数据库引擎 以及它们在 Python 数据库 API (DB-API) 实现中的各种不一致,再加上我们使用 Pandas 在序列化为 JSON 之前将 SQL 读入 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)