カレンダーで遊んでみる②

今週の名言

「幸せを与えてくれるのは、富でも豪華さでもなく、穏やかさと仕事である。」
トーマス・ジェファーソン

各カレンダーを比べてみる

Oracle Database 11gR2 で和暦を含む7つのカレンダーをサポートしていることは前回紹介しました。(12cではさらに1つ追加)
私たち日本人は西暦(グレゴリオ暦)と和暦しか馴染みがないのですが、他のカレンダーを使う機会はまずないので、それらでちょっと遊んでみたいと思います。

2015年1月のカレンダーで比較する

例として、2015年1月を各カレンダーで表示させてみたいと思います。
1月1日のデータは作成しているので2日から31日までのデータを追加します。

SQL> INSERT INTO jcal VALUES (11,TO_DATE('20150102','YYYYMMDD'));

1 row created.

SQL> INSERT INTO jcal VALUES (12,TO_DATE('20150103','YYYYMMDD'));

1 row created.
....................................................................
SQL> INSERT INTO jcal VALUES (39,TO_DATE('20150130','YYYYMMDD'));

1 row created.

SQL> INSERT INTO jcal VALUES (40,TO_DATE('20150131','YYYYMMDD'));

1 row created.

SQL> COMMIT;

Commit complete.

それでは、6つのカレンダーを比較してみましょう。

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''')         "グレゴリオ暦"
  4  ,TO_CHAR(sdate,'YYYY.MM.DD', 'NLS_CALENDAR = ''Arabic Hijrah''')     "イスラム暦"
  5  ,TO_CHAR(sdate,'YYYY.MM.DD', 'NLS_CALENDAR = ''Persian''')           "ペルシャ暦"
  6  ,TO_CHAR(sdate,'eeyy.mm.dd', 'NLS_CALENDAR = ''Japanese Imperial''') "和暦"
  7  ,TO_CHAR(sdate,'eeyyy.mm.dd','NLS_CALENDAR = ''ROC Official''')      "台湾暦"
  8  ,TO_CHAR(sdate,'eeYYYY.MM.DD','NLS_CALENDAR = ''Thai Buddha''')      "タイ仏教暦"
  9  FROM jcal
 10  WHERE id = 1 OR id BETWEEN 11 AND 40
 11  ORDER BY sdate
 12 ;

 ID グレゴリオ暦  イスラム暦  ペルシャ暦  和暦          台湾暦             タイ仏教暦
--- ------------ ---------- ---------- ------------- ----------------- --------------------
  1 2015.01.01   1436.03.10 1393.10.11 平成27.01.01  中華民國104.01.01  พุทธศักราช2558.01.01
 11 2015.01.02   1436.03.11 1393.10.12 平成27.01.02  中華民國104.01.02  พุทธศักราช2558.01.02
 12 2015.01.03   1436.03.12 1393.10.13 平成27.01.03  中華民國104.01.03  พุทธศักราช2558.01.03
 13 2015.01.04   1436.03.13 1393.10.14 平成27.01.04  中華民國104.01.04  พุทธศักราช2558.01.04
 14 2015.01.05   1436.03.14 1393.10.15 平成27.01.05  中華民國104.01.05  พุทธศักราช2558.01.05
 15 2015.01.06   1436.03.15 1393.10.16 平成27.01.06  中華民國104.01.06  พุทธศักราช2558.01.06
 16 2015.01.07   1436.03.16 1393.10.17 平成27.01.07  中華民國104.01.07  พุทธศักราช2558.01.07
 17 2015.01.08   1436.03.17 1393.10.18 平成27.01.08  中華民國104.01.08  พุทธศักราช2558.01.08
 18 2015.01.09   1436.03.18 1393.10.19 平成27.01.09  中華民國104.01.09  พุทธศักราช2558.01.09
 19 2015.01.10   1436.03.19 1393.10.20 平成27.01.10  中華民國104.01.10  พุทธศักราช2558.01.10

 ID グレゴリオ暦  イスラム暦  ペルシャ暦  和暦          台湾暦             タイ仏教暦
--- ------------ ---------- ---------- ------------- ----------------- --------------------
 20 2015.01.11   1436.03.20 1393.10.21 平成27.01.11  中華民國104.01.11  พุทธศักราช2558.01.11
 21 2015.01.12   1436.03.21 1393.10.22 平成27.01.12  中華民國104.01.12  พุทธศักราช2558.01.12
 22 2015.01.13   1436.03.22 1393.10.23 平成27.01.13  中華民國104.01.13  พุทธศักราช2558.01.13
 23 2015.01.14   1436.03.23 1393.10.24 平成27.01.14  中華民國104.01.14  พุทธศักราช2558.01.14
 24 2015.01.15   1436.03.24 1393.10.25 平成27.01.15  中華民國104.01.15  พุทธศักราช2558.01.15
 25 2015.01.16   1436.03.25 1393.10.26 平成27.01.16  中華民國104.01.16  พุทธศักราช2558.01.16
 26 2015.01.17   1436.03.26 1393.10.27 平成27.01.17  中華民國104.01.17  พุทธศักราช2558.01.17
 27 2015.01.18   1436.03.27 1393.10.28 平成27.01.18  中華民國104.01.18  พุทธศักราช2558.01.18
 28 2015.01.19   1436.03.28 1393.10.29 平成27.01.19  中華民國104.01.19  พุทธศักราช2558.01.19
 29 2015.01.20   1436.03.29 1393.10.30 平成27.01.20  中華民國104.01.20  พุทธศักราช2558.01.20

 ID グレゴリオ暦  イスラム暦  ペルシャ暦  和暦          台湾暦             タイ仏教暦
--- ------------ ---------- ---------- ------------- ----------------- --------------------
 30 2015.01.21   1436.03.30 1393.11.01 平成27.01.21  中華民國104.01.21  พุทธศักราช2558.01.21
 31 2015.01.22   1436.04.01 1393.11.02 平成27.01.22  中華民國104.01.22  พุทธศักราช2558.01.22
 32 2015.01.23   1436.04.02 1393.11.03 平成27.01.23  中華民國104.01.23  พุทธศักราช2558.01.23
 33 2015.01.24   1436.04.03 1393.11.04 平成27.01.24  中華民國104.01.24  พุทธศักราช2558.01.24
 34 2015.01.25   1436.04.04 1393.11.05 平成27.01.25  中華民國104.01.25  พุทธศักราช2558.01.25
 35 2015.01.26   1436.04.05 1393.11.06 平成27.01.26  中華民國104.01.26  พุทธศักราช2558.01.26
 36 2015.01.27   1436.04.06 1393.11.07 平成27.01.27  中華民國104.01.27  พุทธศักราช2558.01.27
 37 2015.01.28   1436.04.07 1393.11.08 平成27.01.28  中華民國104.01.28  พุทธศักราช2558.01.28
 38 2015.01.29   1436.04.08 1393.11.09 平成27.01.29  中華民國104.01.29  พุทธศักราช2558.01.29
 39 2015.01.30   1436.04.09 1393.11.10 平成27.01.30  中華民國104.01.30  พุทธศักราช2558.01.30

 ID グレゴリオ暦  イスラム暦  ペルシャ暦  和暦          台湾暦             タイ仏教暦
--- ------------ ---------- ---------- ------------- ----------------- --------------------
 40 2015.01.31   1436.04.10 1393.11.11 平成27.01.31  中華民國104.01.31  พุทธศักราช2558.01.31

31 rows selected.

西暦と年だけが異なるもの、月日も異なるもの、いろいろな違いがあることがわかります。
タイ仏教暦などはなんて書いてあるかもわかりませんね。

各カレンダーの紀元を調べてみる

それでは、各カレンダーで「1年1月1日」というデータを作成して、西暦(グレゴリオ暦)で確認してみましょう。

イスラム暦

イスラム暦に関してはOracle Databaseでは「Arabic Hijrah(イスラム暦)」と「English Hijrah(英語版イスラム暦)」をサポートしていますが、両者の正確な違いは確認していません。
今回は「Arabic Hijrah(イスラム暦)」を使います。
ちなみにWikipediaでは次のように紹介されています。
イスラム暦(ヒジュラ暦)

SQL> ALTER SESSION SET NLS_CALENDAR='Arabic Hijrah';

Session altered.

SQL> INSERT INTO jcal VALUES (41,TO_DATE('00010101','YYYYMMDD'));

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''') "A.D."
  4  FROM jcal
  5  WHERE id = 41;

 ID A.D.
--- ----------
 41 0622.07.16

上記Wikipediaのリンクには「預言者ムハンマドがマッカからマディーナへ聖遷(ヒジュラ)したユリウス暦622年を「ヒジュラの年」と定めヒジュラ暦元年とする新たな暦を制定した。なお、ヒジュラがあったとされる正確な日付は同622年7月16日(ユリウス通日1948439日)である。」との記述があります。

ペルシャ暦

これまたWikipediaからの引用になりますが次のリンクに解説があります。
ペルシャ暦(イラン暦)

SQL> ALTER SESSION SET NLS_CALENDAR='Persian';

Session altered.

SQL> INSERT INTO jcal VALUES (42,TO_DATE('00010101','YYYYMMDD'));

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''') "A.D."
  4  FROM jcal
  5  WHERE id = 42;

 ID A.D.
--- ----------
 42 0622.03.21

紀元はヒジュラ暦と同じ年ですが、年初がグレゴリオ暦の3月21日となっているところが特徴です。

台湾暦

台湾暦とは「民国紀元」と呼ばれ、中華民国が成立された1912年を紀元とする暦です。
民国紀元(Wikipedia)
中華民国は、1911年の辛亥革命の結果、アジアで史上初の共和制国家として成立しました。
「台湾暦」というくらいなので中国(中華人民共和国)でこの暦法は使用されておらず、西暦が用いられているそうです。

SQL> ALTER SESSION SET NLS_CALENDAR='ROC Official';

Session altered.

SQL> INSERT INTO jcal VALUES (43,TO_DATE('00010101','YYYYMMDD'));

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''') "A.D."
  4  FROM jcal
  5  WHERE id = 43;

 ID A.D.
--- ----------
 43 1912.01.01

ちなみに、民国紀元の年は日本の大正(元年から15年のみ)あるいは北朝鮮の主体暦(チュチェ暦-金日成の生誕年が紀元)と偶然同じになっています。
北朝鮮のOracle使いは裏技で台湾暦を使っているかもしれません。(冗談)
また、台湾では「中華民國99年」から「中華民國100年」(西暦2011年)にかけて、年データを一部のシステムにおいて2桁で管理していたことによる、いわゆる「民国100年問題」というどこかで聞いたことがある問題が起きたそうです。

タイ仏教暦

これもWikipediaからの受け売りですが、タイ仏教暦は「仏滅紀元(Wikipedia)」とも呼ばれ、紀元はお釈迦様が入滅(死去)された年だそうです。
この年は宗教上の伝来によるものとされているようで、学術的に言われている年とは一致しないようですが、Oracleの仕様上はこの年が採用されています。

他の暦法と違って、(グレゴリオ暦)の紀元前から始まっているので、日付書式も若干異なっていることに注意してください。

SQL> ALTER SESSION SET NLS_CALENDAR='Thai Buddha';

Session altered.

SQL> INSERT INTO jcal VALUES (44,TO_DATE('00010101','YYYYMMDD'));

1 row created.

SQL> COMMIT;

Commit complete.

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'BC YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''') sdate
  4  FROM jcal
  5  WHERE id = 44;

 ID SDATE
--- --------------------
 44 紀元前 0542.01.01

日付書式BC/ADについて

上の問い合わせで日付書式「BC」は「紀元前」と日本語で表示されましたが、これは以下のように「NLS_DATE_LANGUAGE」パラメータが「JAPANESE」であることによります。
このパラメータは「NLS_LANGUAGE」パラメータから導出され、さらに「NLS_LANGUAGE」パラメータは環境変数「NLS_LANG」の先頭に指定された言語によって決まります。

SQL> SELECT PARAMETER,VALUE FROM NLS_SESSION_PARAMETERS
  2  WHERE PARAMETER LIKE 'NLS_%LANGUAGE';

PARAMETER                      VALUE
------------------------------ ------------------------------
NLS_LANGUAGE                   JAPANESE
NLS_DATE_LANGUAGE              JAPANESE

SQL> !env|grep NLS_LANG
NLS_LANG=Japanese_Japan.AL32UTF8

また、NLS_DATE_LANGUAGE=JAPANESEの環境においては、以下のように日付書式「BC」は日付書式「AD」と同じ結果を返す、つまり日付データが西暦0年よりも前であればどちらも「紀元前」と表示されるようです。

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'AD YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''') sdate
  4  FROM jcal
  5  WHERE id = 44;

 ID SDATE
--- --------------------
 44 紀元前 0542.01.01

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'AD YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''') sdate
  4  FROM jcal
  5  WHERE id = 43;

 ID SDATE
--- --------------------
 43 西暦 1912.01.01

SQL> SELECT
  2   id
  3  ,TO_CHAR(sdate,'BC YYYY.MM.DD', 'NLS_CALENDAR = ''Gregorian''') sdate
  4  FROM jcal
  5  WHERE id = 43;

 ID SDATE
--- --------------------
 43 西暦 1912.01.01

前回から2回に渡ってカレンダーでいろいろ遊んでみましたがいかがでしたでしょうか?

このように日付データはDATE型で格納しておけば、様々な日付書式が使えるだけでなく、「2月30日」のような明らかに不正なデータが混入する危険を防ぐことができるため大変有益です。

ところが、未だに文字型8桁のようなフォーマットで日付データを保持しているシステムを見ることがあります。
どうしてそのような設計になるのか理由はわかりませんが、わずかなパフォーマンス上のメリットを追求するあまりデータの正確性を犠牲にするような設計は根本的に間違っていると思います。

終わり