3

我们正在开发一个从遗留系统检索和显示数据的移动应用程序。在遗留系统中,日期时间列存储在 Date 数据类型中,而不是 TIMESTAMP WITH TIME ZONE。现在我们需要在移动应用程序中处理时区问题。

我们希望数据的时间能够反映输入它的用户(或至少存储在 GMT 中)的时区(偏移到 GMT),以便我们可以代表查看它的用户正确解释它。

我们的“极端情况”情况是 A 时区的旧系统用户输入时区 A 的订单,时区 B 的数据库服务器和时区 C 的移动应用程序用户。

数据似乎没有偏移信息并且不在 GMT 中,因此在每一步都“推断”了时区:

  • 时区 A 中的用户看到“正确”时间,因为它是为该时区输入的,因此被正确解释。

  • 移动应用程序数据服务(持久化/序列化数据集)从时区 B 中的数据库服务器读取数据并将时间解释为时区 B 中,并且时间相差几个小时。

  • 时区 C 中的移动应用程序用户将时间解释为时区 C,并且时间偏离了其他一些小时数。

如果我们有输入订单的时区信息,我们将能够决定如何显示它:

  • 就“始发地”时区而言

  • 就“观众”时区而言

如果我们在 GMT 中有一个绝对时间,我们将能够根据“查看者”时区(不知道“发起者”时区)来显示它。

将旧的 DATE 数据类型更改为 TIMESTAMP WITH TIME ZONE 不是一种选择,因为这对我们来说工作量太大,并且可能会带来其他并发症。

这是我的创可贴解决方案

  1. 创建一个包含 UTC 信息的位置表。

  2. 将 loc_code 列作为 FK 添加到订单表。

  3. 加入订单和位置表并将 order_date 转换为 TIMESTAMP WITH TIME ZONE。

我知道这个解决方案存在漏洞,我正在寻找更好的方法来实现我们的目标。任何投入将不胜感激。

下面是代码,

DROP TABLE location;
CREATE TABLE location
(
  loc_code                    VARCHAR2(6)  NOT NULL,
  descr                       VARCHAR2(40) NOT NULL,
  utc_time_zone_offset        VARCHAR2(6)  NULL,
  daylight_savings_start_date DATE         NULL,
  daylight_savings_end_date   DATE         NULL,
  CONSTRAINT locationp1 PRIMARY KEY (loc_code)
);

INSERT INTO location VALUES ('-5','EST','-05.00',To_Date('2013-03-10 2:00:00','yyyy-mm-dd hh24:mi:ss'),To_Date('2013-11-03 2:00:00','yyyy-mm-dd hh24:mi:ss'));
INSERT INTO location VALUES ('-6','CST','-06.00',To_Date('2013-03-10 2:00:00','yyyy-mm-dd hh24:mi:ss'),To_Date('2013-11-03 2:00:00','yyyy-mm-dd hh24:mi:ss'));
INSERT INTO location VALUES ('-7','MST','-07.00',To_Date('2013-03-10 2:00:00','yyyy-mm-dd hh24:mi:ss'),To_Date('2013-11-03 2:00:00','yyyy-mm-dd hh24:mi:ss'));
INSERT INTO location VALUES ('-8','PST','-08.00',To_Date('2013-03-10 2:00:00','yyyy-mm-dd hh24:mi:ss'),To_Date('2013-11-03 2:00:00','yyyy-mm-dd hh24:mi:ss'));
INSERT INTO location VALUES ('-9','ALST','-09.00',To_Date('2013-03-10 2:00:00','yyyy-mm-dd hh24:mi:ss'),To_Date('2013-11-03 2:00:00','yyyy-mm-dd hh24:mi:ss'));
INSERT INTO location VALUES ('-10','HST','-10.00',To_Date('2013-03-10 2:00:00','yyyy-mm-dd hh24:mi:ss'),To_Date('2013-11-03 2:00:00','yyyy-mm-dd hh24:mi:ss'));
COMMIT;

DROP TABLE orders
CREATE TABLE orders (order_id VARCHAR2(10),loc_code VARCHAR2(6), order_date DATE);

SELECT order_id,order_date,
       cs_timezone.to_UCT(loc_code,order_date),
       cs_timezone.to_viewer_time(loc_code,order_date)  
  FROM orders 

CREATE OR REPLACE PACKAGE cs_timezone AUTHID CURRENT_USER AS
-- 1. Only SYSDATE is affected by SYSTIMESTAMP which the timestamp on the server machine itself.
--    If we do not use SYSDATE, we do not have to worry about SYSTIMESTAMP.
-- 2. When insert a DATE value Oracle only stores the date time value without any knowledge of time zone.
--    When we convert to local time we only care about time zone offset for the user who save the data.

  FUNCTION to_viewer_time (p_loc_code IN VARCHAR2, p_date IN DATE) RETURN DATE;
  FUNCTION to_UCT (p_loc_code IN VARCHAR2, p_date IN DATE) RETURN TIMESTAMP WITH TIME ZONE;
END;
/

CREATE OR REPLACE PACKAGE BODY cs_timezone
AS
FUNCTION to_viewer_time (p_loc_code IN VARCHAR2, p_date IN DATE) RETURN DATE
IS
  v_offset_hrs INT;  v_utc_time_zone_offset VARCHAR2(10); v_daylight_savings_start_date DATE; v_daylight_savings_end_date DATE;
BEGIN
  SELECT   utc_time_zone_offset,   daylight_savings_start_date,   daylight_savings_end_date
    INTO v_utc_time_zone_offset, v_daylight_savings_start_date, v_daylight_savings_end_date
    FROM location
   WHERE loc_code = p_loc_code;
  IF InStr(v_utc_time_zone_offset,'+') > 0 THEN
    IF To_Date(To_Char(p_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') BETWEEN To_Date(To_Char(v_daylight_savings_start_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') AND To_Date(To_Char(v_daylight_savings_end_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') THEN
       v_utc_time_zone_offset := SubStr(v_utc_time_zone_offset,1,1)||To_Char(To_Number(v_utc_time_zone_offset)+1);
    END IF;
    v_offset_hrs :=  extract(TIMEZONE_HOUR FROM current_timestamp) - To_Number(v_utc_time_zone_offset);
  ELSIF InStr(v_utc_time_zone_offset,'-') > 0 THEN
    IF  To_Date(To_Char(p_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') BETWEEN To_Date(To_Char(v_daylight_savings_start_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') AND To_Date(To_Char(v_daylight_savings_end_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') THEN
       v_utc_time_zone_offset := To_Char(To_Number(v_utc_time_zone_offset)+1);
    END IF;
    v_offset_hrs := To_Number(v_utc_time_zone_offset) - extract(TIMEZONE_HOUR FROM current_timestamp);
  ELSE
    Raise_Application_Error(-20001,'Offset format missing - or +');
  END IF;
  Dbms_Output.put_line(v_offset_hrs); 
  RETURN p_date+(v_offset_hrs/24);
EXCEPTION WHEN OTHERS THEN RETURN 'ERROR: '||SQLERRM;
END to_viewer_time;

FUNCTION to_UCT (p_loc_code IN VARCHAR2, p_date IN DATE) RETURN TIMESTAMP WITH TIME ZONE
IS
  v_utc_time_zone_offset VARCHAR2(10); v_daylight_savings_start_date DATE; v_daylight_savings_end_date DATE;
BEGIN
  SELECT   utc_time_zone_offset,   daylight_savings_start_date,   daylight_savings_end_date
    INTO v_utc_time_zone_offset, v_daylight_savings_start_date, v_daylight_savings_end_date
    FROM location
   WHERE loc_code = p_loc_code;
  IF InStr(v_utc_time_zone_offset,'+') > 0 THEN
    IF To_Date(To_Char(p_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') BETWEEN To_Date(To_Char(v_daylight_savings_start_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') AND To_Date(To_Char(v_daylight_savings_end_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') THEN
      v_utc_time_zone_offset := SubStr(v_utc_time_zone_offset,1,1)||To_Char(To_Number(v_utc_time_zone_offset)+1);
      IF InStr(v_utc_time_zone_offset,'-') = 0 OR InStr(v_utc_time_zone_offset,'+') = 0 THEN
        v_utc_time_zone_offset := '+'||v_utc_time_zone_offset;
      END IF;
      IF InStr(v_utc_time_zone_offset,'.00') = 0 THEN
        v_utc_time_zone_offset := v_utc_time_zone_offset||':00';
      ELSE
        v_utc_time_zone_offset := REPLACE(v_utc_time_zone_offset,'.',':');
      END IF;
    ELSE
      v_utc_time_zone_offset := REPLACE(v_utc_time_zone_offset,'.',':');
    END IF;
  ELSIF InStr(v_utc_time_zone_offset,'-') > 0 THEN
    IF To_Date(To_Char(p_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') BETWEEN To_Date(To_Char(v_daylight_savings_start_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') AND To_Date(To_Char(v_daylight_savings_end_date,'mm-dd hh24:mi:ss'),'mm-dd hh24:mi:ss') THEN
      v_utc_time_zone_offset := To_Char(To_Number(v_utc_time_zone_offset)+1);
      IF InStr(v_utc_time_zone_offset,'-') = 0 OR InStr(v_utc_time_zone_offset,'+') = 0 THEN
        v_utc_time_zone_offset := '+'||v_utc_time_zone_offset;
      END IF;
      IF InStr(v_utc_time_zone_offset,'.00') = 0 THEN
        v_utc_time_zone_offset := v_utc_time_zone_offset||':00';
      ELSE
        v_utc_time_zone_offset := REPLACE(v_utc_time_zone_offset,'.',':');
      END IF;
    ELSE
      v_utc_time_zone_offset := REPLACE(v_utc_time_zone_offset,'.',':');
    END IF;
  ELSE
    Raise_Application_Error(-20001,'Offset format missing - or +');
  END IF;
  Dbms_Output.put_line(''); 
  RETURN TO_TIMESTAMP_TZ(To_Char(p_date,'YYYY-MM-DD HH24:MI:SS')||' '||v_utc_time_zone_offset, 'YYYY-MM-DD HH24:MI:SS TZH:TZM');
EXCEPTION WHEN OTHERS THEN RETURN 'ERROR: '||SQLERRM;
END to_UCT;

END cs_timezone;

谢谢,

肖恩

4

1 回答 1

0

如果您有时区的名称,则可以使用类似于以下的查询来调整更改时间戳:

SELECT
  e.last_updated_date AS cst_last_updated_date,
  (FROM_TZ(e.last_updated_date, 'US/Central') AT TIME ZONE 'US/Eastern') AS est_last_updated_date
FROM events_tbl e;

以我的经验,这将根据语言环境自动考虑 DST。

如果您使用的是 DATES 而不是 TIMESTAMPS,您还可以执行以下操作:

select 
  CAST (e.last_updated_date AS TIMESTAMP) AS cst_last_updated_date,
  (FROM_TZ(CAST (e.last_updated_date AS TIMESTAMP), 'US/Central') AT TIME ZONE 'US/Eastern') AS est_last_updated_date
from events_tbl e;
于 2013-10-03T21:37:52.553 回答