如何在夏时制开始时跳过的一小时内将时间戳存储在 Oracle 中

Posted

技术标签:

【中文标题】如何在夏时制开始时跳过的一小时内将时间戳存储在 Oracle 中【英文标题】:How to store a timestamp in Oracle that occurs within the hour skipped at the start of Daylight Savings 【发布时间】:2014-09-04 03:44:34 【问题描述】:

我们的 Oracle 服务器在澳大利亚/悉尼时间运行。

我们计划在 UTC 中存储一些新日期。

例如,我们可能存储的日期之一是 2014 年 10 月 5 日凌晨 2:00

但是,在悉尼,我们同时开始夏令时,这意味着从 2:00AM2:59AM 的时间在当天不存在。

例如,2014 年 10 月 5 日发生的时间是:

01:58 01:59 03:00 03:01

麻烦的是,如果我尝试将时间2014-10-05 02:00 存储在数据库中,它会默默地转换为2014-10-05 03:00

我们没有在服务器上更改时区的选项,那么有什么方法可以将2014-10-05 02:00 存储在我们的数据库中吗?

编辑@mrjoltcola 的评论

我们的服务器正在运行时区设置(GMT +10) 堪培拉、墨尔本、悉尼

如果我运行命令select DBTIMEZONE from dual;,输出是单个值+00:00(这是意料之外的)。

我们的原始列是仅 TIMESTAMP 列,我没有在插入时提供任何时区详细信息。

为了进一步探索,我创建了一个测试表,如下所示:

create table TEST
(
  ID number,
  TS timestamp,
  TS_TZ timestamp with time zone
)

然后我运行以下插入语句(带和不带时区):

insert into TEST
VALUES
(
  1,
  TIMESTAMP '2014-10-05 02:00:00 UTC',
  TIMESTAMP '2014-10-05 02:00:00 UTC'
);

insert into TEST
VALUES
(
  2,
  TIMESTAMP '2014-10-05 02:00:00',
  TIMESTAMP '2014-10-05 02:00:00'
);

产生结果:

+---+---------------------------------+--------------------------------------------------+
| 1 | 05/OCT/14 03:00:00.000000000 AM | 05/OCT/14 02:00:00.000000000 AM UTC              |
+---+---------------------------------+--------------------------------------------------+
| 2 | 05/OCT/14 03:00:00.000000000 AM | 05/OCT/14 03:00:00.000000000 AM AUSTRALIA/SYDNEY |
+---+---------------------------------+--------------------------------------------------+

【问题讨论】:

什么是列类型?它是普通的 TIMESTAMP 还是带有 TIMEZONE 的 TIMESTAMP 或带有本地时区的 TIMESTAMP? @mrjoltcola 该列是一个普通的 TIMESTAMP。这是我们要添加的一个新字段,所以我刚刚将其更改为 TIMESTAMP WITH TIMEZONE,它似乎已经解决了这个问题。您想提供此评论作为答案,以便我可以将其标记为已接受的答案吗?非常感谢。 一旦我弄清楚到底发生了什么,我会很高兴的。如果您将原始时间戳存储在 UTC 中,我仍然对它们的调整方式感到困惑。如果您能告诉我您的 Oracle 数据库的时区设置是什么,以及您用于插入的 SQL 之前和之后,那将会有所帮助。您是否在插入中指定时区? 我已经编辑了原始问题以提供更多详细信息。最初,我只有一个 TIMESTAMP 字段,其中插入了一个不提供时区的内容。现在我使用 TIMESTAMP WITH TIME ZONE 字段并提供时区。 【参考方案1】:

问题是,如果我尝试将时间 2014-10-05 02:00 存储在数据库中,它会默默地转换为 2014-10-05 03:00

在我的数据库中,我可以将会话时区设置为“澳大利亚/悉尼”,并且仍然将 2014-10-05 02:00:00 存储在一个普通的 TIMESTAMP 列中,而无需对其进行转换。但是,如果我将其更改为 TIMESTAMP WITH TIMEZONE 并尝试存储相同的值,我会得到 ORA-01878

所以我不认为你的时间被转换为 03:00:00 基于 Oracle 试图调整夏令时,我认为你应该收到一个 ORA-01878 当您指定不正确的时间戳时。相反,我认为您的客户端/会话时区与您的数据库/服务器时区不匹配一个小时,而您看到的是正常的 Oracle 调整。

当客户端/会话时区与服务器/数据库时区不匹配时,Oracle 将转换时间戳。由于常规 TIMESTAMP 字段中没有时区信息,Oracle 不介意存储它。

所以您的“静默转换为 2014-10-05 03:00”应该已经发生在其他时间值(您是否尝试插入 01:00 并查看它是否导致调整和/或 ORA-01878 错误?)。

当我在这里尝试将会话时区设置为澳大利亚/悉尼并插入该时间时,我得到:

SQL> alter session set time_zone = 'Australia/Sydney';

Session altered.

SQL> insert into test(ts) values(1, timestamp '2014-10-05 02:00:00');
insert into test values(1, timestamp '2014-10-05 02:00:00')

1 row created.

SQL> insert into test(ts_tz) values(2, timestamp '2014-10-05 02:00:00');
insert into test(ts_tz) values(2, timestamp '2014-10-05 02:00:00')
            *
ERROR at line 1:
ORA-01878: specified field not found in datetime or interval

现在,关于您的问题,如果您想存储 UTC 时间,您应该明确指定带有 UTC 的时间戳,或者将您的数据库或会话时区设置为 UTC。您不必拥有 DBA 权限即可设置会话 time_zone,它可以与 db time_zone 不同。

SQL> alter session set time_zone = '+00:00';

Session altered.

SQL> select dbtimezone, sessiontimezone from dual;

DBTIME SESSIONTIMEZONE
------ ---------------------------------------------------------------------------
+00:00 +00:00
SQL> insert into test values(1, timestamp '2014-10-05 02:00:00');

1 row created.

SQL> select * from test;

        ID TS
---------- ---------------------------------------------------------------------------
         1 05-OCT-14 02.00.00.000000 AM

但是,上述 TIMESTAMP 字段不一定是 UTC。如果数据库服务器时区更改,它可能会更改。对于 Oracle 来说,一个普通的 TIMESTAMP 只是一个没有时区信息的时间戳。如果您想要 UTC 时间,但可能与不同时区的客户打交道,您可以使用 TIMESTAMP WITH TIMEZONE 或 TIMESTAMP WITH LOCAL TIMEZONE。

【讨论】:

感谢您的帮助和详细解答。根据你的笔记,我已经取得了一些进展。我正在使用 Oracle SQL Developer。当数据库中的值为“05/OCT/2014 02:00”时,它在 SQL Dev 中显示为“05/OCT/2014 03:00”,但是使用 TO_CHAR(ts,'HH24') 始终显示 02 小时.我认为这是一个 Java 问题 - 在 SQL Dev 或 JDBC 驱动程序中。我的计算机的时区是澳大利亚/悉尼时区,我相信这会导致 Java 在 SQL Developer 中执行这些静默日期转换。当我将计算机的时区设置为其他时区时,时间会正确显示。 我正在考虑我们只是使用 TIMESTAMP 字段而不是 TIMESTAMP WITH TIME ZONE,原因如下:(1)我们只会存储 UTC,因此时区实际上是多余的,(2 ) 我的印象是 TIMEZONE 字段不受会话时区的影响,因此我们不需要对此进行任何特殊处理,并且 (3) 我怀疑这也意味着 ORA-01878 永远不会发生。希望您有任何想法? 我明天将使用 SQL Developer 进行更多研究。我认为服务器和客户端之间肯定会发生转换。要真正弄清楚这一点,我可能还需要将服务器设置为澳大利亚/悉尼。如果我明天有时间,我会设置我的一个。您是在另一台 PC 上还是直接在服务器上运行 SQL Developer? 是的,SQL Developer 位于与服务器不同的计算机上。非常感谢。 实际上,一个快速的测试就是将日期 2014-10-05 02:00:00 存储在 TIMESTAMP 列中,将您的 PC 更改为悉尼时区,启动 SQL Developer 并查看日期。我猜你会看到同样的行为。

以上是关于如何在夏时制开始时跳过的一小时内将时间戳存储在 Oracle 中的主要内容,如果未能解决你的问题,请参考以下文章

Matplotlib:如何在使用日期时间轴绘图时跳过一系列小时?

WiX 安装程序:修改安装以包括在初始安装时跳过的包

使用 WIA 2.0 和 C# 扫描多页时跳过的页面

ProGuard 优化通过了在 Android Studio 3.4.1 中突然生成构建时跳过的 5 条规则

在 Chrome 开发者工具中调试时跳过线

java hibernate在插入时跳过插入'id'