2013-09-05 45 views
2

我們正在開發移動應用程序,用於從舊系統中檢索和顯示數據。在舊系統中,日期時間列存儲在日期數據類型中,不是TIMESTAMP WITH TIME ZONE。現在我們需要處理移動應用中的時區問題。如何在不將日期時間存儲爲TIMESTAMP WITH TIME ZONE的情況下處理時區問題

我們希望數據能夠反映輸入用戶的時區(至GMT的時區)(或至少以GMT格式存儲),以便我們可以代表查看它的用戶正確解釋它。

我們「極端的情況下」的情況是一個傳統系統用戶在時間段A輸入訂單時區A,在時間區域B的數據庫服務器,並在時區中的移動應用用戶C.

數據似乎沒有偏移信息,而不是在GMT,這樣的時區是「推斷」在每一個步驟:

  • 在時間段A的用戶將看到,因爲它是進入 爲「正確」的時間時區,因此被正確解釋。

  • 該移動應用的DataService(其持續/串行化數據集) 在時間區域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; 

感謝,

肖恩

+0

什麼是'LOC_CODE'?它是一個時區名稱(縮寫)嗎?如何確定每個訂單是否由客戶輸入? – krokodilko

+0

loc_code通常是城市名稱。如Youngstown,OH。它涉及UTC偏移-5:00。當用戶創建訂單時,他們會從下拉列表中選擇一個位置來選擇他們所在的位置。 – Shawn

回答

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 bas編輯區域設置。

如果你使用日期,而不是戳,你也可以做到以下幾點:

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; 
相關問題