我尝试理解PostgresTIMESTAMP WITH TIMEZONE中使用的一些查询示例。
给出了一些将now结果转换为时区“CET”和“CEST”的时间戳的查询。“CEST”比 CET(夏令时间)早 1 小时
例子:
\nSELECT 1, now()\nUNION\nSELECT 2, now()::timestamp\nUNION\nSELECT 3, now() AT TIME ZONE \'CET\'\nUNION\nSELECT 4, now()::timestamp AT TIME ZONE \'CET\'\nUNION\nSELECT 5, now() AT TIME ZONE \'CEST\'\nUNION\nSELECT 6, now()::timestamp AT TIME ZONE \'CEST\'\n;\nRun Code Online (Sandbox Code Playgroud)\n结果:
\n 1, 2023-09-10 17:07:10.524389 +00:00\n 2, 2023-09-10 17:07:10.524389 +00:00\n 3, 2023-09-10 18:07:10.524389 +00:00\n 4, 2023-09-10 16:07:10.524389 +00:00\n 5, 2023-09-10 19:07:10.524389 +00:00\n 6, 2023-09-10 15:07:10.524389 +00:00\nRun Code Online (Sandbox Code Playgroud)\n所以,之间的区别
\nSELECT 5, now() AT TIME ZONE \'CEST\'\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 和:
\nSELECT 6, now()::timestamp AT TIME ZONE \'CEST\'\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 是 4 小时。为什么?
\n和...之间的不同
\nSELECT 3, now() AT TIME ZONE \'CET\'\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 和:
\nSELECT 4, now()::timestamp AT TIME ZONE \'CET\'\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 是 2 小时。我也不明白。
\n我的 Postgres-Container 的当前时区是 UTC:
\nSELECT current_setting(\'TIMEZONE\');\n\n> UTC\nRun Code Online (Sandbox Code Playgroud)\n有人可以解释一下吗?
\nUNION这里。由于TIMESTAMP WITH TIME ZONE 和类型的混合,会导致从第二个类型到第一个类型的隐式转换。TIMESTAMP WITHOUT TIME ZONEUNIONTIMESTAMP WITHOUT TIME ZONE而您应该只使用TIMESTAMP WITH TIME ZONE。AT TIME ZONE之间来回翻转。TIMESTAMP WITH TIME ZONETIMESTAMP WITHOUT TIME ZONECET/ CEST) 仅指示夏令时是否有效。这些伪时区可以指多个实时时区中的任何一个。TIMESTAMP WITH TIME ZONE实际上始终采用 UTC 格式的值(偏移量为零)。TIMESTAMP WITHOUT TIME ZONE您使用了错误的数据类型。TIMESTAMP是 per the SQL standard 的缩写TIMESTAMP WITHOUT TIME ZONE。
该类型没有时区或与 UTC 的偏移量的概念。所以它不能代表一个时刻,时间轴上的一个特定点。该类型仅代表一个含糊不清的日期和时间,仅此而已。如果你告诉我“2024 年 1 月 23 日中午”,我怎么知道你指的是东京的中午、图卢兹的中午,还是俄亥俄州托莱多的中午 \xe2\x80\x94\xc2\xa0 三个截然不同的时刻,相隔几个小时。
\n请参阅Postgres 文档。
\nTIMESTAMP WITH TIME ZONE您应该使用另一种类型, TIMESTAMP WITH TIME ZONE. 这种类型确实具有偏移量的概念,始终以 UTC 格式存储提交的值(距 UTC 时间子午线的零时-分-秒偏移量)。
提交值附带的任何偏移量或区域信息都用于调整为 UTC,然后被丢弃。
\n如果您想保留有关该原始区域的信息,则需要将其记录为第二列中的文本。
\n您可以缩写TIMESTAMP WITH TIME ZONE为TIMESTAMPTZ, 作为 Postgres 特定的功能。就我个人而言,我总是用全名拼写这两种类型,以避免误读造成的混乱。
请注意,这种存储在 UTC 中的行为是 Postgres 团队的策略选择。其他一些数据库引擎也以同样的方式工作;其他人则不然。始终研究文档。遗憾的是,SQL 标准缺乏对日期时间处理行为的指定。
\n不幸的是,一些数据库访问工具和中间件可能会选择 \xe2\x9a\xa0\xef\xb8\x8f 将一些默认时区注入到从 Postgres 检索的 UTC 值上。虽然本意是好的,但这种反功能会造成该区域已被存储的错误错觉。pgAdmin就是这样的工具之一。
\n我认为数据访问工具应该始终\xe2\x80\x9c 说出真相\xe2\x80\x9d,而不是篡改检索结果。但那些工具的创造者并没有问我。
\n所以请理解 Postgres 中的:任何和所有TIMESTAMP WITH TIME ZONE值都以零偏移量存储,并以零偏移量检索。您看到的任何其他区域或偏移都被干扰工具覆盖。
CET和CEST不是实时时区。
此类伪区仅用于向用户呈现,而绝不能用于数据存储或数据交换。
\n实时区域的名称格式为Continent/Region. 举几个例子:
Europe/ParisEurope/BerlinEurope/StockholmEurope/Warsaw让我们看看您的示例代码。
\nSELECT 1, now()\nUNION\nSELECT 2, now()::timestamp\nUNION\nSELECT 3, now() AT TIME ZONE \'CET\'\nUNION\nSELECT 4, now()::timestamp AT TIME ZONE \'CET\'\nUNION\nSELECT 5, now() AT TIME ZONE \'CEST\'\nUNION\nSELECT 6, now()::timestamp AT TIME ZONE \'CEST\'\n;\nRun Code Online (Sandbox Code Playgroud)\n根据 Postres 15.4日期时间函数和日期时间类型的文档\xe2\x80\xa6
\n第一个,now()返回一个timestamp with time zone值。我们可以通过将会话设置为 UTC 偏移量 0 来尝试。
set timezone=\'UTC\' ;\nRun Code Online (Sandbox Code Playgroud)\n验证:
\nshow timezone ;\nRun Code Online (Sandbox Code Playgroud)\n调用该now函数。
now \n------------------------------\n 2023-09-11 01:13:03.64721+00\n(1 row)\nRun Code Online (Sandbox Code Playgroud)\n我们看到偏移量为+00,这意味着与 UTC 的偏移量为零时-分-秒。这是格林威治皇家天文台牧羊人门时钟上看到的时间(好吧,在一秒钟左右的时间内,我们不会在这里讨论 GMT 与 UTC,这与实际业务环境无关)。
将我们会话的默认时区设置为柏林时间。
\nset timezone=\'Europe/Berlin\' ;\nRun Code Online (Sandbox Code Playgroud)\n和SELECT now() ;。
2023-09-11 03:19:46.171143+02\nRun Code Online (Sandbox Code Playgroud)\n我们看到现在的时间03比01上面看到的早了两个小时。这符合+02. 这两个结果代表同一时刻,时间轴上的同一点(或者如果我打字速度更快的话就会有)。他们的挂钟时间不同,就像冰岛的某人给柏林的某人打电话一样,两人同时抬头看墙上各自的时钟\xe2\x80\x94,一个人看到凌晨 1 点,另一个人看到凌晨 3 点。
注意:我们现在看到psql具有在生成要在控制台上显示的文本时应用默认时区的反功能。Postgres 中的值TIMESTAMP WITHOUT TIME ZONE始终采用 UTC 格式。因此,我们不能完全信任psql的输出,因为该工具向我们显示的内容并不完全是数据库引擎生成的内容。
相反,如果您在 Java 中通过 JDBC 使用类似 的调用检索该值myResultSet.getObject( \xe2\x80\xa6 , OffsetDateTime.class ),您将始终获得偏移量为零的值。
你的下一行:
\nSELECT 2, now()::timestamp \nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 使用了错误的类型,TIMESTAMP WITHOUT TIME ZONE。您正在丢弃有价值的信息,即与 UTC 的偏移量,而没有获得任何回报。该行应该是:
SELECT 2, now()::TIMESTAMP WITH TIME ZONE \nRun Code Online (Sandbox Code Playgroud)\n但这很愚蠢,因为你TIMESTAMP WITHOUT TIME ZONE手上已经有了一个值。演员阵容毫无意义,没有增加任何价值。
你的第三行:
\nSELECT 3, now() AT TIME ZONE \'CET\'\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 使用伪时区。那应该使用实时时区名称。像这样的东西:
\nSELECT 3, now() AT TIME ZONE \'Europe/Berlin\'\nRun Code Online (Sandbox Code Playgroud)\n但你必须小心操作员AT TIME ZONE。timestamp with time zone该运算符在和类型之间来回翻转输入的类型timestamp without time zone:
timestamp without time zone值进行操作,你就会得到一个timestamp with time zone值。timestamp with time zone会返回 a timestamp without time zone。添加或删除偏移量时,会根据您指定的时区对时间和日期进行调整。
\n所以这一行:
\nSELECT now() AT TIME ZONE \'Europe/Berlin\' ;\nRun Code Online (Sandbox Code Playgroud)\n\xe2\x80\xa6 为您提供柏林地区的日期和时间,而不考虑我们数据库会话的默认时区。
\n因此,在我们的 UTC 凌晨 1 点示例场景中,上面的行将始终返回下面带有凌晨 3 点时间的文本,无论默认时区是 UTC、欧洲/柏林还是亚洲/东京。
\n timezone \n---------------------------\n 2023-09-11 03:39:19.67042\n(1 row)\nRun Code Online (Sandbox Code Playgroud)\n但请注意,在上面的行中,我们丢失了偏移指示。这是因为我们调用AT TIME ZONE对 a 进行操作timestamp with time zone,并返回 a timestamp without time zone,在进行日期时间调整后丢失了偏移量内容。
您的第四行SELECT 4, now()::timestamp AT TIME ZONE \'CET\'重复了前面几行的两个错误:(a)将 a 转换timestamp with time zone为 atimestamp without time zone从而丢失偏移信息,以及(b)使用伪时区。所以我们可以删除这一行,因为它不会加深我们的理解。
您的第五行,SELECT 5, now() AT TIME ZONE \'CEST\',使用不同的伪时区,CEST而不是CET。它们之间的区别在于S指示“夏令时”的方式,即遵守夏令时(DST) 。这是这些伪区域的问题之一:人们经常混淆 DST 和非 DST 对应区域并使用错误的区域。再次强调,我们不应该在编程中使用这些伪区域。所以我们可以放弃你的这一行。
您的第六行SELECT 6, now()::timestamp AT TIME ZONE \'CEST\'也可以忽略。同样,这一行进行了不恰当的转换,并且不恰当地使用了伪区域。
通过使用实时时区名称(例如 )Europe/Berlin,而不是伪时区(例如CET/ CEST),我们让软件确定 DST 在给定时刻是否有效。Postgres 有自己的嵌入的tzdata副本。只要该文件是最新的,那么我们就可以依靠 Postgres 来确定Europe/Berlin.
因此,我们可以将示例代码简化为示例代码中唯一有用/合理的行(第一行和第三行)的修改版本:
\nSET timezone=\'UTC\' \n;\nSELECT 1 AS RowNo , now() \n;\nSELECT 3 as RowNo, now() AT TIME ZONE \'Europe/Berlin\' \n;\nRun Code Online (Sandbox Code Playgroud)\n结果:
\nSET\n rowno | now \n-------+-------------------------------\n 1 | 2023-09-11 02:05:53.188829+00\n(1 row)\n\n rowno | timezone \n-------+----------------------------\n 3 | 2023-09-11 04:05:53.189836\n(1 row)\nRun Code Online (Sandbox Code Playgroud)\nUNION我们要考虑的另一个问题是jjanes 在评论中提出的。使用UNION组合 SELECT 查询会改变结果。
\n请注意,在上面直接显示的结果中,#3 没有偏移指示器。这意味着结果是一个TIMESTAMP WITHOUT TIME ZONE值。但 #1确实有一个偏移指示符,这意味着该项目是一个TIMESTAMP WITH TIME ZONE值。因此,我们有两个不同的单独SELECT语句,其结果具有不同的数据类型。
让我们尝试一下问题中看到的方法UNION。我们将分号替换为UNION.
SET timezone=\'UTC\' \n;\nSELECT 1 AS RowNo , now() \nUNION\nSELECT 3 as RowNo, now() AT TIME ZONE \'Europe/Berlin\' \n;\nRun Code Online (Sandbox Code Playgroud)\n哇!这些结果与上面的结果不匹配。在这里我们看到 #1 和 #3上都有一个偏移指示器。所以现在 #3 是一个TIMESTAMP WITH TIME ZONE值。为什么?行间列的类型必须相同。如何?Postgres 隐式地将TIMESTAMP WITHOUT TIME ZONE值转换为一个 TIMESTAMP WITH TIME ZONE值,在上一个示例中没有应用偏移量。
SET\n rowno | now \n-------+-------------------------------\n 1 | 2023-09-11 02:10:13.934363+00\n 3 | 2023-09-11 04:10:13.934363+00\n(2 rows)\nRun Code Online (Sandbox Code Playgroud)\n这里要理解的关键是日期时间函数返回日期时间值,而不仅仅是字符串。(然后psql应用程序根据这些键入的值生成文本以供显示。)
\n为了解决类型混合问题,Postgres 尝试通过强制转换来提供帮助。因此,最终,UNION此代码的版本会产生与非UNION版本在语义上不同的结果。
这意味着问题的代码示例对于尝试了解 Postgres 的日期时间处理来说更加没有用处。对于我们的研究,我们必须将这些示例语句分开,不带UNION.
顺便说一句,时区与 UTC 偏移量:
\n| 归档时间: |
|
| 查看次数: |
139 次 |
| 最近记录: |