日付と時刻の国際化対応には、各言語の文字列の表だけでは対処できません。グレゴリオ暦がどこにでも通用すると単純に想定しているプログラムは、異なる言語を使用するだけでなく異なるカレンダーを使用する国々でトラブルを招きます。この記事では、日付と時刻を操作および表示することが可能な Java Class Library 機能について説明しています。また、Java カレンダー・クラスを拡張して、JDK に組み込まれていないカレンダーをサポートさせる方法を学ぶこともできます。最後に、仏教、ヘブライ、イスラムの各暦、および日本の年号をサポートする IBM 提供のクラスについてご紹介します。

Laura WernerIBM Center for Java Technology

Laura Werner 氏は Cupertino, CA にある IBM の Unicode Technology Group のマネージャーです。1994 年には Taligent に所属し、Taligent が IBM に吸収される少し前の 1997 年に Unicode グループに移動しました。1999 年にグループのマネージャーとなり、現在は IBM Cupertino で Java および C++ Unicode 推進運動のコーディネーターを担当し、Unicode クラス・ライブラリーの設計支援や、この記事のようなサイド・プロジェクトを行っています。Northwestern 大学で Geological Sciences および Integrated Science の学士を取得しました。



1999年 9月 01日

ここ数年、多くのプログラマーが抱える Java その他の言語における国際化の問題が大きくなって来ました。ソフトウェア産業と経済は総じてさらにグローバルなものになりつつあり、多数の言語および国々で正しく機能するアプリケーションの必要性が高まっています。また、Java Class Libraries、Win32 と Macintosh API、POSIX のようなプログラミング・ツールキットの多くには非常に広範囲な国際化対応サポートが組み込まれており、国際化対応アプリケーションの作成が以前よりも容易になりました。

これらの API は設計は異なりますが、核となる部分では同様の機能性を提供しています。このような機能には、たとえば、ユニコードを旧来のコード・ページに、またはその逆に変換する文字コンバーター、言語に依存する文字列比較に使用できるソート・ルーチンまたはコレーター・オブジェクト、各言語での単語または行の切れ目を検出する機能、さまざまな言語や国々の数値、日付、時刻、通貨記号をフォーマット(書式化)する機能などがあります。

日付、時刻、数値の表示規則は国ごとに異なるので、これらのフォーマッターは必須です。たとえば、米国英語では 1/1/2000 AD (または 1/1/2000 CE) という日付を "Saturday, January 1, 2000" と書きます。これに対して、英国英語では "Saturday, 1 January 2000" と書きます。またフランス語では "samedi 1 janvier 2000" と書きます。母国語重視の国際化対応ライブラリーならこれを扱うことができます。Java ではクラス java.text.DateFormatを使用してこれを行います。

しかし現在でも、何人のプログラマーがこの記事のタイトル "国際化対応カレンダー" に目をとめるでしょうか? 日付はもちろんのこと、カレンダーはどうでしょうか。このトピックが真っ先にリンクされるとは思えませんが、このことについて考えるなら接続は当然でしょう。

イスラエルでヘブライ語の使用者があなたのプログラムを使用しているときに何が起きるかを考えてみてください。前述の日付 1/1/2000 AD は または "Saturday 23 Tevet 5760" と表示されるでしょう。文字列だけでなく、数字 も異なります。

これはアメリカ人の多くにとっては意外でしょうが、イスラエルの公式カレンダーは、西洋の大部分で使用されているグレゴリオ暦ではなくヘブライ暦なのです。ヘブライ暦は、イスラム、ヒンズー、仏教、日本などの暦と同様、年の数字がすべて異なっています。ヘブライ暦を含め、これらの暦の多くは月の計算方法も異なります。1/1/2000 AD は January 1 ではなく、"23  または "Tevet 23" になります。

経緯

最初に、すこしの間 JDK 1.0 に話しを戻しましょう。Java の最初のリリースでは、国際化対応日時のサポートはどちらかといえば粗末なもので、自由に使えるツールとしては java.util.Date とそのtoStringメソッドくらいしかありませんでした。Java のそれ以降のリリースでも状況はあまり変わりません。Java char がユニコード文字として保管され、これが国際化対応サポートの始まりでした。しかしこれだけです。ラテン文字以外は入力も表示もできず、言語依存のフォーマット、ソート等々の機能はありませんでした。

Sun と IBM のマネージメントが JDK 1.1 のこの問題を修正する方法を発見しました。Java には国際化対応サポートがありませんでした。しかし IBM の Taligent は、優れた国際化対応テクノロジー、才能あるエンジニア (ユニコード国際協会会長 Mark Davis 博士他)、および Cupertino, California にある Sun の JavaSoft 部門から 100 ヤードという好い立地条件を持っていました。これによってパートナーシップが生まれました。IBM は、Java を実社会のビジネス・アプリケーションのために十分強力なものにするため、国際化対応クラスを Sun の JDK に提供するように Taligent の Text and International グループをとりまとめました。

Sun の国際化対応エンジニアとの協力のもとに、Taligent は新しいjava.text パッケージと、java.util の新しいクラスを提供しました。これには日時関連クラスDateFormat、SimpleDateFormat、Calendar、GregorianCalendar TimeZone、および SimpleTimeZone が含まれていました。これらのクラスについて順番に、まず旧来からのDate クラスについて見ていくことにしましょう。


日付

java.util.Date クラスは JDK 1.0 以来、Java の一部分でした。Date は時間内の特定の時点を表し、January 1, 1970 AD, 00:00 GMT 以降の長いミリ秒の形で保管されます。Date を構成するには、一般に次のコンストラクターを使用します。

              Date(int year, int month, int date)

または、時、分、秒などの追加の引き数を持つ上記の変形を使用します。さらに、引き数が "Sat Aug 12 1995 13:30:00 GMT" のような文字列 であるコンストラクターもあります。

JDK 1.0 では (また現在でも)、これらのメソッドはそのふれこみ通りにすべて機能しています。次のコードを実行した場合:

              Date d = new Date(99, 1, 1);
              String s = d.toString();

s の値は "Mon Feb 1 00:00:00 PDT 1999" になります。

ここには明白な問題がいくつかあります。もっとも問題なのは、コンストラクターに対する年の引き数が、1900 を基にしていると想定される 2 桁の数字だけだということです。これは重大な Y2K 問題です。なぜなら、1900 以前または 1999 以降の日付を指定する方法がないからです。これを修正する明確な手段もありません。既存のコードを壊してしまうために、Sun は最初のパラメーターの意味を変更することができません。Y2K 問題のないオーバーライドの追加も、異なる引き数タイプを持つという多重定義が必要になるため、困難です。

次の問題はtoString メソッドです。JDK 1.0 の資料には、US 英語の日付と時間帯の省略形による、"Sat Aug 12 1995 13:30:00 GMT" の形式の文字列が返されると記載されています。繰り返しますが、これを後のリリースで変更する方法はありません。Java Class Libraries の仕様を記載した資料で特定の動作が保証されていると、既存アプリケーションを損傷の危険なしに変更することはできなくなります。

最後に、上記のコードの一部で Date コンストラクターをもう一度見てみましょう。

              Date d = new Date(99, 1, 1);

月と日には "1" を渡していますが、結果の日付は February 1st になります。Java および C では配列は 0 ベースであり、月の数値が文字列の配列に対する索引として使用されることが多いため、Java のオリジナル設計者は月の数値を 0 ベースにしました。January は月 0、February は月 1...以後も同様になります。残念ながら人々の多く (プログラマーでさえ) は、January を 0 番目 (0th) ではなく 1番目 (1st) の月と考えるため、この選択は大きな混乱をもたらしました。

JDK 1.1 を使うようになって、私達はDate について何を行うべきか? の決定を迫られました。前述した問題のため、IBM と Sun は、Date は壊れると修正できないために置き換えることを決断しました。しかし、Date は非常に多くのこと -- 日付のフォーマット、カレンダー計算、時間帯 -- を行おうとしていたため、私達はこれをいくつかの異なるクラスで置き換えることにしました。


日付のフォーマット

最初の新しいクラスはDateFormat です。前述の通り、Date の String コンストラクターとそのtoString メソッドには 2 つの問題がありました。文字列が固定形式で、常に英語であることです。DateFormat はこれらの問題を両方とも解決しました。

DateFormat とその具象サブクラス SimpleDateFormat の役割は、Date オブジェクトから String へ、またはその逆へ変換し、Java がサポートするロケールのすべてにおいて正しくこれを行うことです。現行ロケールの日時をフォーマットするためのコードは、次のようにまったくシンプルなものです。

              Date d = new Date(1999, 0, 1);
              DateFormat f = DateFormat.getDateTimeInstance(
              DateFormat.FULL,//Date style DateFormat.FULL);//Time style String s = s.format(d);

これを US 英語システムで実行した場合、結果は "Friday, January 1, 1999 00:00:00 AM PST" になります。

ここまでは、単にDate.toString より手間のかかる方法のように見えます。しかし、これに国際化対応の機能が加わっているのです。コードの 2行目を次のように変更すると

              DateFormat f = DateFormat.getDateTimeInstance(DateFormat.FULL,
              DateFormat.FULL,
              Locale.FRANCE);

結果がフランス語 "venredi 1 janvier 1999 00:00:00 GMT-08:00" で示されます。

DateFormat は固定形式の問題も解決します。上の例では、日時の "FULL " フォーマッターを使用しています。もう少し簡潔な形式にしたい場合は、DateFormat.MEDIUM を使用できます。これを使用した結果は、英語では "Jan 1, 1999 00:00:00 AM" となります。同様に、DateFormat.SHORT の結果は "1/1/99 00:00 AM." になります。

出力の時刻ではなく日付だけを見たい場合も、ソリューションはシンプルです --getDateTimeInstance の代わりにgetDateInstance を呼び出します。

              Date d = new Date(99, 0, 1);
              DateFormat f = DateFormat.getDateInstance(DateFormat.FULL);
              String s = s.format(d);

この結果は "January 1, 1999." になります。

ここで、String を伴なうDate コンストラクターを思い出してみましょう。このコンストラクターはtoString と同じ (だが逆の) 問題を持っていました。固定形式を必要とし、文字列がすべて英語であると想定される点です。DateFormat は日付をフォーマットするだけでなく、日付を解析するため、これらの問題も解決されます。たとえば、次のコードを見てください。

              DateFormat f = DateFormat.getDateInstance(DateFormat.FULL,
              Locale.FRANCE);
              Date d = f.parse("venredi 1 janvier 1999");

Date オブジェクトd は 1/1/1999 を参照して終了します。前述の他の点はすべて、フォーマットと同じく解析にも当てはまります。つまり、特定のロケールを要求できること、長い形式または短い形式を選択できること、等々です。


TimeZone

上記のDateFormat の例の多くは、その出力に時間帯も含まれています。JDK 1.0 では、Java の時間帯ロジックはすべてDate.toString に組み込まれていました。このロジックでは、常に現在のデフォルトの時間帯で日付を表示すること、また時間帯名に US 英語の省略形を使用することが想定されています。この問題も、新しいクラスjava.util.TimeZone の追加により、JDK 1.1 で修正されています。

TimeZone とその具象サブクラスSimpleTimeZone は、ローカル・クロックとグリニッジ標準時の関係を扱う、相対的に低レベルのクラスです。これらを使用して GMT からローカル時、またはその逆に変換し、夏時間が有効であるかを判別することができます。他のクラスはその時刻関連の計算で TimeZone を使用しており、これらの多くは取得および設定が可能なプロパティーとして時間帯を公開しています。

シンプルな例を以下に示します。上記のすべての例で使用しているのと同じ日付を表示したいが、使用している時間帯にかかわらず、これを GMT で表示するものとします。そのコードは次のようになります。

              Date d = new Date(99, 0, 1);
              DateFormat f = DateFormat.getDateTimeInstance(DateFormat.FULL);
              f.setTimeZone(TimeZone.getTimeZone("GMT"));
              String s = s.format(d);

フォーマッターは GMT を使用し、その結果は " Friday, January 1, 1999 08:00:00 AM GMT " のようなものになります。時間帯が異なること、また GMT は PST より 8 時間後であるために時刻が 08:00 であることに注目してください。

JDK 1.1 では、DateFormat が TimeZone を使用する方法に 1つの問題がありました。もう一度この例に戻ってみましょう。

              Date d = new Date(99, 0, 1);
              DateFormat f = DateFormat.getDateTimeInstance(DateFormat.FULL,
              DateFormat.FULL,
              Locale.FRANCE);
              String s = s.format(d);

前述の通り、太平洋標準時 (PST) でコンピューターを稼働している場合の結果は "venredi 1 janvier 1999 00:00:00 GMT-08:00" になると想定されます。ただし、JDK 1.1.5 以前では、結果は実際には "venredi 1 janvier 1999 09:00:00 CEST" になっていました。CEST は "Central European Standard Time" (中央ヨーロッパ標準時) で、GMT の 1 時間後です。DateFormat はシステムのデフォルト時間帯ではなく、要求したロケールで見つかった最初の時間帯を使用していました。この場合だとLocale.FRANCE です。このことから、時間帯はプログラマーが期待したものにはならず、終わることのない混乱を引き起こします。

幸いなことに、この問題にはシンプルな回避策がありました。DateFormat に強制的にデフォルトの時間帯を使用させるには、次のようにすればよいのです。

              DateFormat f = . . . .;
              f.setTimeZone(TimeZone.getDefault());

JDK 1.1.6 ではこの問題は解決され、新たに作成された DateFormat オブジェクトは常にデフォルトの時間帯を使用するようになりました。別の時間帯が必要な場合は、DateFormat.setTimeZone を呼び出して要求することができます。

Calendar

JDK 1.1 で新しく追加された日付関連クラスの概要を見てきたので、次にこの記事の白眉であるjava.util.Calendar に進むことにします。まず、Date の項にあったコードの一部を見てみましょう。

              Date d = new Date(99, 1, 1);

この例では、コンストラクター引き数はグレゴリオ暦の February 1, 1999 AD に解釈されます。これにはgetDay、getMonth、getYear 等々の Date メソッドに類似した問題があります。冒頭で述べたように、一部の国々ではヘブライ、イスラムその他、さまざまなカレンダーを使用しています。国際化に完全対応する Java アプリケーションでは、グレゴリオ暦だけでなく、複数のカレンダー・システムをサポート可能であることが必要になります。

Java Class Libraries はオブジェクト指向であるため、この問題のソリューションは、一般のカレンダーを表す抽象クラスを、特定のカレンダー・システムの具象サブクラスを用いて作成することです。そして私達が行ったのはこれだけです。JDK 1.1 には、カレンダー操作用の汎用 API を提供する新しい抽象クラスjava.util.Calendar が組み込まれています。また具象サブクラスも 1 つ含まれています。これはもうお分かりかもしれませんが、GregorianCalendar です。

カレンダーには、古くて使用すべきでない Date の get メソッドに似たいくつかの抽象クラスがあります。たとえば、現在の年を知りたいとします。Date API を使用すると、そのコードは次のようになります。

              int year = new Date().getYear();

Calendar を使用した場合は、次のようになります。

              int year = Calendar.getInstance().get(Calendar.MONTH);

getInstance の呼び出しで現行ロケールに適したCalendar が作成され、get の呼び出しによりカレンダーのMONTH フィールドの現行値が返されます。Calendar は、YEARDAY_OF_MONTHDAY_OF_WEEKWEEK_OF_YEAR、その他を含む、15 ほどのフィールドについて定数を提供します。これらの定数はすべて Calendar オブジェクトが表すカレンダー・システムの用語に解釈されるため、ヘブライのカレンダー・オブジェクトを持っている場合は、グレゴリオ暦ではなくヘブライの月が得られます。

この仕組がよく分からない場合は、Calender が抽象クラスであることを思い出してください。get が呼び出されるたびに、カレンダーはフィールドが最新であるかを検査します。フィールドが最新でない場合は、抽象の保護メソッドcomputeFields を呼び出します。各サブクラスはこのメソッドをオーバーライドし、そのカレンダー・システムに適した計算を実行します。たとえばGregorianCalendar は、標準グレゴリオ計算を実行するcomputeFields メソッドを持っています。

Calendar.get は Date の使用すべきでない get メソッドを置き換えますが、コンストラクターについてはどうでしょうか? コンストラクターは、具象 Calendar サブクラスでこの機能を提供しています。January 1, 2000 に設定された Calendar を構成したい場合は、次のようにします。

              Calendar c = new GregorianCalendar(2000, Calendar.JANUARY, 1);

これにより問題がきれいに解決されます。どのカレンダー・システムが指定した年、月、日への解釈に使用されるかが正確に分かります。インスタンス化を行っているカレンダーです。誰かがヘブライ日付を入力し、HebrewCalendar クラスを持っている場合には、コードは次のようになります。

              Calendar c = new HebrewCalendar(5760, HebrewCalendar.TEVET, 23);

January は相変わらずゼロ

Date の多くの点を非難し、新しい Calendar クラスを追加したことからすれば、Date のもっとも厄介な問題、January は月 0 であるという事実は解決していると思われることでしょう。残念ながら解決されていません。Date が 0 ベースの月を使用し、Calendar が 1 ベースの月を使用しているのではと、プログラマーが混乱しないかが心配でした。おそらく混乱したプログラマーもいたことでしょう。しかし、Calendar が相変わらず 0 ベースであることが大きな混乱を招き、これはおそらく Java 国際化対応 API でのもっとも大きな間違いでした。

Calendar またはそのサブクラスを使用しているときは、Calendar 呼び出しでは、できれば数字そのものは使用しないのが得策です。次のようなコードを書く代わりに

              Calendar c = new GregorianCalendar(2000, 0, 1);

次のように書いてください。

              Calendar c = new GregorianCalendar(2000, Calendar.JANUARY, 1);

多少タイプする文字が多くなりますが、エラーは少なくなります。


add と roll

Date にはなかった Calendar の特徴の 1 つはカレンダー操作 でした。たとえば、1 か月後の日付を判別したいとします。旧 API では、getMonthgetYeargetDate を呼び出す、月に 1 を加算する、次の年に繰り上がるかどうかを見る、日付が月の範囲を超えていないか (うるう年があります) 等々、やらなければならない操作がたくさんありました。これは面倒なだけでなく、国際化対応になっていません。月の日数、月の名前、うるう年の計算方法などは、カレンダー・システムごとに異なるのです。

Calendar とそのサブクラスは、その "add androll" メソッドでこの問題を解決しました。現在の日付に 1 か月を加算したい場合、必要なコードは次の 2 行だけです。

              Calendar c = Calendar.getInstance();
              c.add(Calendar.MONTH, 1);

御存じの通り、add メソッドは指定した数値を指定したフィールドに加算します。これはすべてのカレンダー・システムの規則を認識しているため、このようなコードは正しく機能します。

              GregorianCalendar c = new GregorianCalendar(1999, Calendar.JULY, 29);
              c.add(Calendar.MONTH,7);
              String s = DateFormat.getInstance().format(c.getTime());

この計算結果は February 29, 2000 になります。GregorianCalendar.add は、11 の月 (December) が過ぎると 0 に戻ることを認識しています。また、February 29, 2000 が有効な日付であることも認識しています。この計算には、100 で割り切れる場合と 400 で割り切れる場合を除き、4 で割り切れる場合はうるう年である という複雑な規則を使用しています。

addroll メソッドに深く関連しています。このメソッドは、月の終わりから同じ月の始めに "戻る" か、同じことを週または年で行うようにユーザー・インターフェースをインプリメントしたいときに便利です。使い方は add とほぼ同じです。

              GregorianCalendar c = new GregorianCalendar(1999, Calendar.JULY, 29);
              c.roll(Calendar.DAY_OF_MONTH, 6);

結果は July 4, 1999 になります。roll は 29 に 7 を加算して 33 を得ます。これは 7 月の終わりを過ぎています。add はこれを August に継続し、結果は August 4th になります。roll はこれと異なり、July の始めに戻って July 4th になります。


ロケール特定カレンダー・プロパティー

これでカレンダーの国際化に関する問題はすべて解決したとお思いでしょうが、そうではありません。グレゴリオ暦のような 1 つのカレンダー・システムの中でも、国によっていくつかプロパティーが異なります。この例として、下に July, 1999 の米国バージョンとフランス・バージョンのカレンダーを示します。

米国
SunMonTueWedThuFriSat
123
45678910
11121314151617
18192021222324
25262728293031
フランス
lunmarmerjeuvensamdim
1234
567891011
12131415161718
19202122232425
262728293031

フランスでは、週の最初の日が Monday (lundi) ですが、米国では Sunday です。カレンダーをグラフィカルに表示するアプリケーションを作成している場合は、このことを考慮する必要があります。Java はこれを扱うために、メソッドCalendar.getFirstDayOfWeek を提供しています。カレンダーを作成するときは、次のように必要なロケールを指定し、

              Calendar c = Calendar.getInstance(Locale.FRANCE);

次に getFirstDayOfWeek を呼び出して、描画方法を調べることができます。

              int d = c.getFirstDayOfWeek();

関連するメソッドgetMinimalDaysInFirstWeek は、月の "最初の" 週とみなすかを通知します。上記の米国のカレンダーでは、July の最初の週は July 5 に始まる週でしょうか、1 月前の June 27 に始まる週でしょうか? Java のロケール・データに従えば後者です。なぜなら、getMinimalDaysInFirstWeek が 1を返します。


独自のカレンダーを作成する

これまでに述べた国際化対応カレンダー・フィーチャーはどれも優秀なものです。ただし、実際に行うことができるカレンダー国際化対応を制限してしまう落とし穴があります。JDK 1.1 と Java 2 はどちらも、Calendar :GregorianCalendar の 1 つの具象サブクラスのみを提供します。他の国々で使用されている旧来のカレンダーはまだサポートされていません。

ただし、方法はあります。さまざまなカレンダー・システムをサポートする Calendar の独自の具象サブクラスを作成することができるのです。わたしは仏教、ヘブライ、イスラムの暦、および日本の年号をサポートするクラスを作成したことがあり、このいくつかをここで御紹介します。

Calendar クラスを見ると、add、after、before、equals、getMinimumgetMaximumgetGreatestMinimumgetLeastMaximumrollcomputeTime、およびcomputeFields の 11 のメソッドがあることに気付くでしょう。独自のカレンダー・サブクラスをインプリメントするには、これらのメソッドをすべてオーバーライドして、カレンダー・システムに固有のインプリメンテーションを提供することが必要になります。これらのメソッドは 3 つの基本グループに分けることができます。

最初のグループでは minimum 関数と maximum 関数がもっとも簡単なので、いつもこれらを最初にインプリメントします。最初の 2 つgetMinimumgetMaximum は各フィールドに許される最大の範囲を通知しますが、getLeastMaximumgetGreatestMinimum はフィールドの最小範囲を通知します。たとえば、GregorianCalendar の DAY_OF_MONTH フィールドは minimum 1、maximum 31 を持っていますが、greatest minimum と least maximum は 1 と 29 です。このインプリメンテーションは簡単です。結果は定数になるので、これをテーブルに保管するだけです。メソッドは通常は次のようなものになります。

              public int getMinimum(int field){
              return minMax[field][0];
              }

カレンダー・クラスでもっとも重要な部分は、そのcomputeFields およびcomputeTime メソッドから構成されます。computeFields は絶対時刻からすべてのフィールドの値 (年、月、日など) を算出します。絶対時刻は January 1, 1970 以降のミリ秒で表されます。逆に、computeTime は、フィールド値を使用して絶対時刻を算出します。

これら 2 つのメソッドはカレンダー・システムの規則を正確にインプリメントする必要があるため、非常に複雑なものになります。実際のカレンダーの詳細を扱うことはこの記事の範ちゅうを超えてしまうため、ここでは大変シンプルなカレンダーを考えてみました。1 年が 360 日で、30 日ずつ 12 か月に分かれ、うるう年はありません。1 週間は 7 日で、このカレンダーでは 1/1/1 は土曜日です。このシンプルさをもとに、一般化の例をいくつか示します。

まず、計算は常に、カレンダーが始まる "エポック" 日付に基づきます。この日を常にカレンダーの 0番目 の日 (年 1 の最初の日の前の日) にするものとします。エポックを 1/1/1970 AD 以降のミリ秒で指定する定数を定義することが必要になります。定数を都合の良いものにするために、ヘブライ暦が始まるのと同じ日にサンプル・カレンダーを始めることにします。

              private static final long EPOCH_MILLIS = -180799862400000L;

秒、分、時、などのミリ秒単位の定数もいくつか必要になります。

              private static final long SECOND_MS = 1000;
              private static final long MINUTE_MS = 60 * SECOND_MS;
              private static final long HOUR_MS = 60 * MINUTE_MS;
              private static final long DAY_MS = 24 * HOUR_MS;

次に、モジュラー計算法を使うことにします。計算の多くはエポック以降の日数に基づくため、下の計算をまず行わなければなりません。

              long absDay = (time - EPOCH_MILLIS)/DAY_MS;

この値が出たら、年、月、日は簡単に求められます。

              int year = (int)(absDay/360)+1;
              int month = (int)((absDay/30)%12)+1;
              int day = (int)(absDay%30)+1;

実際のカレンダーでは当然ですが計算はずっと複雑になります。月および年の長さの変化、うるう年、ヘブライ暦の "延期規則" のような複雑な相互依存性などを考慮しなければなりません。このテーマにはたくさんの優れた参考文献があります。ふだん使用しているサーチ・エンジンで見つけられる Web サイトもあります。わたしが一番好きなのは、この記事の終わりの参考文献にあるCalendrical Calculations という本です。


実際のコード

グレゴリオ暦以外のカレンダーをインプリメントする実際の Java クラスを見たい場合は、http://www.alphaWorks.ibm.com/tech/calendars にアクセスしてみてください。このページにある "International Calendars" パッケージは、仏教、ヘブライ、イスラムの暦、および日本の年号をサポートします。このパッケージには JavaResourceBundle ファイルが入っており、これらのカレンダーの各種言語に翻訳されたストリングや、日付をグレゴリオ暦以外のカレンダーのストリングとして変換するためのユーティリティー・メソッドが含まれています。

この記事が Java でカレンダーを扱う際の助けになることを願っています。多少の難はあっても、Java のカレンダー・フレームワークは私がこれまでに多くのオペレーティング・システムやアプリケーション・フレームワークで見てきたなかでもっとも強力なものと言えます。


謝辞

この記事は、JDK の日時のクラスを担当する IBM エンジニア、Alan Liu の協力により作成しました。

参考文献

  • グレゴリオ暦以外のカレンダーについては、http://www.alphaWorks.ibm.com/tech/calendars にアクセスしてみてください。"International Calendars" パッケージには JavaResourceBundle ファイルが入っており、これらのカレンダーの各種言語に翻訳されたストリングや、日付をグレゴリオ暦以外のカレンダーのストリングとして変換するためのユーティリティー・メソッドが含まれています。
  • Calendrical Calculations (Nachum Dershowitz および Edward M. Reingold 共著、Cambridge University Press, 1997) -- 一般のカレンダー・アルゴリズムに加えて、現在よく使用されているすべてのカレンダーの詳細アルゴリズムについての記述があります。
  • The Java Class Libraries, 2nd Edition, Vol. 1 (Chan, Lee, および Kramer 共著、Addison-Wesley, 1998) -- Calendar と GregorianCalendar について説明しています。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Java technology
ArticleID=218915
ArticleTitle=Javaにおける国際化対応カレンダー
publish-date=09011999