提问人:Jan Klan 提问时间:9/19/2023 最后编辑:Jan Klan 更新时间:9/19/2023 访问量:38
如何在用时区截断时间戳时保留时区信息
How to retain the time zone info when truncating a timestamp with time zone
问:
TL的;博士:
如何选择不同时区(例如,没有分钟和秒)中的截断日期时间信息,以显示使用的时区,例如'1999-12-31 20:59:59+00:00'::timestamptz
2000-01-01 07:00:00 +10:30
在时间戳以 UTC 存储的多时区应用程序中,我想计算作为 SQL 查询的一部分提供的本地时区中按分钟、小时、天等分组的行数(本地时区是用户设置的主题,而不是服务器配置)
我正在尝试在时间戳上使用,但时区信息未显示在最终产品中。date_trunc
原因是我随后获取生成的日期并将其传递给 PHP 的 DateTime 对象。在此过程中,如果时区不是结果字符串的一部分,则信息将丢失。
这是我到目前为止尝试过的,在UTC中进行了几次测试,以验证我是否在输入中使用正确的数据:
CREATE TEMPORARY TABLE tstest (
ts timestamptz not null
);
INSERT INTO tstest VALUES ('1999-12-31 20:59:59'::timestamptz at time zone 'UTC');
select 1 AS row, ts::text, 'Control: Original value. Correct in local time zone, showing TZ' AS observation from tstest union
select 2, (ts AT TIME ZONE 'UTC')::text, 'Control: Correct in UTC, but no TZ' from tstest union
select 3, (ts::timestamptz AT TIME ZONE 'Australia/Adelaide')::text, 'Test case: Correct in Adelaide, **but no TZ information**' from tstest union
select 4, date_trunc('hour', ts::timestamptz)::text, 'Truncated Control: Correct in local time zone, showing TZ' from tstest UNION
select 5, date_trunc('hour', ts::timestamptz AT TIME ZONE 'UTC')::text, 'Truncated Control: Correct in UTC, but no TZ' from tstest UNION
select 6, date_trunc('hour', ts::timestamptz AT TIME ZONE 'Australia/Adelaide')::text, 'Truncated Test case: Correct time in Adelaide, **but no TZ information**' from tstest UNION
select 7, to_char(date_trunc('hour', ts::timestamptz AT TIME ZONE 'Australia/Adelaide'), 'YYYY-mm-dd HH:MI:ss OF')::text, 'Truncated test case: Correct time in Adelaide, **but the TZ shown is incorrect**' from tstest UNION
select 8, concat(to_char(date_trunc('hour', ts::timestamptz AT TIME ZONE 'Australia/Adelaide'), 'YYYY-mm-dd HH:MI:ss')::text, ' Australia/Adelaide'), 'Desired result, **but concatenated manually**.' from tstest
;
导致:
排 | ts | 观察 |
---|---|---|
1 | 1999-12-31 20:59:59+00 | 控件:原始值。在本地时区正确,显示 TZ |
2 | 1999-12-31 20:59:59 | 控制:在 UTC 中正确,但没有 TZ |
3 | 2000-01-01 07:29:59 | 测试用例:在阿德莱德正确,但无 TZ 信息** |
4 | 1999-12-31 20:00:00+00 | 截断控件:在本地时区中正确,显示 TZ |
5 | 1999-12-31 20:00:00 | 截断控件:在 UTC 中正确,但没有 TZ |
6 | 2000-01-01 07:00:00 | 截断的测试用例:阿德莱德的正确时间,**但没有 TZ 信息** |
7 | 2000-01-01 07:00:00 +00 | 截断的测试用例:阿德莱德时间正确,但显示的 TZ 不正确** |
8 | 2000-01-01 07:00:00 澳大利亚/阿德莱德 | 所需的结果,**但手动连接**。 |
第 3 行、第 6 行和第 7 行是我追求的时间,但缺少(正确的)时区。第 8 行是我最终追求的(我认为 TZ 名称比 OFFSET 更好,因为 DST),但我发现很难接受在将 TZ 感知时间戳转换为字符串后,我需要自己附加 TZ 信息。
我错过了什么?
以下是这些日期如何转换为PHP对象(=为什么我需要时区成为SELECT结果中字符串输出的一部分): https://3v4l.org/u7A8q#v8.2.10
<?php
// @see https://stackoverflow.com/questions/77131068/how-to-retain-the-time-zone-info-when-truncating-a-timestamp-with-time-zone
date_default_timezone_set('UTC');
$dates = [
'1999-12-31 20:59:59+00',
'1999-12-31 20:59:59',
'2000-01-01 07:29:59',
'1999-12-31 20:00:00+00',
'1999-12-31 20:00:00',
'2000-01-01 07:00:00',
'2000-01-01 07:00:00 +00',
'2000-01-01 07:00:00 Australia/Adelaide'
];
foreach ($dates as $dateString) {
$date = new \DateTimeImmutable($dateString);
echo $dateString."\n-> ".$date->format('r')."\n\n";
}
生产:
1999-12-31 20:59:59+00
-> Fri, 31 Dec 1999 20:59:59 +0000
1999-12-31 20:59:59
-> Fri, 31 Dec 1999 20:59:59 +0000
2000-01-01 07:29:59
-> Sat, 01 Jan 2000 07:29:59 +0000
1999-12-31 20:00:00+00
-> Fri, 31 Dec 1999 20:00:00 +0000
1999-12-31 20:00:00
-> Fri, 31 Dec 1999 20:00:00 +0000
2000-01-01 07:00:00
-> Sat, 01 Jan 2000 07:00:00 +0000
2000-01-01 07:00:00 +00
-> Sat, 01 Jan 2000 07:00:00 +0000
2000-01-01 07:00:00 Australia/Adelaide
-> Sat, 01 Jan 2000 07:00:00 +1030
我看到过类似的问题(时区感知date_trunc函数,date_trunc具有原始时间戳的时区,date_trunc具有原始时间戳的时区),但没有一个放大到我认为是我的问题:使用或date_trunc
::timestamptz
答:
您的选项是将会话时区设置为要用于会话的时区,或者禁止手动连接字符串。如果你想在没有可见重复的情况下做后者,你可以写一个函数来做后者。
(本地时区是用户设置的主题,而不是服务器配置)
这几乎已经是PostgreSQL的工作方式了。服务器配置仅控制默认会话时区是什么,如果会话忽略了为自己声明一个时区。因此,使每个会话的第 1 或第 2 个命令设置用户首选时区(在数据库中查找它,如果这是存储首选项的位置)
评论