32

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

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

4

5 回答 5

38

不幸的是 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 的首字母缩写词,但EST仍然WST常用)。

很困惑?

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

于 2012-12-12T11:34:21.067 回答
17

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

David E. Wheleer 的此wiki 提示创建了一个域,该域已测试其作为时区的有效性:

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)
);
于 2015-03-06T23:37:00.953 回答
14

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

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

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

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

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

于 2012-12-12T13:31:46.367 回答
11

在 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 框架在下拉框中为您提供了时区列表,那么此验证就足够了,然后您的检查约束只是一个备份。

于 2019-05-27T07:30:37.057 回答
-2

也许是间隔

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

postgres=#选择间隔'-01:30';
 间隔  
------------
 -01:30:00
(1 行)
于 2012-12-12T10:53:56.253 回答