我正在考虑简单地使用格式为“+hh:mm”(或“-hh:mm”)的字符串。这既必要又充分吗?
注意:我不需要存储日期或时间,只需要存储时区。
我正在考虑简单地使用格式为“+hh:mm”(或“-hh:mm”)的字符串。这既必要又充分吗?
注意:我不需要存储日期或时间,只需要存储时区。
不幸的是 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。
在理想世界中,您可以拥有一组已知时区的外键。您可以使用视图和域做一些与此类似的事情。
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)
);
"+hh:mm" 和 "-hh:mm" 不是时区,它们是 UTC 偏移量。将这些保存为带符号整数的一种很好的格式,偏移量以分钟为单位。您也可以使用类似的东西,interval
但只有在您想直接在 PostgreSQL 中进行日期计算时才会对您有所帮助,例如在查询等中。通常尽管您使用另一种语言进行这些计算,然后它取决于该语言是否支持类型好,interval
是否有一个好的日期/时间库。但是将整数转换为某种类似的interval
类型,比如 Pythontimedelta
应该是微不足道的,所以我个人会将它存储为整数。
时区有名称,虽然时区没有标准化名称,但“tz”或“zoneinfo”数据库中有一个事实上的标准,即“Europe/Paris”、“Americas/New_York”或“美国/太平洋”。这些应该存储为字符串。
Windows 使用完全不同的名称,例如“浪漫时间”(不要问)。您可以存储它们以及字符串,但我会避免它,这些名称不在 Windows 之外使用,而且这些名称没有意义。此外,Windows 的翻译版本倾向于使用这些时区的翻译名称,这使得情况变得更糟。
像“PDT”和“EST”这样的缩写不能用作时区名称,因为它们不是唯一的。有四个(我认为,或者是五个?)不同的时区都称为“CST”,所以这是不可用的。
简而言之:对于时区,将名称存储为字符串。对于 UTC 偏移量,将偏移量以分钟为单位存储为有符号整数。
在 postgres 中,您已经可以将任何TIMESTAMP
或TIMESTAMPTZ
转换为指定时区或从指定时区转换,因此您无需从表中查找值。您可以直接在检查约束中使用此表达式,因此您也不需要为此创建函数:
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 框架在下拉框中为您提供了时区列表,那么此验证就足够了,然后您的检查约束只是一个备份。
也许是间隔
postgres=#选择间隔'01:30'; 间隔 ---------- 01:30:00 (1 行) postgres=#选择间隔'-01:30'; 间隔 ------------ -01:30:00 (1 行)