kbkさんとこ。

setlocale(3)でどうやって

言語(language)

地域(territory)

文字符号化手法(encoding)

おまけ(modifiers)

を文字列にして指定するかは、 仕様では決まってないのですよ。

The locale argument is a pointer to a character string containing the required setting of category. The contents of this string are implementation-defined.

とある通り、"C", "POSIX", "", NULL以外は実装依存です。

まあUnix方面だと

言語 + "_" + 地域 + "." 文字符号化手法 ( + "@" おまけ)

というフォーマットで

となってる実装が多いのですが

これHTTPのAccept-Languageのように、 RFC4646 Tags for Identifying Languages

の仕様で縛られてる訳ではないです(そもそも言語タグだと"_"じゃなくて"-"ですな)。

混同されてる方は要注意。

さらに文字符号化手法については

eucJP

EUC-JP

ujis(テラナツカシス)

Extended_UNIX_Code_Packed_Format_for_Japanese (IANA charset registry儲向け)

Japanese-EUC (XFree86/xorgのlocale.alias参照)

と、まあ表記の揺れが激しいわけです、glibc2ではloose matchingとか導入したくらいで。

HTTPのAccept-CharsetはいちおIANA charset registry縛りがあるのだったっけ？

あれはPOSIX localeには何の強制もないですし、そもそも

setlocale(LC_ALL, "ja_JP.Extended_UNIX_Code_Packed_Format_for_Japanese");

なんて嫌すぐる *1 。

それと、複数のカテゴリをいっぺんにsetlocale(3)に指定する場合も

やりかたは実装によってバラバラです、いくつか確認してみましょ。

Solarisの場合

$ cat >hoge.c #include <locale.h> #include <stdio.h> #if defined(_WIN32) #define JPLOCALE "Japanese_Japan.932" #elif defined(__linux__) #define JPLOCALE "ja_JP.EUC-JP" #else #define JPLOCALE "ja_JP.eucJP" #endif main(void) { setlocale(LC_ALL, "C"); setlocale(LC_CTYPE, JPLOCALE); printf("%s

", setlocale(LC_ALL, NULL)); } ^D $ make hoge cc -o hoge hoge.c $ ./hoge /ja_JP.eucJP/C/C/C/C/C

各カテゴリをスラッシュ区切りで表示します(順序はLC_*の定数値の小さい方から)。

なのですが、"/"ではじまる場合はpathnameと解釈するちゅう仕様に反してる気が。

つかそもそもスラッシュ区切りだと、LC_ALLの場合pathnameが使えなくなりますな。

/usr/local/share/locale/ww_WW.wwwWW/LC_COLLATE;/usr/local/share/locale/ww_WW.wwwWW/LC_CTYPE;...

とか、せめて;とか:なんかにしとけばよかったのに。

glibc2の場合は

$ ./hoge LC_CTYPE=ja_JP.EUC-JP;LC_NUMERIC=C;...

となります、これはこれでウザい(注：単に個人差です)。

われらがNetBSDは↓になってます、FreeBSDが4.4BSD runeを拡張したコードが元ネタ。

$ ./hoge C/ja_JP.eucJP/C/C/C/C

Solarisと似てますが、先頭が"/"ではじまる場合はpathnameと解釈するPOSIX仕様とはいちお整合性あります。

まあLC_ALLの場合pathname使えないのはSolarisと一緒です(汗)。

んで最後、MSVCはglibc2と一緒っすね。

C:\> hoge.exe LC_COLLATE=C;LC_CTYPE=Japanese_Japan.932;...

んで気づいてしまったのだけど これ、将来的にカテゴリを追加する( ISO/IEC TR 14652にはある)

場合を考えると、カテゴリが足りない場合エラーになる今のNetBSD setlocale(3)の

実装では glibc2のLC_*_MASK問題と同様に、やっぱりバイナリの後方互換性失われるのよなぁ。

仕様にopaqueとは書かれてないので、移植性を諦めさえすれば

ハードコードすることは別に禁じられてないし…うーむ頭痛い。

どーせ互換性無くなるならglibc2/MSVC風の方がキモイけどリーズナブル。

この問題の別解として

setlocale(LC_ALL, "ja_JP.eucJP");

として、LC_ALLを指定した場合は全部同じロケールをセットすること以外は

禁止してしまえばいいようにも思えるのだけど

char *tmp, *s; setlocale(LC_ALL, "C"); setlocale(LC_CTYPE, "ja_JP.eucJP"); ... tmp = setlocale(LC_ALL, NULL); s = strdup(tmp); /* save current locale */ ... setlocale(LC_ALL, s); /* restore previous locale */ free(s);

というcurrent localeのsave/restoreを行うコードが動作しなくなっちまうよな。

まあアプリ側はそもそも この通り

そもそも、/ で区切る指定ができるということを POSIX その他は 全く規定していないですし、そういうことをやりたい人は LC_xxx で 明示的に指定するべきでしょう。

なのよな。

つーわけでパラノイアなアプリ実装者は、とことん安全側に倒して

char *tmp, *collate, *ctype, ...; setlocale(LC_ALL, "C"); setlocale(LC_CTYPE, "ja_JP.eucJP"); ... tmp = setlocale(LC_COLLATE, NULL); collate = strdup(tmp); /* save current LC_COLLATE */ tmp = setlocale(LC_CTYPE, NULL); ctype = strdup(tmp); /* save current LC_CTYPE */ ... setlocale(LC_COLLATE, collate); /* restore previous LC_COLLATE */ setlocale(LC_CTYPE, ctype); /* restore previous LC_CTYPE */ free(collate); free(ctype); ...

とした方がいいかもしれない、save/restore目的にはLC_ALLは使うなと。

ああ、やっぱりPOSIX localeは設計が腐(以下略

あと XFree86/xorg のlocale.aliasにある

POSIX-UTF2 C

なんじゃこれ(UTF2ってのはUTF-8の古い名前ね)。