3

レガシー システムからデータを取得して表示するモバイル アプリを開発しています。レガシー システムでは、日時列は TIMESTAMP WITH TIME ZONE ではなく Date データ型に格納されます。次に、モバイル アプリでタイム ゾーンの問題を処理する必要があります。

データを表示するユーザーに代わってデータを適切に解釈できるように、データを入力したユーザーのタイム ゾーン (GMT からのオフセット) をデータの時刻に反映する (または少なくとも GMT で保存する) 必要があります。

私たちの「極端なケース」の状況は、タイム ゾーン A のレガシー システム ユーザーがタイム ゾーン A の注文を入力し、データベース サーバーがタイム ゾーン B に、モバイル アプリ ユーザーがタイム ゾーン C にいる場合です。

データにはオフセット情報がないように見え、GMT ではないため、各ステップでタイム ゾーンが「推測」されます。

  • タイム ゾーン A のユーザーには、そのタイム ゾーンで入力された時刻から「正しい」時刻が表示されるため、正しく解釈されます。

  • モバイル アプリのデータ サービス (データセットを永続化/シリアル化する) は、タイム ゾーン B でデータベース サーバーからデータを読み取り、時間をタイム ゾーン B にあると解釈し、時間は数時間ずれています。

  • タイム ゾーン C のモバイル アプリ ユーザーは、時刻がタイム ゾーン C にあると解釈し、時刻が別の時間数ずれています。

入力された注文のタイム ゾーン情報があれば、それを表示する方法を決定できます。

  • 「発信者」のタイムゾーンに関して

  • 「視聴者」のタイムゾーンに関して

GMT で絶対時間があれば、「視聴者」のタイム ゾーンで表示できます (「発信者」のタイム ゾーンはわかりません)。

従来の DATE データ型を TIMESTAMP WITH TIME ZONE に変更することは選択肢ではありません。これを行うには多大な労力がかかり、他の問題が発生する可能性があるためです。

これが私のバンドエイドソリューションです

  1. UTC 情報を含むロケーション テーブルを作成します。

  2. loc_code 列を FK として orders テーブルに追加します。

  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 を自動的に考慮します。

TIMESTAMPS ではなく DATES を使用している場合は、次のこともできます。

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 に答える