オープンソースのおと


わりとマイナーな機能ですが、linuxでja_JP.utf8のlocaleで以下のようにコマンドを叩くと、元号をちゃんと処理して表示してくれたりします。

$ LC_TIME=C date +'%EY'
2016
$ LC_TIME=ja_JP.utf8 date +'%EY'
平成28年

昨年、今上天皇の退位が話題になっていましたが、それにともない元号が変わるときには、少なくともこのコードを修正する必要がでてくるかもしれません。
この元号処理はどこで実装されているのかを探りましょう。

まずはlddで関連ライブラリを確認します。

$ ldd `which date`
linux-vdso.so.1 (0x00007ffc2cd7b000)
libc.so.6 => /lib64/libc.so.6 (0x00007f77170cd000)
/lib64/ld-linux-x86-64.so.2 (0x00005605aed5c000)

シンプルですね。linux kernel, dateコマンドの中身, libcのどれかに絞られました。
kernelはありえないので候補から外します。心配な人はstraceでもして安心してください。

date本体もないと思っているのですが念の為確認します。

$ LC_TIME=ja_JP.utf8 ltrace date +'%EY'
(中略)
strftime(" \345\271\263\346\210\22028\345\271\264", 1024, " %EY", 0x7f8f5c1464a0) = 12
fwrite("\345\271\263\346\210\22028\345\271\264", 11, 1, 0x7f8f5c142600) = 1
(以下略)

libcのstrftimeがやっているようです。man 3 strftime を見ます。

%E Modifier: use alternative format, see below. (SU)
(ー略ー)
(SU) The Single UNIX Specification mentions %Ec, %EC, %Ex, %EX,

%Ey, %EY, %Od, %Oe, %OH, %OI, %Om, %OM, %OS, %Ou,
%OU, %OV, %Ow, %OW, %Oy, where the effect of the O modifier
is to use alternative numeric symbols (say, roman numerals), and
that of the E modifier is to use a locale-dependent alternative
representation.

Single UNIX Specificationで決まっているとのこと。

さて、libcでlocale依存だということがわかりました。ソースコードを見ましょう。

$ find glibc-2.17-c758a686 | grep ja
glibc-2.17-c758a686/po/ja.po
glibc-2.17-c758a686/localedata/locales/ja_JP

ということで以下のファイルに含まれていそうです。

glibc-2.17-c758a686/localedata/locales/ja_JP

いかにもそれらしい「era」というエントリがあります。きっとこれなんですが……

era "<U002B><U003A><U0032><U003A><U0031><U0039><U0039><U0030><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U002B><U002A><U003A><U5E73><U6210><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
    "<U002B><U003A><U0031><U003A><U0031><U0039><U0038><U0039><U002F><U0030><U0031><U002F><U0030><U0038><U003A><U0031><U0039><U0038><U0039><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U5E73><U6210><U003A><U0025><U0045><U0043><U5143><U5E74>";/
    "<U002B><U003A><U0032><U003A><U0031><U0039><U0032><U0037><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0039><U0038><U0039><U002F><U0030><U0031><U002F><U0030><U0037><U003A><U662D><U548C><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
    "<U002B><U003A><U0031><U003A><U0031><U0039><U0032><U0036><U002F><U0031><U0032><U002F><U0032><U0035><U003A><U0031><U0039><U0032><U0036><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U662D><U548C><U003A><U0025><U0045><U0043><U5143><U5E74>";/
    "<U002B><U003A><U0032><U003A><U0031><U0039><U0031><U0033><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0039><U0032><U0036><U002F><U0031><U0032><U002F><U0032><U0034><U003A><U5927><U6B63><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
    "<U002B><U003A><U0032><U003A><U0031><U0039><U0031><U0032><U002F><U0030><U0037><U002F><U0033><U0030><U003A><U0031><U0039><U0031><U0032><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U5927><U6B63><U003A><U0025><U0045><U0043><U5143><U5E74>";/
    "<U002B><U003A><U0036><U003A><U0031><U0038><U0037><U0033><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0039><U0031><U0032><U002F><U0030><U0037><U002F><U0032><U0039><U003A><U660E><U6CBB><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
    "<U002B><U003A><U0031><U003A><U0030><U0030><U0030><U0031><U002F><U0030><U0031><U002F><U0030><U0031><U003A><U0031><U0038><U0037><U0032><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U897F><U66A6><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>";/
    "<U002B><U003A><U0031><U003A><U002D><U0030><U0030><U0030><U0031><U002F><U0031><U0032><U002F><U0033><U0031><U003A><U002D><U002A><U003A><U7D00><U5143><U524D><U003A><U0025><U0045><U0043><U0025><U0045><U0079><U5E74>"

ソースコードのくせに謎エンコードされてて読めない……!  >_<

unicodeの表が頭に全部はいっていれば心の目で読めるんでしょうがそんなスキルは持っていません。

しょうがないので <Uxxxx> みたいな入力が来たらxxxx部分にあたるunicode文字に置換する使い捨てスクリプトをシコシコ書きます。

    import os, re

    def main():
        for line in file('ja_JP'):
            out = ""
            while 1:
                m = re.search('(<U([0-9a-fA-F]+)>)', line)
                if m:
                    out = out + line[:m.start()] + unichr(int(m.group(2), base=16))
                    line = line[m.end():]
                else:
                    out = out + line
                    break
            print out.encode('utf8'),

    main()

これを実行すると.. でました

    era     "+:2:1990/01/01:+*:平成:%EC%Ey年";/
            "+:1:1989/01/08:1989/12/31:平成:%EC元年";/
            "+:2:1927/01/01:1989/01/07:昭和:%EC%Ey年";/
            "+:1:1926/12/25:1926/12/31:昭和:%EC元年";/
            "+:2:1913/01/01:1926/12/24:大正:%EC%Ey年";/
            "+:2:1912/07/30:1912/12/31:大正:%EC元年";/
            "+:6:1873/01/01:1912/07/29:明治:%EC%Ey年";/
            "+:1:0001/01/01:1872/12/31:西暦:%EC%Ey年";/
            "+:1:-0001/12/31:-*:紀元前:%EC%Ey年"

元年だけ別の行で定義して「1年」にならないようにしてるんですね。 明治は6年からなので、旧暦から新暦に移行するあたりのごちゃっとした話は気にしないと。

このlocaleの情報は、/usr/share/i18n/locales/ja_JP のように別ファイルになっていて、rpmの管理から逸脱してもよければその場で置き換えたりlocaledefを使うことで独自のlocaleを定義することもできます。

ただ、これを変更してもlocaleの設定がおこなわれるのはプロセスの中で setlocale() 関数が実行されるタイミングです。setlocale()関数は1プロセスの中で1回しか呼べないので、元号の処理更新を反映したいプロセスの再起動が必要なのは変わらないのでした。

glibc以外にも元号の処理をおこなっているプログラムはあります。私がRHEL7の中でみつけられた範囲ではOpenJDK、KDE、perl-Date-Manip、LibreOfficeがありました。

というわけで元号の処理をおこなっている場所はわかりました。元号が変わったらここを修正したglibcパッケージがでるでしょうからyum updateしましょう。

 

退位の話が今後どのように進むかはわかりませんが、必要に応じてぜひ参考にしてみてください。