【问题标题】:What is an appropriate data type to store a timezone?存储时区的合适数据类型是什么?
【发布时间】:2012-11-30 00:13:44
【问题描述】:

我正在考虑简单地使用格式为“+hh:mm”(或“-hh:mm”)的字符串。这既必要又充分吗?

注意:我不需要存储日期或时间,只需要存储时区。

【问题讨论】:

    标签: postgresql datetime timezone sqldatatypes


    【解决方案1】:

    很遗憾,PostgreSQL 不提供时区数据类型,所以您应该使用text

    interval 乍一看似乎是一个合乎逻辑的选择,并且它适用于某些用途。但是,它没有考虑夏令时,也没有考虑相同UTC偏移量的不同区域具有不同DST规则的事实。

    从 UTC 偏移到时区没有 1:1 映射。

    例如,Australia/Sydney(新南威尔士州)的时区是 UTC+10 (EST) 或 UTC+11 (EDT) 在夏令时。是的,这与美国使用的首字母缩写词EST 相同;时区首字母缩写词在 tzdata 数据库中不是唯一的,这就是 Pg 具有 timezone_abbreviations 设置的原因。更糟糕的是,布里斯班(昆士兰)的经度几乎相同,位于UTC+10 EST...但没有夏令时,因此有时在新南威尔士州夏令时期间与新南威尔士州有-1 的偏移量。

    更新:最近澳大利亚采用了A 前缀,因此它使用AEST 作为其东部州TZ 的首字母缩写,但ESTWST 仍然常用)。

    很困惑?

    如果您只需要存储一个UTC 偏移量,那么interval 是合适的。如果您想存储时区,请将其存储为text。目前验证并转换为时区偏移量很痛苦,但至少它可以应对 DST。

    【讨论】:

    • 是否有指向所有时区的规范文本字符串(澳大利亚/悉尼)的链接?
    • 如果你想优化性能+节省空间(并降低可移植性/灵活性),tz database + enum 会很好
    【解决方案2】:

    在理想情况下,您可以拥有一组已知时区的外键。您可以使用视图和域做一些与此类似的事情。

    David E. Wheleer 的 wiki tip 创建了一个域,该域已被测试为时区的有效性:

    CREATE OR REPLACE FUNCTION is_timezone( tz TEXT ) RETURNS BOOLEAN as $$
    BEGIN
     PERFORM now() AT TIME ZONE tz;
     RETURN TRUE;
    EXCEPTION WHEN invalid_parameter_value THEN
     RETURN FALSE;
    END;
    $$ language plpgsql STABLE;
    
    CREATE DOMAIN timezone AS CITEXT
    CHECK ( is_timezone( value ) );
    

    拥有一个已知时区列表很有用,在这种情况下,您可以省去域,只需在包含已知时区名称(从视图 pg_timezone_names 获得)的一个表中强制执行约束,避免需要在别处公开域:

    CREATE TABLE tzone
    (
      tzone_name text PRIMARY KEY (tzone_name) CHECK (is_timezone(tzone_name))
    );
    
    INSERT INTO tzone (tzone_name)
    SELECT name FROM pg_timezone_names;
    

    然后你可以通过外键强制正确性:

    CREATE TABLE myTable (
    ...
    tzone TEXT REFERENCES tzone(tzone_name)
    );
    

    【讨论】:

      【解决方案3】:

      "+hh:mm" 和 "-hh:mm" 不是时区,它们是 UTC 偏移量。将这些保存为带符号整数的一种很好的格式,偏移量以分钟为单位。您也可以使用 interval 之类的东西,但这只会在您想直接在 PostgreSQL 中进行日期计算时对您有所帮助,例如在查询等中。通常尽管您使用另一种语言进行这些计算,然后它取决于该语言它是否支持interval 类型并且具有良好的日期/时间库。但是将一个整数转换成某种interval-like 类型,比如Pythons timedelta 应该是微不足道的,所以我个人只是将它存储为一个整数。

      时区有名称,尽管时区没有标准化名称,但“tz”或“zoneinfo”数据库中有一个事实上的标准,即“Europe/Paris”、“Americas/New_York”等名称”或“美国/太平洋”。这些应该存储为字符串。

      Windows 使用完全不同的名称,例如“浪漫时光”(不要问)。您可以存储它们以及字符串,但我会避免它,这些名称不在 Windows 之外使用,并且这些名称没有意义。此外,Windows 的翻译版本倾向于使用这些时区的翻译名称,这使得情况更糟。

      “PDT”和“EST”等缩写词不能用作时区名称,因为它们不是唯一的。有四个(我认为,或者是五个?)不同的时区都称为“CST”,因此不可用。

      简而言之:对于时区,将名称存储为字符串。对于 UTC 偏移量,将偏移量以分钟为单位存储为有符号整数。

      【讨论】:

      • 是否有指向所有时区的规范文本字符串(澳大利亚/悉尼)的链接?
      【解决方案4】:

      在 postgres 中,您已经可以将任何 TIMESTAMPTIMESTAMPTZ 转换为指定时区或从指定时区转换,因此您无需从表中查找值。您可以直接在检查约束中使用此表达式,因此您也不需要为此创建函数:

      CREATE TABLE locations (
          location_id SERIAL PRIMARY KEY,
          name TEXT,
          timezone TEXT NOT NULL CHECK (now() AT TIME ZONE timezone IS NOT NULL)
      );
      

      如果您尝试插入一个不包含有效时区的值,您将收到一个实际上对用户友好的错误:

      INSERT INTO locations (name, timezone) VALUES ('foo', 'Adelaide/Australia');
      ERROR:  time zone "Adelaide/Australia" not recognized
      

      根据您的要求,您可能需要将错误采用正常违反约束的格式为您提供,但在许多情况下就足够了。

      如果您使用的 Web 框架在下拉框中为您提供了时区列表,那么此验证就足够了,然后您的检查约束只是一个备份。

      【讨论】:

      • postgresql 不会将时区存储在“with timezone”列中,它们只是在选定的时区显示给用户。它们始终以 UTC 格式存储
      • 我不确定您的评论是否适用于我的回答,或者是否适用于这个问题。这是关于存储时区,而不是存储带有时区的时间戳。
      【解决方案5】:

      也许是间隔

      postgres=#选择间隔'01:30'; 间隔 ---------- 01:30:00 (1 行) postgres=#选择间隔'-01:30'; 间隔 ------------ -01:30:00 (1 行)

      【讨论】:

        猜你喜欢
        • 2011-07-21
        • 1970-01-01
        • 2018-11-04
        • 1970-01-01
        • 1970-01-01
        • 2014-06-02
        • 2010-11-05
        • 1970-01-01
        • 2017-01-22
        相关资源
        最近更新 更多