我們正在開發移動應用程序,用於從舊系統中檢索和顯示數據。在舊系統中,日期時間列存儲在日期數據類型中,不是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不是一種選擇,因爲它對我們來說太大,而且可能有其他複雜性。
這裏是我的創可貼解決方案
創建包含UTC信息位置表。
添加一個loc_code列作爲FK到訂單表。
加入訂單和位置表並將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;
感謝,
肖恩
什麼是'LOC_CODE'?它是一個時區名稱(縮寫)嗎?如何確定每個訂單是否由客戶輸入? – krokodilko
loc_code通常是城市名稱。如Youngstown,OH。它涉及UTC偏移-5:00。當用戶創建訂單時,他們會從下拉列表中選擇一個位置來選擇他們所在的位置。 – Shawn