如何在用时区截断时间戳时保留时区信息

How to retain the time zone info when truncating a timestamp with time zone

提问人:Jan Klan 提问时间:9/19/2023 最后编辑:Jan Klan 更新时间:9/19/2023 访问量:38

问:

TL的;博士:

如何选择不同时区(例如,没有分钟和秒)中的截断日期时间信息,以显示使用的时区,例如'1999-12-31 20:59:59+00:00'::timestamptz2000-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

php postgresql datetime timestamp-with-timezone

评论

0赞 jjanes 9/19/2023
未使用 ::timestamptz 或使用 date_trunc 删除时区。它已通过使用 AT TIME ZONE 删除。这是它应该做的。

答:

0赞 jjanes 9/19/2023 #1

您的选项是将会话时区设置为要用于会话的时区,或者禁止手动连接字符串。如果你想在没有可见重复的情况下做后者,你可以写一个函数来做后者。

(本地时区是用户设置的主题,而不是服务器配置)

这几乎已经是PostgreSQL的工作方式了。服务器配置仅控制默认会话时区是什么,如果会话忽略了为自己声明一个时区。因此,使每个会话的第 1 或第 2 个命令设置用户首选时区(在数据库中查找它,如果这是存储首选项的位置)