株式会社エス・スリー・フォー

16.4 RWLocale と RWZone

Tools.h++ では、抽象クラスの RWLocaleRWZone でこれらの問題に対応しています。RWDate を使ったことがある場合は、おそらく気づかないうちに RWLocale を使用しているはずです。日付や時刻を文字列に、または文字列から日付や時刻に変換するたびに、デフォルト引数が RWLocale の参照として渡されます。デフォルト引数は、変更しないかぎり、C 地域に設定されているプログラム起動時の RWLocale から派生したクラスのグローバルインスタンスです。RWLocale を明示的に使用するには、独自のインスタンスを生成して、それをデフォルトの代わりに渡すことができます。同様に、時刻を操作するとき、RWZone のデフォルト参照を独自のインスタンスに置き換えることもできます。

また、RWLocaleRWZone の独自のインスタンスを、グローバルのデフォルトとして使うこともできます。多くのストリームでは、独自の RWLocale インスタンスをストリームに使い、ストリームで転送される日付と時刻が、特別の引数がなくてもそのインスタンスに従ってフォーマットあるいは分解されるようにすることが可能です。この処理は、「ストリームを染める」と呼ばれ、次の節で詳しく説明されています。

次の節では、各種のデータを RWLocaleRWZone を使ってローカライズする例を見てみることにします。まず、今日の日付で日付を生成することから始めます。

RWDate today = RWDate::now();

これは、通常の C 地域表示方法で表示することができます。

cout << today << endl;

これ以外の地域にいる場合はどうするのでしょうか? または、フランス語の形式を必要とするため、環境変数 LANG が fr[1]に設定されているとします。日付を希望する形式で表示するには、RWLocale オブジェクトを生成します。

RWLocale& here = *new RWLocaleSnapshot("");

RWLocale によって定義されるインタフェースは、主に RWLocaleSnapshot クラスを使って実装します。このクラスは、標準 C ライブラリの strftime() 関数と localeconv() を使って、生成中にグローバル環境から必要な情報を抽出します。RWLocaleSnapshot を使う最も簡単な方法は、それを RWDate のメンバ関数 asString()[2]に渡すことです。

cout << today.asString('x', here) << endl;

ただし、もっと便利な方法があります。挿入演算子が使用できるように、here をグローバルのデフォルト地域として使うことができます。

RWLocale::global(&here);
cout << today << endl;

16.4.1 日付

ユーザがアメリカ人で、日付をドイツ語で表示したいが、ドイツ語をデフォルトにしたくないと仮定します。まず、ドイツ語の地域を生成します。

RWLocale& german = *new RWLocaleSnapshot("de");  //脚注 1 を参照

次のようにして、同じ日付をアメリカ人とドイツ人向けの両方に表示することができます。

cout << today << endl
     << today.asString('x', german) << endl;

x の定義については、『Tools.h++ Class Reference』で RWLocale の項目を参照してください。

日付をドイツ語で表示したい場合はどうでしょうか? この場合も、すべてを明示的に呼び出すことが、最も簡単な方法です。

RWCString str;
cout << "enter a date in German: " << flush;
str.readLine(cin);
today = RWDate(str, german);
if (today.isValid())
  cout << today << endl;

ただし、抽出演算子 >> を使用したい場合がしばしばあります。演算子はドイツ語形式の日付を期待し、それをどのように分解するかを知らなければならないため、ストリームをドイツ語の地域で染めて、この情報を渡します。

次のコードの断片は、ストリーム cin をドイツ語の地域で染め、ストリームを読み込み、日付文字列をドイツ語から変換して、それをローカルの形式で表示します。

german.imbue(cin);
cout << "enter a date in German: " << flush;
cin >> today;  // ドイツ語形式の日付を読み込む!
if (today.isValid())
  cout << today << endl;

特定の地域で挿入または抽出しなければならない値が数多くある場合、あるいは地域引数を必要とする場所に渡す方法がない場合に、この手法は便利です。静的メンバ関数 RWLocale::of(ios&) を使用することによって、ストリームに染め込まれた地域を見つけることができます。ストリームがまだ染められていない場合は、of() が現在のグローバル地域を返します。[3]

RWLocale によって定義されたインタフェースは、日付以外も処理します。これは、時刻、数値、金額を文字列から、または文字列へ変換することもできます。しかし各項目ごとにそれぞれ難しい点があります。時刻の変換は複雑で、時刻文字列が入力された場所と、それが表示される場所の時間帯を識別する必要があります。夏時間を採用する地域と採用しない地域があるので、さらに複雑となります。挿入および抽出演算子 (<< と >>) がすでに <iostream.h> で定義されているため、数値のフォーマットもやや面倒です。通貨の場合、主な問題は金額表示の国際的な基準がないことにあります。いずれにせよ、Tools.h++ は、これらの問題に対処しています。

16.4.2 時刻

はじめに、時間帯の問題について検討しましょう。時間帯と地域の間は、単純な関係ではありません。例えば、スイスには夏時間の規則も含め時間帯が 1 つしかありませんが、フランス語、ドイツ語、イタリア語、そしてロマンス語という 4 つの公式言語があります。その一方、ハワイとニューヨークは同じ言語を使用しますが、時差が 5 時間もあります。ハワイは夏時間を採用しないため、時期によっては時差が 6 時間になります。さらに、時間帯を計算する公式は、文化上の形式と関係がありません。このような理由から、Tools.h++では、時間帯の責任を RWLocal に任すのではなく、別の時間帯オブジェクトを使用します。

Tools.h++ では、RWZone クラスが時間帯についての情報をカプセル化します。これは、RWZoneSimple クラスによって実装されるインタフェースを持つ抽象クラスです。起動時に、ローカル時刻、標準時刻、およびグリニッジ標準時を表わす RWZoneSimple のインスタンスが 3 つ作成されます。ローカル時刻には、使用されている場合は夏時間が含まれます。RWTime クラスで絶対時刻を文字列へ、または文字列から変換するときは常に、RWZone のインスタンスが関わります。デフォルトで、ローカル時刻が仮定されますが、RWZone のインスタンスへの参照を渡すことができます。

さて、ここで例をみてみましょう。ニューヨークからパリへの旅行を計画したと考えてください。ニューヨークを 1993 年の 12 月 20 日 午後 11 時に出発して、パリ時間で 1994 年の 3 月 30 日の午前 5 時にパリを発って戻ります。パリに到着した時は、パリ時間で何時でしょうか?

まず、時間帯と出発時刻を生成します。

RWZoneSimple newYorkZone(RWZone::USEastern, RWZone::NoAm);
RWZoneSimple parisZone  (RWZone::Europe,    RWZone::WeEu);
RWTime leaveNewYork(RWDate(20, 12, 1993), 23,00,00, newYorkZone);
RWTime leaveParis  (RWDate(30,  3, 1994), 05,00,00, parisZone);

行きも帰りも飛行時間は 7 時間です。

RWTime arriveParis  (leaveNewYork + long(7 * 3600));
RWTime arriveNewYork(leaveParis   + long(7 * 3600));

ここで、ローカル時刻、パリ到着日時をフランス語で、ニューヨーク到着日時をアメリカ英語でそれぞれ表示してみましょう。

RWLocaleSnapshot french("fr");              // あるいはコンパイラが指定する値
cout << "Arrive' au Paris a` "
     << arriveParis.asString('c', parisZone, french)
     << ", heure local." << endl;
cout << "Arrive in New York at "
     << arriveNewYork.asString('c', newYorkZone)
     << ", local time." << endl;

いくつかの時間帯にまたがって飛行し、出発した日とは異なる日に到着したとしても、このコードは問題なく動作します。しかも、この例では出発した次の年に帰国し、その時点でフランスでは夏時間が始まっていますが、アメリカはまだ標準時間です。このような処理は、このコード例には明示されていませんが、RWTimeRWZone によって陰で処理されています。

Tools.h++ に内蔵されている夏時間規則に従う場所 (北米、西欧、夏時間なし) の場合、これらはすべて簡単です。ところで、春が 9 月に始まり、夏が 3 月に終わるアルゼンチンなど、その他の規則に従う場所はどうでしょうか?RWZoneSimple は、表駆動です。その規則が簡単なものであれば、RWDaylightRule 型の独自の表を生成し、RWZoneSimple を生成するときにそれを指定することができます。例えば、夏時間が 9 月の最後の日曜日の午前 2 時に始まり、3 月の最初の日曜日に終わるとします。単に RWDaylightRule の静的インスタンスを作成します。

static RWDaylightRule sudAmerica =
   { 0, 0, TRUE, {8, 4, 0, 120}, {2, 0, 0, 120}};

(これらの数字が何を意味するのかは、『Tools.h++ Class Reference』で RWZoneSimple の項目を参照してください。) 次に、RWZone クラスのオブジェクトを生成します。

RWZoneSimple  ciudadSud( RWZone::Atlantic, &sudAmerica );

これで、この例で paris や newYork を使用したのと同じように、ciudadSud を使うことができます。

では、イギリスのように、夏時間の規則が複雑すぎて簡単な表には記述できない場所ではどうすればいいのでしょうか? イギリスでは、夏時間が 4 月の第 3 土曜日の朝に始まります。ただし、その日が復活祭の場合は、1 週間前の土曜日に始まります。このような規則がある場合は、正しくラベルを付けて標準時間を使用するのが、最も良い方法です。これでもうまく動作しない場合は、RWZone からクラスを派生して、イギリス向けのインタフェースを実装することができます。この方法は、イギリスを含めすべての可能性を処理できるようにケースを一般化するよりも、ずっと簡単です。しかも、プログラムがより小さく、高速になります。

ここで解説する最後の問題は、ある特定の地域でどのような夏時間の規則が施行されているかを知るための標準の手段がない場合です。この場合、標準 C ライブラリは役に立ちません。例えば、ユーザに尋ねるなどして、アプリケーションが実行される環境から必要な情報を取得しなければなりません。

例えば、ローカル時刻の RWZone インスタンスは、夏時間が施行されている場合、北米の夏時間規則を使って生成されるというようなことです。ユーザが北米以外の場所にいる場合は、おそらく夏時間の変換が正しく行われないため、ローカル時刻を置き換える必要があります。例えば、パリにいる場合、この問題は以下のようにして解決できます。

RWZone::local(new RWZoneSimple(RWZone::Europe, RWZone::WeEu));

<rw/locale.h> を注意深く調べると、RWDateRWTime がまったく記述されていないことに気づくはずです。その代わり、RWLocale は標準 C ライブラリの struct tm 型を使います。RWDateRWTime の両方ともこの型への変換法として用意されています。このため、RWTime::asString() を使う代わりに、直接この型を使うこともできます。例えば、12:33 のように時間と分数だけから成る時刻の文字列を書かなければならないとします。strftime() に定義され、RWLocale で実装された標準形式には、このようなオプションはありません。ただし、トリックを使うことができます。次に方法の 1 つを示します。

RWTime now = RWTime::now();
cout << now.hour() << ":" << now.minute() << endl;

このコードでは、各種のマニピュレータを使わずに 9:5 のような文字列を生成できます。次に別の方法を示します。

RWTime now = RWTime::now();
cout << now.asString('H') << ":" << now.asString('M') << endl;

09:05.が生成されます。

前の 2 つの例では、時間と分数をそれぞれ抽出するために、now が 2 つのコンポーネントに分解されています。これは、コンピュータの処理能力を要求する操作です。時刻あるいは日付のコンポーネントでときどき作業をする必要がある場合は、時刻を一度だけ分解する方がいいでしょう。

RWTime now = RWTime::now();
struct tm tmbuf;
now.extract(&tmbuf);
const RWLocale& here = RWLocale::global();         // デフォルトの
                                                 // グローバル地域
cout << here.asString(&tmbuf, 'H') << ":"
     << here.asString(&tmbuf, 'M'); << endl;

1901 年より前の、または 2037 年より後の西暦で作業をする場合は、範囲を超えているため、RWTime を使わないでください。[4] struct tm の演算は制約されていないため、どの日時を変換するときも RWLocale を使うことができます。

16.4.3 数値

RWLocale には、文字列と数値 (整数および浮動小数点) の間の変換インタフェースも用意されています。RWLocaleSnapshot では、このインタフェースを実装し、標準 C ライブラリの struct lconv 型によって定義されている機能を完全に使うことができます。この機能には、正しい桁区切り記号、小数点、通貨の表示形式の使用が含まれます。文字列から変換するとき、RWLocaleSnapshot は同じ桁区切り記号を許可し、調べます。

残念なことに、標準 I/O ストリームライブラリが数値の挿入および抽出演算子を定義するため (これは置き換えできない)、クラスのストリーム演算はやや複雑になっています。その代わり、以下のように直接 RWCString の関数を使います。

RWLocaleSnapshot french("fr");
double f = 1234567.89;
long i = 987654;
RWCString fs = french.asString(f, 2);
RWCString is = french.asString(i);
if (french.stringToNum(fs, &f) &&
    french.stringToNum(is, &i))            // 変換を確認
  cout << "C:\t" << f << "\t" << i << endl
       << "French:\t" << fs << "\t" << is << endl;

フランス語では桁区切り記号にピリオドを、小数部と整数部の区切りにコンマを使用するので、このコードでは以下のように表示されます。

C:      1.234567e+07     987654
French: 1.234.567,89     987.654

桁区切り記号付きの数値の方が読みやすいことが分かります。

16.4.4 通貨

通貨変換は、数値変換よりもコツが要ります。これは、コンピュータで金額を表わす標準の方法がないためです。Rogue Wave では金額を現在使用されている最も小さい通貨単位を使って、整数値で表わすという規則を取り入れました。例えば、アメリカで $10.00 の残額は、以下のように表わすことができます。

double sawbuck = 1000.;

この表示方法には、広範囲、正確性、および移植可能という利点があります。広範囲というのは、$0.00 から $10,000,000,000,000.00 (国家予算よりも大きい) よりも大きい金額を正確に表示できるということです。「正確に」とは、小数部を使わずに金額を表わすことにより、計算を実行し、その結果を比べることができることを意味します。

double price = 999.;                                     // $9.99
double penny = 1.;                                       //  $.01
assert(price + penny == sawbuck);

値が price = 9.99 のようにそのまま示されていると、これが不可能になります。

移植可能とは、64 ビット整数や 2 進化 10 進表記 (BCD) ではなく、単に double が標準の型であることを意味します。もちろん、そうした他の表示形式でも財務計算を実行することができます。ただし、そのような表示形式と double との間で変換を行えるためにこれが可能となるのであり、初めから double を使っている方が便利なことは言うまでもありません。将来、RWLocale は、共通の表示形式にも直接対応する予定です。

次の通貨変換の例を検討してみてください。

const RWLocale& here = RWLocale::global();
double sawbuck = 1000.;
RWCString tenNone  = here.moneyAsString(sawbuck, RWLocale::NONE);
RWCString tenLocal = here.moneyAsString(sawbuck,RWLocale::LOCAL);
RWCString tenIntl  = here.moneyAsString(sawbuck, RWLocale::INTL);
if (here.stringToMoney(tenNone,  &sawbuck) &&
    here.stringToMoney(tenLocal, &sawbuck) &&
    here.stringToMoney(tenIntl,  &sawbuck))     // 変換を確認する
  cout << sawbuck  << "  " << tenNone << "  "
       << tenLocal << "  " << tenIntl << "  " << endl;

アメリカの地域では、このコードは以下のように表示します。

1000.00000  10.00  $10.00  USD 10.00

16.4.5 環境変数の設定について

第 5 章「RWTime クラスの使い方」で述べたように、Windows オペレーティングシステムを含め、コンパイラとオペレーティングシステムの中には、地域機能を動作させるために、ある特定の環境変数を設定しなければならないものがあります。これを行わないと、いろいろと面倒なことが起こります。

Borland、MetaWare、Microsoft、Symantec、Watcom のいずれかを使用している場合は、以下のように環境変数 TZ を適切な時間帯に設定する必要があります。

set TZ=PST8PDT

環境変数の設定については、コンパイラおよびオペレーティングシステムのマニュアルを参照してください。

注釈

  1. ^ ロケール名の規準は既にありますが、多くのベンダが異なる命名規準を提供しています。詳細は、各社のマニュアルを参照のこと。
  2. ^ asString() 関数の最初の引数は文字列で、これは標準 C ライブラリの strftime() 関数によって対応されている形式ならばどれでも構いません。
  3. ^ RWLocale::unimbue(ios&) 静的メンバ関数 を使ってストリームを染める前の状態に戻すことができます。これは、現在のグローバルロケールでストリームを染めることとは違うことに注意してください。
  4. ^ 64 ビットシステムで作業をしている場合は、使用できる日付の上限はありません。