setlocale の動作

# setlocale テスト プログラム

コマンドライン引数に <code>CATEGORY[=LOCALE]</code> を並べて動作を調べる簡単なプログラムです。

<code>CATEGORY</code> の "LC_" は省略可能で、大文字・小文字は区別しません。

引数が <code>CATEGORY</code> では

<dd><dl><code>setlocale(CATEGORY, NULL)</code></dl></dd>

の結果を表示します。<code>CATEGORY=LOCALE</code> の形式では上記に加えて

<dd><dl><code>setlocale(CATEGORY, LOCALE)</code></dl></dd>

の結果も表示します。

オプション <code>-l</code> は使用可能なカテゴリ表を出力します。

```c++:setlocale.cpp
#include <stdio.h>
#include <locale.h>
#include <string.h>

#include <map>
#include <string>

#if _MSC_VER
#define strcasecmp _stricmp
#endif

using namespace std;

struct StrCaseCmp
{
    bool operator ()(const string& a, const string& b) const
        { return strcasecmp(a.c_str(), b.c_str()) < 0; }
};

typedef map<string, int, StrCaseCmp> LC_CATEGORY;

static const char* program = NULL;

static int usage()
{
    printf("Usage: %s [-l] CATEGORY[=LOCALE] [CATEGORY[=LOCALE] ...]\n", program);
    return 1;
}

int main(int argc, char** argv)
{
    LC_CATEGORY category_table;
    category_table["LC_ALL"] = LC_ALL;
    category_table["LC_COLLATE"] = LC_COLLATE;
    category_table["LC_CTYPE"] = LC_CTYPE;
    category_table["LC_MONETARY"] = LC_MONETARY;
    category_table["LC_TIME"] = LC_TIME;
#ifdef LC_ADDRESS
    category_table["LC_ADDRESS"] = LC_ADDRESS;
#endif
#ifdef LC_IDENTIFICATION
    category_table["LC_IDENTIFICATION"] = LC_IDENTIFICATION;
#endif
#ifdef LC_MEASUREMENT
    category_table["LC_MEASUREMENT"] = LC_MEASUREMENT;
#endif
#ifdef LC_MESSAGES
    category_table["LC_MESSAGES"] = LC_MESSAGES;
#endif
#ifdef LC_NAME
    category_table["LC_NAME"] = LC_NAME;
#endif
    category_table["LC_NUMERIC"] = LC_NUMERIC;
#ifdef LC_PAPER
    category_table["LC_PAPER"] = LC_PAPER;
#endif
#ifdef LC_TELEPHONE
    category_table["LC_TELEPHONE"] = LC_TELEPHONE;
#endif

    program = argv[0];

    if (argc < 2)
        return usage();

    for (int i = 1; i < argc; i++)
    {
        LC_CATEGORY::const_iterator cit;
        string arg(argv[i]);

        if (arg == "-l")
        {
            printf("CATEGORY:\n");
            for (cit = category_table.begin(); cit != category_table.end(); ++cit)
                printf("    %s\n", cit->first.c_str());
            continue;
        }

        size_t pos = arg.find('=');
        bool npos = (pos < arg.npos);
        string category_name(arg.substr(0, pos));
        string locale(arg.substr(pos + 1));

        cit = category_table.find(category_name);
        if (cit == category_table.end())
        {
            string tmp(string("LC_") + category_name);
            cit = category_table.find(tmp);
            if (cit == category_table.end())
            {
                printf("unknown category: %s\n", category_name.c_str());
                continue;
            }
            category_name = tmp;
        }
        category_name = cit->first;
        int category = cit->second;

        const char* pold = setlocale(category, NULL);
        string oldloc(pold ? pold : "(NULL)");

        if (!npos)
        {
            printf("setlocale(%s, NULL) = \"%s\"\n",
                   category_name.c_str(), oldloc.c_str());
            continue;
        }

        const char* pnew = setlocale(category, locale.c_str());
        string newloc(pnew ? pnew : "(NULL)");
        printf("setlocale(%s, \"%s\"): \"%s\" -> \"%s\"\n",
               category_name.c_str(), locale.c_str(),
               oldloc.c_str(), newloc.c_str());
    }

    return 0;
}
```


## <code>LOCALE</code> の既定

既定は

<dd><dl><code>setlocale(LC_ALL, "C")</code></dl></dd>

の状態で、現在のロケールを設定するには <code>main</code> 関数などで

<dd><dl><code>setlocale(LC_<var>CATEGORY</var>, "")</code></dl></dd>

を実行することになっているようです。


## <code>LOCALE</code> 文字列について

### 大文字・小文字の区別

区別されると不便なので、一般的に区別されないようですが、仕様として明示しているものは MSVC 以外に見つけられませんでした。ライブラリによっては区別されるのかもしれません。


### 「<code>-</code>」文字の省略

Windows では省略可能ですが、他では省略できない場合があります。


## Windows での setlocale

https://learn.microsoft.com/ja-jp/cpp/c-runtime-library/reference/setlocale-wsetlocale

<dd>
<dl>実行環境: Windows 11 バージョン 23H2 (CP932)</dl>
<dl>開発環境: MSVC v143 - VS 2022 C++</dl>
</dd>

```console:実行結果
>setlocale -l all all= ctype=.UTF8 ctype=en_US.UTF-8
CATEGORY:
    LC_ALL
    LC_COLLATE
    LC_CTYPE
    LC_MONETARY
    LC_NUMERIC
    LC_TIME
setlocale(LC_ALL, NULL) = "C"
setlocale(LC_ALL, ""): "C" -> "Japanese_Japan.932"
setlocale(LC_CTYPE, ".UTF8"): "Japanese_Japan.932" -> "Japanese_Japan.utf8"
setlocale(LC_CTYPE, "en_US.UTF-8"): "Japanese_Japan.utf8" -> "en_US.UTF-8"
```


## Unix 系 OS

<code>locale -a</code> コマンドで利用可能なロケール名は取得できます。


### Linux - Debian 11.9

https://manpages.debian.org/bookworm/manpages-ja-dev/setlocale.3.ja.html

```console:localeコマンド
$ locale -a
C
C.utf8
en_US.utf8
POSIX
```

少ないので、<code>apt</code> で <code>locales-all</code> をインストールすると

```console:localeコマンド
$ locale -a | grep -i en_US
en_US
en_US.iso885915
en_US.utf8
$ locale -a | grep -i ja_
ja_JP.eucjp
ja_JP.utf8
```

ja_JP では .eucJP と .utf8 が追加されました。

```console:実行結果
$ g++ --version
g++ (Debian 12.2.0-14) 12.2.0
〜〜〜(略)〜〜〜
$ ./setlocale -l all ctype=en_US.UTF8 ctype=en_US.UTF-8
CATEGORY:
    LC_ADDRESS
    LC_ALL
    LC_COLLATE
    LC_CTYPE
    LC_IDENTIFICATION
    LC_MEASUREMENT
    LC_MESSAGES
    LC_MONETARY
    LC_NAME
    LC_NUMERIC
    LC_PAPER
    LC_TELEPHONE
    LC_TIME
setlocale(LC_ALL, NULL) = "C"
setlocale(LC_CTYPE, "en_US.UTF8"): "C" -> "en_US.UTF8"
setlocale(LC_CTYPE, "en_US.UTF-8"): "en_US.UTF8" -> "en_US.UTF-8"
```

<code>locale</code> コマンドでは出てこない <code>en_US.UTF-8</code> は通ったので、「<code>-</code>」は無視されるようです。


### macOS 14.5

https://developer.apple.com/library/archive/documentation/System/Conceptual/ManPages_iPhoneOS/man3/setlocale.3.html

```console:実行結果
$ clang++ --version
Apple clang version 15.0.0 (clang-1500.3.9.4)
〜〜〜(略)〜〜〜
$ ./setlocale -l all all=ja_JP.UTF-8 ctype=en_US.UTF8 ctype=en_US.UTF-8 ctype=UTF-8
CATEGORY:
    LC_ALL
    LC_COLLATE
    LC_CTYPE
    LC_MESSAGES
    LC_MONETARY
    LC_NUMERIC
    LC_TIME
setlocale(LC_ALL, NULL) = "C"
setlocale(LC_ALL, "ja_JP.UTF-8"): "C" -> "ja_JP.UTF-8"
setlocale(LC_CTYPE, "en_US.UTF8"): "ja_JP.UTF-8" -> "(NULL)"
setlocale(LC_CTYPE, "en_US.UTF-8"): "ja_JP.UTF-8" -> "en_US.UTF-8"
setlocale(LC_CTYPE, "UTF-8"): "en_US.UTF-8" -> "UTF-8"
```

<code>en_US.UTF8</code> はエラーなので、「<code>-</code>」は省略できないようです。


# UTF-8 文字種を指定する

<code>setlocale(LC_CTYPE, "UTF-8")</code> がスッキリしてていいのですが、試した範囲で通るのは macOS だけでした。

UTF-8 の場合、<code>LC_CTYPE</code> の <code>locale</code> では、<code>&lt;言語&gt;_&lt;地域&gt;</code> 部分の影響は受けないハズなので、まず存在が期待できる <code>en_US</code> を使って

<dd>
<dl><code>setlocale(LC_CTYPE, "en_US.UTF8")</code></dl>
<dl><code>setlocale(LC_CTYPE, "en_US.UTF-8")</code></dl>
</dd>

の両方を試すのがよさそうです。が、順番に試すなら

```c:LC_CTYPEをUTF-8に設定
(setlocale(LC_CTYPE, ".UTF8") ||
 setlocale(LC_CTYPE, "UTF8") ||
 setlocale(LC_CTYPE, "UTF-8") ||
 setlocale(LC_CTYPE, "C.UTF8") ||
 setlocale(LC_CTYPE, "C.UTF-8") ||
 setlocale(LC_CTYPE, "en_US.UTF8") ||
 setlocale(LC_CTYPE, "en_US.UTF-8"));
```

となるでしょうか。