UNIX系OSでの日付表記をISO 8601 (YYYY-MM-DD) にする方法

概要

UNIX系OSでの日付表記をISO 8601にする方法を調べた。結論としては,以下のコードを~/.profileか~/.bash_profileに記して,ログインシェルの設定に反映させればよい。

## Use ISO 8601 date time format (YYYY-MM-DDThh:mm:ss).
ISO8601_LOCALE=$(command -v locale >/dev/null && locale -a | sort -r |
	grep -e en_CA.UTF-8 -e en_DK -e se_NO -e si_LK -e sv_SE.ISO | head -n 1)
LC_TIME=$ISO8601_LOCALE date +%x | grep -q - && export LC_TIME=$ISO8601_LOCALE

導入

ノートパソコンにLinuxベースのOSであるUbuntuを使っている。トラブルがあったときに調べやすいように,言語設定 (ロケール) を英語にして,GUIやメッセージなどを英語表記にしている。

概ね満足しているのだが,一点気に入らないことがある。それは,日付の表示形式だ。

単にロケールを英語にすると,MM/DD/YYの形式になってしまう。これは以下のコマンドでも確認できる。

LC_ALL=C date +%x # 現在日時をロケールに従って表示
09/22/18

主にアメリカで使われるこの日付表記は,イギリスでの日時表記 (DD/MM/YY) と日にちと月の位置が入れ替わっており,非常に紛らわしい。例えば,2012-09-11が11/09/12と表示された場合,何年何月何日を指すのか特定できず,トラブルの原因となる。

この問題を避けるために,ISO 8601の日付表記を採用したい。ISO 8601はYYYY-MM-DDの形式で日付を表記する。この形式であれば,世界中のどの国の人でも何年何月何日かが一目で判断可能だ。

そこで,Linuxのロケールに日付表記がISO 8601を指定したい。LinuxやMacOS,Android,FreeBSDなどのUNIX系OSでは,LC_TIME環境変数が日時表記を制御している。そのため,LC_TIMEにISO 8601が適用されるロケールを指定する。

具体的に何の値を指定すれば,よいか調べてみたのだが,はっきりとした根拠のあるものが見つからず,OSの設定を確認するのが確実だと判断した。そこで,主要なOSでISO 8601の時刻表記に適用可能なロケールを調査した。

Linux

Linuxでは,/usr/share/i18n/locales/配下にlocaledefで生成するロケールファイルの入力ファイルが格納されている模様。ここのファイル群で書かれているロケール定義はPOSIXで定義されている。

特に,日付の書式は以下のd_fmtで定義されている。

d_fmt
Define the appropriate date representation, corresponding to the %x conversion specification. The operand shall consist of a string containing any combination of characters and conversion specifications. In addition, the string can contain escape sequences defined in Escape Sequences and Associated Actions .
Locale | POSIX 2018

具体的には,以下のように記述されている。

Ubuntu 16.04の/usr/share/i18n/locales/en_DKのd_fmtの定義
d_fmt    "<U0025><U0059><U002D><U0025><U006D><U002D><U0025><U0064>"

Unicodeの文字コードで記されているのでぱっとみわかりにくいが,これは%Y-%m-%dに等しい。

従って,Linuxの場合/usr/share/i18n/locales/配下のロケールと同名のファイル内に,上記の記述があるものが,日付表記にISO 8601を採用しているロケールだ。

このロケールを見つけるには,例えば以下のコードを実行すればよい。

LinuxでのISO 8601対応ロケール表示コード
for l in $(find /usr/share/i18n/locales/ -type f -exec grep -l "^d_fmt.*<U0059><U002D>.*<U006D>.*<U0064>" {} + | grep -o '[^/]*$'); do
  locale -a | grep $l
done

ただし,この方法はロケールの定義が用意されているLinuxだけで通用する。MacやFreeBSDなどのBSD系のOSは別の方法でロケールを定義しているようで,Linuxでしか使えない。

Ubuntu

手始めに,自分が使っているUbuntu 16.04のISO 8601対応ロケールを調べた。先ほどのコードをUbuntu 16.04で実行すると以下が出力された。

en_DK.utf8
en_CA.utf8

実際に,以下のコマンドで反映されることを確認できる。

LC_TIME=en_DK.utf8 date +%x
LC_TIME=en_CA.utf8 date +%x
2018-09-22
2018-09-22

このため,Ubuntu 16.04ではLC_TIMEにen_DK.utf8かen_CA.utf8を指定すれば,日付表記をISO 8601にできることがわかった。

Mac

続いて,Mac (OS X Snow Leopard 10.3.0) でのロケールには何を指定したらよいかを調査した。

MacやFreeBSDなどのBSD系OSでは,前述の方法は使えない。そこで,ロケールに関するファイルの格納ディレクトリーから,%Y-のキーワードで検索する。具体的には,以下のコマンドで検索した。シンボリックリンクが存在したので,-Lでリンクを辿らないようにしている。

find -L /usr/share/locale/ -type f -exec grep -i %y- {} +
/usr/share/locale/sv_SE.ISO8859-1/LC_TIME:%Y-%m-%d
/usr/share/locale/sv_SE.ISO8859-15/LC_TIME:%Y-%m-%d

その結果,sv_SE.ISO8859-1とsv_SE.ISO8859-15がヒットした。

実際にLC_TIMEにこの値を指定して実行したところ,きちんとISO 8601の日付表記が反映された。

LC_TIME=sv_SE.ISO8859-1 date +%x
LC_TIME=sv_SE.ISO8859-15 date +%x
2018-09-22
2018-09-22

また,Macのベースとなっている,FreeBSD (v9)で同じようにロケールを検索した。

find -L /usr/share/locale/ -type f -exec grep -i "%y-" {} +
/usr/share/locale/sv_SE.ISO8859-1/LC_TIME:%Y-%m-%d
/usr/share/locale/sv_SE.ISO8859-15/LC_TIME:%Y-%m-%d
/usr/share/locale/sv_SE.UTF-8/LC_TIME:%Y-%m-%d

こちらは,sv_SE.ISO8859-1とsv_SE.ISO8859-15だけでなく,sv_SE.UTF-8も存在していた。そして,これらの3種類のロケールで,LC_TIMEが反映されることがわかった。

調査

UbunutuとMacでの調査から,OSごとにISO 8601の反映に使用するロケール値が異なることがわかった。そこで,もう少し数を広げて調査することにした。具体的には,商用UNIX (Solaris, UnixWare, HP-UX),主要なLinuxディストリビューションのCentOS,組み込み系OS (OpenWrt),Windows (Cygwin/MSYS2, WSL) だ。

具体的には,OSごとに以下の内容のシェルコマンドを実行して日付の形式を目視で判断した。

ISO 8601ロケール確認コード
for l in $(locale -a |
grep -i -e csb_PL -e de_AT -e de_BE -e de_CH -e de_LU -e en_CA -e en_DK -e fr_CA -e hu_HU -e pl_PL -e ro_RO -e se_NO -e si_LK -e ^sv); do echo $l LC_TIME=$l date +%x done

調査結果を以下の表にまとめた。

ISO 8601ロケール調査結果
OSロケール検索場所ISO 8601ロケールコメント
Ubuntu 16.04/usr/share/i18n/localesen_DK.utf8
en_CA.utf8

CentOS 5/usr/share/i18n/localescsb_PL, csb_PL.utf8
de_AT, de_AT@euro, de_AT.iso88591, de_AT.iso885915@euro, de_AT.utf8
de_BE, de_BE@euro, de_BE.iso88591, de_BE.iso885915@euro, de_BE.utf8
de_CH, de_CH.iso88591, de_CH.utf8
de_LU, de_LU@euro, de_LU.iso88591, de_LU.iso885915@euro, de_LU.utf8
en_DK, en_DK.iso88591
, en_DK.utf8
fr_CA, fr_CA.iso88591, fr_CA.utf8
hu_HU, hu_HU.iso88592, hu_HU.utf8
pl_PL, pl_PL.iso88592, pl_PL.utf8
se_NO, se_NO.utf8
si_LK, si_LK.utf8
sv_FI, sv_FI@euro, sv_FI.iso88591, sv_FI.iso885915@euro, sv_FI.utf8
sv_SE, sv_SE.iso88591, sv_SE.iso885915, sv_SE.utf8
ro_RO*は違う形式。
Mac OS 10.3.0/usr/share/locale/sv_SE.ISO8859-1
sv_SE.ISO8859-15
sv_SE, sv_SE.UTF-8とその他,de_AT*, de_CH*, en_CA* (ISO8859-1, ISO8859-15, US-ASCII, UTF-8), fr_CA*, hu_HU*, pl_PL*, ro_RO*は違う形式。
FreeBSD/usr/share/locale/sv_SE.ISO8859-1
sv_SE.ISO8859-15
sv_SE.UTF-8
de_AT*, de_CH*, en_CA*, fr_CA*, hu_HU*, pl_PL*, ro_RO*は違う形式。
Solaris 10 /usr/share/locale/
商用UNIX。locale -aの出力がC,POSIX,iso_8859_1しかなく,ISO 8601利用不可能。
UnixWare 7.1.1/usr/lib/localesen_CA.UTF-8, fr_CA, fr_CA.850, fr_CA.863
ro_RO.ISO8858-2, ro_RO.UTF-8
sv
sv_FI, sv_FI.437, sv_FI.850, sv_FI.UTF-8
sv_SE.437, sv_SE.850, sv_SE.UTF-8
商用UNIX。en_CA.UTF-8のみYYYY-MM-DDだが,その他はYY-MM-DDと年が2桁になる。
de_AT*, de_CH,*, en_CA* (UTF-8以外), hu_HU*, pl_PL*は違う形式。
HP-UX 11.31/usr/lib/nls/loc
/src/

/usr/lib/nls/loc/srcにロケール定義ファイルが存在するが,ISO 8601形式の日付定義なし。
fr_CA*, hu_HU*, pl_PL, ro_RO*, sv_SE* (sv_SE.iso88591, sv_SE.iso885915@euro, sv_SE.roman8, sv_SE.utf8) は違う形式。
OpenWrt 15.05

組込み機器向けOS。localeコマンドが存在せず,ロケール自体が不在。
Cygwin/MSYS2/usr/share/locale/en_CA, en_CA.utf8
fr_CA, fr_CA.utf8
se_NO, se_NO.utf8
si_LK, si_LK.utf8
sv_FI, sv_FI.utf8, sv_FI@euro
sv_SE, sv_SE.utf8
/usr/share/locale/にはバイナリーしか存在しないため,他のOSで実績のあるロケールを試して確認。
ro_ROは違う形式。
Windows Subsystem for Linux (Windows 10,バージョン1607,OSビルド14393.2189)/usr/share/locale/
locale -aの出力がC, C.UTF-8, POSIX, en_US.utf8, ja_JP.utf8しかない。

この表から以下のことがわかった。

OSごとのISO 8601対応ロケールの調査結果考察
  1. csb_PL, de_AT, de_BE, de_CH, de_LU, en_CA, en_DK, fr_CA, hu_HU, pl_PL, ro_RO, se_NO, si_LK, sv_FI, sv_SEがISO 8601に対応している可能性がある。
  2. de_AT, de_CH, en_CA, fr_CA, hu_HU, pl_PL, ro_ROはISO 8601形式でない場合がある。
  3. Mac OS 10.3.0ではsv_SE.ISO8858-1とsv_SE.ISO8859-15はISO 8601だが,sv_SEとsv_SE.UTF-8はISO 8601でない
  4. HP-UX 11.31では,Mac OSとFreeBSDで唯一ISO 8601対応しているsv_SEのロケールがISO 8601と異なる形式となっている。
  5. UnixWareではen_CA.UTF-8のみISO 8601に対応している。
  6. csb_PL, de_BE, de_LU, se_NO, si_LK, en_DK, sv_FI, sv_SE.ISO*のどれかのロケールがlocale -aの出力に存在する場合,その値を使えばOK。

1. に記した通り,ロケールファイル内には記述がないにも関わらず,ISO 8601であるロケールがいくつかあった。しかし,2. に記した通り,これらの値はOSごとにISO 8601の場合とそうでない場合があるので注意が必要だ。とくに,3. に記した通り,Mac OS 10.3.0でsv_SEとsv_SE.UTF-8がダメなのが解せない。また,4に記した通り,HP-UXではMacとFreeBSDで唯一ISO 8601に対応しているsv_SEが違う形式となっている。幸いなことに,HP-UXのsv_SEはsv_SE.isoと小文字のisoなので,大文字のISOで識別可能だ。5. に記したUnixWareでは,en_CA.UTF-8のみISO 8601に対応している。en_CAはISO 8601に対応していないOSがそんざいするので注意を要する。そして,6. に記したのいずれかのロケールが存在する場合は,その値を使えばISO 8601を実現できそうだ。

方法

この調査結果を元に,ISO 8601をロケールに指定可能な場合に,指定するコードは以下となる。

ISO 8601のロケール指定コード
## Use ISO 8601 date time format (YYYY-MM-DDThh:mm:ss).
ISO8601_LOCALE=$(command -v locale >/dev/null && locale -a | sort -r |
	grep -e en_CA.UTF-8 -e en_DK -e se_NO -e si_LK -e sv_SE.ISO | head -n 1)
LC_TIME=$ISO8601_LOCALE date +%x | grep -q - && export LC_TIME=$ISO8601_LOCALE

実装方針は以下の通りだ。

ISO 8601ロケールの指定コードの実装フロー
  1. ロケールが存在しないOSのために,最初にlocaleコマンドの有無を判定
  2. ロケール内に,唯一ISO 8601に対応するOSが存在するen_DK, en_CA.UTF-8, sv_SE.ISO,CentOS, Cygwin/MSYS2に存在し,UnixWareに不在なse_NOとsi_LKのいずれかのキーワードが存在するか判定し,先頭1行を変数に格納
  3. en_CA.UTF-8は一番最後に判定したいので,locale -aの結果をsort -rで逆順にする。
  4. 万が一の競合による誤認防止のため,日付に-が入っているか確認後,LC_TIMEを更新

Solaris 10やOpenWrt,WSLのように,ISO 8601対応ロケールがそもそも存在しないOSがあったので,その場合はデフォルト値を変更しないようにした。

あとは,上記のコードをOS起動時のログインシェルの設定ファイルである~/.profile~/.bash_profileに記入して,ログイン時に読み込むようにすれば,ログイン後に反映される。

LC_TIMEはOS全体に影響があるので,ThunderbirdやWebブラウザーなど,LC_TIMEを参照している箇所全てに反映される。アプリケーションごとに個別に表示形式を指定する必要がなくなる。

結論

きっかけは,メールクライアントのThunderbirdの日時表記だった。毎日使うので気になっていた。

最初調べたときは,Ubuntuのen_DKとMacのsv_SEだけで対応できると思っていた。しかし,念の為他の環境を調べると,商用UNIXのUnixWareとHP-UXで厄介な競合があり,またMac OSもsv_SE単独とsv_SE.UTF-8の2個だけだがISO 8601に対応していないなど,頭を悩ました。

調査に際し,POSIXのLC_TIMEの定義を確認するなど,思っていた以上に複雑で時間がかかってしまった。しかし,その分確実な内容にできたのでよかった。これで,今後はアプリケーションの日付形式で悩むこともないだろう。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です