在 oracle 中使用 to_char 将时间戳转换为 tz 时的奇怪行为
Strange behaviour when converting timestamp with tz using to_char in oracle
我要求 Oracle 告诉我当地时间下周一 12:00 中午圣地亚哥的日期和时间。
以下SQL:
SELECT to_timestamp(
to_char(trunc(next_day(sysdate, 1)) + 0.5,
'DD-MM-YY HH24:MI'))
AT TIME ZOME 'America/Santiago'
FROM dual;
Returns: 16-08-21 06:00:00,000000000 AMERICA/SANTIAGO
我想提取日期、小时和分钟,所以我正在做:
SELECT to_char(
to_timestamp(
to_char(trunc(next_day(sysdate,1)) + 0.5,
'DD-MM-YY HH24:MI'))
AT TIME ZONE 'America/Santiago',
'DD-MM-YY HH24:MI')
FROM dual;
Returns: 16-08-21 07:00
谁能解释为什么 06:00 在转换 to_char 时变成 07:00?!
提前致谢
这不是一个完整的答案;使用“答案”格式,以便我可以 post 格式化代码。如果我能找到更多,我可能会编辑这个答案。
首先,为了隔离问题,最好编写最简单的示例来重现错误。这与 next_day
函数或初始转换无关 - 它与最后一次转换有关,从带时区的时间戳到字符串。
在下面的查询中,我 select 一个带有时区文字的硬编码时间戳(使用我会话的 nls_timestamp_tz_format
设置显示在我的屏幕上),并且同样的时间戳隐式转换为字符串(也使用相同的 nls_timestamp_tz_format
模型)。如您所见,错误重现 - 我们获得了更多信息:当 Oracle 将时间戳转换为字符串时,它更改了时区 DST 标志(它显示 CLST 而不是 CLT;CLST 是智利 Summer 时间,这不应该适用于八月 - 那是南半球的冬天)。
为什么会发生这种情况是一个很好的问题。首先要看的地方是时区文件——那里很可能有错误,而且可能有补丁。 (无论这个问题如何,都应定期维护时区文件的补丁和更新等。)我能够在我的系统上复制它,因为我是一个业余爱好者,使用免费版本的 Oracle,它不附带访问补丁和更新等;但这是我首先要看的地方。
如果这没有帮助,也许是时候询问 Oracle 本身了(提出服务请求,或者可能报告为错误)。
所以 - 这是我的 nls_timezone_tz_format
首先,以确保我们知道发生了什么:
select value
from v$nls_parameters
where parameter = 'NLS_TIMESTAMP_TZ_FORMAT'
;
VALUE
----------------------------------------------------------------
yyyy-mm-dd hh24:mi:ss.ff3 tzr tzd
下面是重现问题的简单查询。请注意,“时间戳....”是一个硬编码常量(文字),直到并包括时区规范。
select timestamp '2021-08-16 12:00:00 America/Santiago' as tz
, to_char(timestamp '2021-08-16 12:00:00 America/Santiago') as str
from dual
;
TZ STR
-------------------------------------------- ---------------------------------------------
2021-08-16 11:00:00.000 America/Santiago CLT 2021-08-16 12:00:00.000 America/Santiago CLST
明显的问题是 将 TIMESTAMP with TIMEZONE
转换为 到 DATE
。
无论如何,如果您使用显式 时区 转换为 DATE
,一切正常 - 请参阅 dt2
结果下方的查询。
with ts as (
select
to_timestamp(TO_DATE ('2021-08-16 12:00','yyyy-mm-dd hh24:mi')) AT TIME ZONE 'America/Santiago' ts
from dual)
select
ts,
CAST(ts as TIMESTAMP) ts1,
CAST(ts as DATE) dt1,
CAST(ts AT TIME ZONE 'America/Santiago' AS DATE) dt2
from ts;
TS TS1 DT1 DT2
---------------------------------------------- ----------------------------- ------------------- -------------------
16.08.2021 06:00:00,000000000 AMERICA/SANTIAGO 16.08.2021 07:00:00,000000000 16.08.2021 07:00:00 16.08.2021 07:00:00
我要求 Oracle 告诉我当地时间下周一 12:00 中午圣地亚哥的日期和时间。
以下SQL:
SELECT to_timestamp(
to_char(trunc(next_day(sysdate, 1)) + 0.5,
'DD-MM-YY HH24:MI'))
AT TIME ZOME 'America/Santiago'
FROM dual;
Returns: 16-08-21 06:00:00,000000000 AMERICA/SANTIAGO
我想提取日期、小时和分钟,所以我正在做:
SELECT to_char(
to_timestamp(
to_char(trunc(next_day(sysdate,1)) + 0.5,
'DD-MM-YY HH24:MI'))
AT TIME ZONE 'America/Santiago',
'DD-MM-YY HH24:MI')
FROM dual;
Returns: 16-08-21 07:00
谁能解释为什么 06:00 在转换 to_char 时变成 07:00?!
提前致谢
这不是一个完整的答案;使用“答案”格式,以便我可以 post 格式化代码。如果我能找到更多,我可能会编辑这个答案。
首先,为了隔离问题,最好编写最简单的示例来重现错误。这与 next_day
函数或初始转换无关 - 它与最后一次转换有关,从带时区的时间戳到字符串。
在下面的查询中,我 select 一个带有时区文字的硬编码时间戳(使用我会话的 nls_timestamp_tz_format
设置显示在我的屏幕上),并且同样的时间戳隐式转换为字符串(也使用相同的 nls_timestamp_tz_format
模型)。如您所见,错误重现 - 我们获得了更多信息:当 Oracle 将时间戳转换为字符串时,它更改了时区 DST 标志(它显示 CLST 而不是 CLT;CLST 是智利 Summer 时间,这不应该适用于八月 - 那是南半球的冬天)。
为什么会发生这种情况是一个很好的问题。首先要看的地方是时区文件——那里很可能有错误,而且可能有补丁。 (无论这个问题如何,都应定期维护时区文件的补丁和更新等。)我能够在我的系统上复制它,因为我是一个业余爱好者,使用免费版本的 Oracle,它不附带访问补丁和更新等;但这是我首先要看的地方。
如果这没有帮助,也许是时候询问 Oracle 本身了(提出服务请求,或者可能报告为错误)。
所以 - 这是我的 nls_timezone_tz_format
首先,以确保我们知道发生了什么:
select value
from v$nls_parameters
where parameter = 'NLS_TIMESTAMP_TZ_FORMAT'
;
VALUE
----------------------------------------------------------------
yyyy-mm-dd hh24:mi:ss.ff3 tzr tzd
下面是重现问题的简单查询。请注意,“时间戳....”是一个硬编码常量(文字),直到并包括时区规范。
select timestamp '2021-08-16 12:00:00 America/Santiago' as tz
, to_char(timestamp '2021-08-16 12:00:00 America/Santiago') as str
from dual
;
TZ STR
-------------------------------------------- ---------------------------------------------
2021-08-16 11:00:00.000 America/Santiago CLT 2021-08-16 12:00:00.000 America/Santiago CLST
明显的问题是 将 TIMESTAMP with TIMEZONE
转换为 到 DATE
。
无论如何,如果您使用显式 时区 转换为 DATE
,一切正常 - 请参阅 dt2
结果下方的查询。
with ts as (
select
to_timestamp(TO_DATE ('2021-08-16 12:00','yyyy-mm-dd hh24:mi')) AT TIME ZONE 'America/Santiago' ts
from dual)
select
ts,
CAST(ts as TIMESTAMP) ts1,
CAST(ts as DATE) dt1,
CAST(ts AT TIME ZONE 'America/Santiago' AS DATE) dt2
from ts;
TS TS1 DT1 DT2
---------------------------------------------- ----------------------------- ------------------- -------------------
16.08.2021 06:00:00,000000000 AMERICA/SANTIAGO 16.08.2021 07:00:00,000000000 16.08.2021 07:00:00 16.08.2021 07:00:00