Глобализация и локализация программ PL/SQL: Дата и время

Глобализация и локализация программ PL/SQL: Дата и время

До настоящего момента наше обсуждение глобализации было сосредоточено исключительно на строках. Тем не менее проблемы с датой и временем порой создают в ходе локализации ничуть не меньше проблем. Пользователи, работающие с вашей базой данных и веб-сервером, могут находиться на другом конце света, но им все равно нужна точная информация, относящаяся к их часовому поясу, а дата и время должны иметь знакомую структуру.


Оглавление статьи[Показать]


Основные проблемы, усложняющие работу с датой и временем:

  •  В мире существует много разных часовых поясов.
  •  В одних регионах действует летнее время, в других его нет.
  •  В некоторых локальных контекстах используются разные календари.
  •  В мире не существует общепринятых правил форматирования даты/времени.

 

Типы данных временных меток

До выхода Oracle9i работа с датой и временем была достаточно тривиальной. В распоряжении разработчика был тип DATE и функция TO_DATE (читайте эту и эту  статью). Из-за ограниченных возможностей типа DATE разработка глобальных приложений становилась делом утомительным и рутинным. Введение любых поправок на часовой пояс требовало ручных вычислений. К сожалению, если ваше приложение должно работать в Oracle8i и более ранних версиях, этот вариант остается единственным.

А в распоряжении пользователей Oracle9i и последующих версий оказываются все выдающиеся возможности типов данных TIMESTAMP и INTERVAL. Если вы еще не читали эти статьи, я приведу краткую сводку, но рекомендую вернуться и прочитать ее для более подробного понимания темы.

Следующий пример демонстрирует типы данных timestamp, timestamp with time zone и timestamp with local time zone в действии:

DECLARE
   v_date_timestamp       TIMESTAMP ( 3 )                      := SYSDATE;
   v_date_timestamp_tz    TIMESTAMP ( 3 ) WITH TIME ZONE       := SYSDATE;
   v_date_timestamp_ltz   TIMESTAMP ( 3 ) WITH LOCAL TIME ZONE := SYSDATE;
BEGIN
   DBMS_OUTPUT.put_line ('TIMESTAMP:  ' || v_date_timestamp);
   DBMS_OUTPUT.put_line ('TIMESTAMP WITH TIME ZONE:  ' || v_date_timestamp_tz);
   DBMS_OUTPUT.put_line (   'TIMESTAMP WITH LOCAL TIME ZONE:  '
                         || v_date_timestamp_ltz
                        );
END;

Пример возвращает следующие результаты:

TIMESTAMP:                      08-JAN-05 07.28.39.000 PM
TIMESTAMP WITH TIME ZONE:       08-JAN-05 07.28.39.000 PM −07:00
TIMESTAMP WITH LOCAL TIME ZONE: 08-JAN-05 07.28.39.000 PM

Данные timezone и timezone with local timestamp идентичны, потому что время базы данных принадлежит тому же локальному контексту, что и время сеанса. Значение TIMESTAMP WITH TIMEZONE показывает, что пользователь находится в часовом поясе Мон­таны. Если бы подключение к базе данных в Колорадо осуществлялось из Калифорнии, то результат был бы немного другим:

TIMESTAMP:                      08-JAN-05 07.28.39.000 PM
TIMESTAMP WITH TIME ZONE:       08-JAN-05 07.28.39.000 PM −07:00
TIMESTAMP WITH LOCAL TIME ZONE: 08-JAN-05 06.28.39.000 PM

Значение timestamp with local timezone использовало часовой пояс сеанса (смещение -08:00), а значение было автоматически преобразовано.

 

Форматирование даты и времени

Одна из проблем локализации связана с форматированием даты и времени. Например, в Японии принят формат yyyy/MM/dd hh:mi:ssxff AM, тогда как в США распространен формат dd-MON-yyyy hh:mi:ssxff AM.

Подобные проблемы обычно решаются включением списка форматных масок в таблицу локальных контекстов. При подключении пользователя по его локальному контексту устанавливается правильный формат даты/времени для его региона.

В схему glln включены таблицы USERS и LOCALE, объединяемые по значению locale_id. Рассмотрим несколько примеров использования функций даты/времени (см. главу 10) и форматных масок из таблицы glln.locale.

Столбец registration_date в таблице относится к типу данных TIMESTAMP WITH TIME ZONE. Функция TO_CHAR и передача маски для локального контекста каждого пользователя обеспечивают отображение данных в правильном формате.


FUNCTION date_format_func
   RETURN sys_refcursor
IS
   v_date   sys_refcursor;
BEGIN
   OPEN v_date
    FOR
       SELECT locale.locale_desc "Locale Description",
              TO_CHAR (users.registration_date,
                       locale.DATE_FORMAT
                      ) "Registration Date"
         FROM users, locale
        WHERE users.locale_id = locale.locale_id;

   RETURN v_date;
END date_format_func;

Выполнение функции:

variable v_format refcursor
CALL date_format_func() INTO :v_format;
PRINT v_format

Результат выглядит так:

Locale Description          Registration Date
-----------------------     ------------------
English                     01-JAN-2005 11:34:21.000000 AM US/MOUNTAIN
Japanese                    2005/01/01 11:34:21.000000 AM JAPAN
German                      01 January 05 11:34:21.000000 AM EUROPE/WARSAW

В каждом из трех локальных контекстов назначена своя маска формата даты. Этот метод позволяет каждому пользователю работать с датой в формате, соответствующем локаль­ному контексту из его профиля. Если теперь добавить условие NLS_DATE_LANGUAGE, то при выводе даты и времени будет использоваться соответствующий язык из локального контекста. Давайте проверим правильность вывода на наших таблицах:


FUNCTION date_format_lang_func
   RETURN sys_refcursor
IS
   v_date   sys_refcursor;
BEGIN
   OPEN v_date
    FOR
       SELECT locale.locale_desc "Locale Description",
              TO_CHAR (users.registration_date,
                       locale.DATE_FORMAT,
                       'NLS_DATE_LANGUAGE= ' || locale_desc
                      ) "Registration Date"
         FROM users, locale
        WHERE users.locale_id = locale.locale_id;

   RETURN v_date;
END date_format_lang_func;

При выполнении функции:

variable v_format refcursor
CALL date_format_lang_func() INTO :v_format;
PRINT v_format

мы получаем следующий результат:

Locale Description          Registration Date
-----------------------     ------------------
English                     01-JAN-2005 11:34:21.000000 AM US/MOUNTAIN
Japanese                    2005/01/01 11:34:21.000000 午前  JAPAN
German                      01 Januar 05 11:34:21.000000 AM EUROPE/WARSAW

Данные в таблице USERS не изменились, но время выводится в формате конкретного ло­кального контекста. Значение NLS_DATE_LANGUAGE адаптируется для каждой территории, так что для японского локального контекста обозначение полудня (AM) выводится на японском, а название месяца в немецком локальном контексте выводится на немецком. Функцию можно усовершенствовать так, чтобы в выходных данных учитывался часовой пояс сеанса, — для этого значение преобразуется либо к типу данных TIMESTAMP WITH TIME ZONE, либо к локальному часовому поясу сеанса TIMESTAMP WITH LOCAL TIME ZONE. Для этого мы воспользуемся функцией CAST (см. главу 7), изменяющей тип данных значения, хранящегося в таблице:


FUNCTION date_ltz_lang_func
   RETURN sys_refcursor
IS
   v_date   sys_refcursor;
BEGIN
   OPEN v_date
    FOR
       SELECT locale.locale_desc,
              TO_CHAR
                 (CAST
                     (users.registration_date AS TIMESTAMP WITH LOCAL TIME ZONE
                     ),
                  locale.DATE_FORMAT,
                  'NLS_DATE_LANGUAGE= ' || locale_desc
                 ) "Registration Date"
         FROM users, locale
        WHERE users.locale_id = locale.locale_id;

   RETURN v_date;
END date_ltz_lang_func;

Выполнение функции:

variable v_format refcursor
CALL date_ltz_lang_func() INTO :v_format;
PRINT v_format

Данные выводятся в следующем формате:

Locale Description          Registration Date
-----------------------     ------------------
English                     01-JAN-2005 11:34:21.000000 AM −07:00
Japanese                    2004/12/31 07:34:21.000000 午後 −07:00
German                      01 Januar 05 03:34:21.000000 AM −07:00

Обратите внимание на следующие моменты:

  •  Язык даты/времени преобразуется к локальному контексту.
  •  Форматирование привязано к локальному контексту.
  •  Я использую CAST для преобразования значений, хранящихся в формате TIMESTAMP WITH TIMEZONE, в TIMESTAMP WITH LOCAL TIMEZONE.
  •  Время выводится относительно часового пояса сеанса (-07:00 в нашем примере).

Во многих примерах, приводившихся ранее, часовой пояс выводился в виде смещения UTC. Такой формат не всегда оказывается самым понятным для пользователя. Oracle ведет список названий регионов и сокращений, которые можно подставлять с измене­нием маски форматирования. Я вставил три записи, используя имена регионов вместо смещения UTC. Чтобы получить полный список часовых поясов, обратитесь с запро­сом к представлению V$TIMEZONE_NAMES. Другие примеры использования имен регионов встречаются в командах INSERT для таблицы USERS схемы g11n.

Необходимо упомянуть еще один параметр NLS, относящийся к дате/времени. При сохранении времени в таблице из нашего примера используется григорианский ка­лендарь, определяемый значением параметра NLS_CALENDAR. Однако не все локальные контексты используют тот же календарь, и смена базового календаря не компенсиру­ется никаким форматированием. Параметр NLS_CALENDAR позволяет переключиться с григорианского календаря на другие стандартные календари — скажем, на японский имперский календарь:

ALTER SESSION SET NLS_CALENDAR = 'JAPANESE IMPERIAL';
ALTER SESSION SET NLS_DATE_FORMAT = 'E RR-MM-DD';

После изменения конфигурации сеанса выполняется следующая команда SELECT:

SELECT sysdate
  FROM dual;

Она выводит измененное значение SYSDATE:

SYSDATE
---------
H 17-02-08

Значения по умолчанию определяются настройками NLS. Если в системе имеется основной локальный контекст, с которым вы работаете, то задать параметры NLS для базы данных намного проще, чем явно указывать их в приложении. С другой стороны, в приложениях, в которых настройки NLS должны изменяться динамически, рекомендуется включить их в состав настроек пользователя/локального контекста и сохранить их в приложении.

 

Вас заинтересует / Intresting for you:

Управление приложениями PL/SQL...
Управление приложениями PL/SQL... 4634 просмотров Stas Belkov Thu, 16 Jul 2020, 06:20:48
Встроенные методы коллекций PL...
Встроенные методы коллекций PL... 14740 просмотров sepia Tue, 29 Oct 2019, 09:54:01
Символьные функции и аргументы...
Символьные функции и аргументы... 18546 просмотров Анатолий Wed, 23 May 2018, 18:54:01
Тип данных RAW в PL/SQL
Тип данных RAW в PL/SQL 12240 просмотров Doctor Thu, 12 Jul 2018, 08:41:33
Войдите чтобы комментировать