存储时区的适当数据类型是什么?

时间:2012-12-12 10:04:05

标签: postgresql datetime timezone sqldatatypes

我想简单地使用格式为" + hh:mm" (或" -hh:mm")。这既充实又充足?

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

5 个答案:

答案 0 :(得分:24)

不幸的是PostgreSQL没有提供时区数据类型,所以你应该使用text

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

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

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

更新:最近澳大利亚采用了A前缀,因此它使用AEST作为东部各州的TZ首字母缩写,但是EST和{{1保持常用)。

困惑多少?

如果您需要存储的是 UTC偏移量,那么WST是合适的。如果您要存储时区,请将其存储为interval。目前验证和转换为时区偏移是一种痛苦,但至少它应对DST。

答案 1 :(得分:13)

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

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

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

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

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

答案 2 :(得分:13)

在理想世界中,您可以拥有一组已知时区的外键。您可以通过视图和域来做一些与此相近的事情。

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 :(得分:0)

也许间隔

postgres=# select interval '01:30';
 interval 
----------
 01:30:00
(1 row)

postgres=# select interval '-01:30';
 interval  
-----------
 -01:30:00
(1 row)

答案 4 :(得分:0)

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