Oracle sql 时区问题

Oracle sql timezone issue

我的要求是从日期类型列中获取 GMT/UTC 中的时间。但是当我使用 cast 将日期转换为时间戳时,它使用 US/Pacific 时区作为参考,尽管会话时区设置为 GMT。因此,除非我使用 from_tz,否则我看不到预期的结果。我需要修改 oracle sql 中的任何其他时区设置以始终将 GMT 作为参考吗?

alter session set time_zone='+00:00';
select sessiontimezone from dual;
select current_timestamp from dual;
select sys_extract_utc(cast (sysdate as timestamp)) from dual;
select sys_extract_utc(from_tz(cast (sysdate as timestamp), '-07:00')) from dual;
select sys_extract_utc(current_timestamp) from dual;


Session altered.


SESSIONTIMEZONE
---------------------------------------------------------------------------
+00:00


CURRENT_TIMESTAMP
---------------------------------------------------------------------------
11-APR-16 08.46.42.292173 AM +00:00


SYS_EXTRACT_UTC(CAST(SYSDATEASTIMESTAMP))
---------------------------------------------------------------------------
11-APR-16 01.46.42.000000 AM

SYS_EXTRACT_UTC(FROM_TZ(CAST(SYSDATEASTIMESTAMP),'-07:00'))
---------------------------------------------------------------------------
11-APR-16 08.46.42.000000 AM


SYS_EXTRACT_UTC(CURRENT_TIMESTAMP)
---------------------------------------------------------------------------
11-APR-16 08.46.42.295310 AM

Tasks table 有一个名为 task_started 的日期类型列。我希望从此日期字段中获取 UTC 时间。作为其中的一部分,我试图在插入数据时将会话时区更改为 GMT,以便我可以简单地将其转换回不起作用的时间戳。

select task_started from tasks where rownum <2;

TASK_STAR
---------
10-APR-16

desc tasks;
 Name                                      Null?    Type
 ----------------------------------------- -------- ----------------------------
...
 TASK_STARTED                                       DATE
...

这是一个使用在伦敦时间系统上使用 sysdate 插入的数据的演示,因此当前在 BST (+01:00)。差异比您在西海岸看到的要小,但同样适用。

模仿您的 table,您可以看到会话时区对插入的值没有影响,因为 sysdate 使用数据库服务器时间,而不是会话(客户端)时间(as explained here):

alter session set nls_date_format = 'YYYY-MM-DD HH24:MI:SS';
alter session set nls_timestamp_format = 'YYYY-MM-DD HH24:MI:SS.FF3';
alter session set nls_timestamp_tz_format = 'YYYY-MM-DD HH24:MI:SS.FF3 TZH:TZM';

create table tasks (id number, task_started date);

alter session set time_zone='America/Los_Angeles';

insert into tasks (id, task_started) values (1, sysdate);

select task_started, cast(task_started as timestamp) as ts
from tasks where rownum < 2;

TASK_STARTED        TS                    
------------------- -----------------------
2016-04-11 11:55:59 2016-04-11 11:55:59.000

alter session set time_zone = 'UTC';

select task_started, cast(task_started as timestamp) as ts
from tasks where rownum < 2;

TASK_STARTED        TS                    
------------------- -----------------------
2016-04-11 11:55:59 2016-04-11 11:55:59.000

所以这两种情况都是 BST 时间。没有任何会话或数据库设置会自动向您显示转换为特定时区的日期(或时间戳,没有时区)。除非您告诉数据库,否则数据库不知道存储的 date/time 代表什么。如果您使用 sysdatecurrent_date 或日期文字,它不知道或区分大小写;在将数据插入 table 时,它根本没有时区信息。

要获得 UTC 等效值,您需要使用 from_tz 来声明存储值代表特定时区;然后你可以使用 at time zone (保留时区信息)或 sys_extract_utc (不保留)来转换它;并可选择返回日期:

select from_tz(cast(task_started as timestamp), 'Europe/London') as db_tstz,
  from_tz(cast(task_started as timestamp), 'Europe/London') at time zone 'UTC' as utc_tstz,
  sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as utc_ts,
  cast(sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as date) as utc_date
from tasks where rownum < 2;

DB_TSTZ                        UTC_TSTZ                       UTC_TS                  UTC_DATE          
------------------------------ ------------------------------ ----------------------- -------------------
2016-04-11 11:55:59.000 +01:00 2016-04-11 10:55:59.000 +00:00 2016-04-11 10:55:59.000 2016-04-11 10:55:59

我使用了时区区域名称,所以它可以帮我处理夏令时;您可以使用 dbtimezone 但那将始终使用 -08:00,正如您在问题中所展示的,您现在需要使用 -07:00。并且您可以使用 sessiontimezone 但是您必须记住正确设置它。显然,在您的情况下,您会使用您所在的地区,例如America/Los_Angeles,而不是 Europe/London.

然后您可以通过比较时间戳或更简单地通过比较日期来获得时间间隔:

select sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London'))
    - timestamp '1970-01-01 00:00:00' as diff_interval,
  cast(sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as date)
    - date '1970-01-01' as diff_days,
  86400 *
    (cast(sys_extract_utc(from_tz(cast(task_started as timestamp), 'Europe/London')) as date)
      - date '1970-01-01') as epoch
from tasks where rownum < 2;

DIFF_INTERVAL    DIFF_DAYS       EPOCH
---------------- --------- -----------
16902 10:55:59.0  16902.46  1460372159

如果您将 1460372159 放入在线转换器(有很多),它将显示 UTC 时间。