我们正在开发一个移动应用程序,用于检索和显示旧系统中的数据。在旧系统中,日期时间列存储在日期数据类型中,而不是TIMESTAMP WITH TIME ZONE。现在我们需要在移动应用程序中处理时区问题。
我们希望数据的时间反映输入它的用户的时区(偏移到GMT)(或至少存储在GMT中),以便我们可以代表查看它的用户正确解释它。
我们的“极端情况”情况是时区A中的遗留系统用户输入时区A的订单,时区B中的数据库服务器以及时区C中的移动应用用户。
数据似乎没有偏移信息且不在GMT中,因此每个步骤都会“推断”时区:
时区A中的用户看到自输入以来的“正确”时间 因为该时区正确解释。
移动应用数据服务(持久化/序列化数据集) 从时区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;
谢谢,
肖恩
答案 0 :(得分: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;