いろんなプログラミング言語での曜日のi18n対応状況
Pythonで今日の曜日を日本語表記で表示したいときってどうやるのかな (例えば木曜日なら "木") と思って調べていたところ、 locale.setlocale
してから strftime
すればよいことがわかりました。
(Powerlineでシェルに現在日時を表示してあげるために調べていました[1])
from datetime import datetime
import locale
locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8')
datetime.now().strftime('%a') # '%a' は曜日。日本語ロケールだと '木' とか '金' とかが返る
なんとなく気になったので曜日の日本語表記を表示する方法をいろんなプログラミング言語の標準処理系でどう行えるのかを調べてみました。なおstrftimeでロケールが影響しそうな要素には他にも月の表示などありますが今回は曜日だけしか調べていません。今回調べてみたプログラミング言語はメジャーどころから適当にもってきました。
確認環境はmacOS 13.5 (Apple Silicon) 、実行環境のロケールは日本語/日本になっております。
❯ locale
LANG="ja_JP.UTF-8"
LC_COLLATE="ja_JP.UTF-8"
LC_CTYPE="ja_JP.UTF-8"
LC_MESSAGES="ja_JP.UTF-8"
LC_MONETARY="ja_JP.UTF-8"
LC_NUMERIC="ja_JP.UTF-8"
LC_TIME="ja_JP.UTF-8"
LC_ALL=
C
POSIXの setlocale + strftime で実現できます。
❯ clang --version
Apple clang version 14.0.3 (clang-1403.0.22.14.1)
Target: arm64-apple-darwin22.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
#include <locale.h>
#include <stdio.h>
#include <time.h>
int main() {
setlocale(LC_TIME, "ja_JP");
char wday[4];
time_t t;
struct tm *tmp;
t = time(NULL);
tmp = localtime(&t);
if (strftime(wday, sizeof(wday), "%a", tmp)) {
puts(wday);
}
}
C++
バージョンによって書き方が結構変わりそうです。
❯ clang++ --version
Apple clang version 14.0.3 (clang-1403.0.22.14.1)
Target: arm64-apple-darwin22.6.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
C++03
標準ライブラリ ctime
を使えばCのように書けます。
// clang++ -std=c++03 main03.cpp -o main03
#include <clocale>
#include <ctime>
#include <iostream>
int main() {
std::time_t now = std::time(nullptr);
char wday[4];
std::setlocale(LC_TIME, "ja_JP.UTF-8");
std::strftime(wday, sizeof(wday), "%a", std::localtime(&now));
std::cout << wday << std::endl;
}
std::chrono
(C++11)
C++11以降なら標準ライブラリの chrono
や iomanip
の put_time
が使用できます。
// clang++ -std=c++11 main11.cpp -o main11
#include <chrono>
#include <iomanip>
#include <iostream>
#include <locale>
int main() {
const auto p = std::chrono::system_clock::now();
const auto t = std::chrono::system_clock::to_time_t(p);
std::cout.imbue(std::locale("ja_JP"));
const auto wday = std::put_time(std::localtime(&t), "%a");
std::cout << wday << std::endl;
}
std::format
(C++20)
C++20以降なら std::format が使えるようになり、ロケールによって表示が変わる値を文字列に変換するために第一引数にロケールを指定できます(省略可)。このためJavaやJavaScriptのようにすっきり書くことができるようになります。ただしXcode 14.3.1付属のClangでは std=c++20
を指定してもまだ対応できていません。
❯ clang++ -std=c++20 main.cpp -o main
main.cpp:63:20: error: no member named 'format' in namespace 'std'
auto wday = std::format(std::locale("ja_JP.utf8"), "{:L%a}", now);
~~~~~^
1 error generated.
GCC 13に std::format
のサポートが入ったのでDocker Hubの gcc:13
イメージを使って試してみました [2]。曜日をロケール依存で表示するにはフォーマット文字列として {:L%a}
を指定してあげればよさそうです。
#include <chrono>
#include <iostream>
#include <locale>
int main() {
auto now = std::chrono::system_clock::now();
std::string wday = std::format(std::locale("ja_JP.utf8"), "{:L%a}", now);
std::cout << wday << std::endl; // "木" や "金" など
}
Go
❯ go version
go version go1.20.6 darwin/arm64
Golangでは標準でi18nの機構をもっていません。
有名どころのi18nだと go-i18nとかでしょうか。ただgo-i18nは日時に関するロケールをもっているわけではないので、もし使いたかったら自分で曜日ロケールを定義してあげないとだめみたいです。
monday は日時に特化していますが、日本語を含む一通りのメジャーな言語の表記を標準でもっていそうです。
mondayを使ったコード例
package main
import (
"fmt"
"time"
"github.com/goodsign/monday"
)
func main() {
now := time.Now()
// 第二引数 "Mon" はもちろん2006-01-02の曜日 = 月曜日を表すフォーマット文字列です。
fmt.Println(monday.Format(now, "Mon", monday.LocaleJaJP))
}
Java SE
例えば SimpleDateFormat はコンストラクタの第二引数でロケールを受け取れます。
❯ java -version
java version "17.0.8" 2023-07-18 LTS
Java(TM) SE Runtime Environment (build 17.0.8+9-LTS-211)
Java HotSpot(TM) 64-Bit Server VM (build 17.0.8+9-LTS-211, mixed mode, sharing)
import java.text.*
var wday = new SimpleDateFormat("EEE", new Locale("ja", "JP")).format(new Date());
System.out.println(wday);
SimpleDateFormatにLocaleを指定できるので、システム全体を汚さなくてよいのはよいですが、スレッドセーフじゃないので注意です。
JavaScript
Node v18とFirefox v116で確認しました。
❯ node --version
v18.17.0
Intl.DateTimeFormatを使いましょう。もちろんDay.jsなどのi18nの機能を使ってもよいです。
Intl.DateTimeFormat("ja-JP", { weekday: "short" } ).format();
そういえばいつの間にかMDNの「ブラウザの互換性」表からInternet Explorerの列がなくなっていますね (Intl.DateTimeFormatはIE 11+で対応)。この記事を書いていて初めて気付きました。
PHP
strftime はPythonと同様にロケールをsetlocaleしてあげれば日本語で表示できます。
❯ php --version
PHP 8.2.8 (cli) (built: Jul 6 2023 10:57:44) (NTS)
Copyright (c) The PHP Group
Zend Engine v4.2.8, Copyright (c) Zend Technologies
with Zend OPcache v8.2.8, Copyright (c), by Zend Technologies
❯ php -a
Interactive shell
php > echo strftime('%a', time());
PHP Deprecated: Function strftime() is deprecated in php shell code on line 1
Thu
php > setlocale(LC_TIME, 'ja_JP.UTF-8');
php > echo strftime('%a', time());
木
↑でも警告が表示されていますが strftime
はPHP 8.1から非推奨になっており、代替として IntlDateFormatter:format とか使って、とドキュメントに記載されています。
https://php.watch/versions/8.1/strftime-gmstrftime-deprecated によると setlocale
が現在のスレッドだけでなくPHPプロセス全体に影響するためマルチスレッド処理で問題が発生する危険性があることが非推奨になった理由のようです。
IntlDateFormatterを使った例です。
$formatter = new IntlDateFormatter(
'ja-JP',
IntlDateFormatter::FULL,
IntlDateFormatter::FULL,
'Asia/Tokyo',
IntlDateFormatter::GREGORIAN,
'eee'
);
$formatter->format(time());
Python 3
❯ python3 --version
Python 3.11.4
datetime.strftime は locale.setlocale でセットされたロケールを見てくれるようです。ただし一回はロケールをセットしておかないと date.strftime('%a')
は 'Thu'
のような英語の省略形を返してきます。
Because the format depends on the current locale
https://docs.python.org/ja/3/library/datetime.html#strftime-and-strptime-format-codes
from datetime import datetime
import locale
locale.setlocale(locale.LC_TIME, 'ja_JP.UTF-8')
# もしくは現在のシステムロケールが日本となっているなら空文字列を指定してもよい
locale.setlocale(locale.LC_TIME, '')
datetime.now().strftime('%a') # 漢字で一文字
Ruby MRI
Rubyの標準ライブラリには日時の日本語表記はなさそうです。i18nのメジャーどころとしては gem i18n
でしょうか。
❯ ruby --version
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
gem i18nを使う
gem i18nには標準で日時の日本語表記が入ってません。そこで rails-i18n
の ja.yml
を参考に必要なデータをyamlで作成します。まあほんとに曜日だけを日本語にしたいなら配列で定義しちゃえばいい気もしますが…
❯ cat ja.yml
ja:
date:
abbr_day_names:
- 日
- 月
- 火
- 水
- 木
- 金
- 土
require 'i18n'
I18n.load_path += Dir["ja.yml"]
I18n.default_locale = :ja
I18n.l(Time.now, format: '%a')
Rust
Rustの日時ライブラリのデファクトである chrono を使います。
featuresで "unstable-locales" を指定することでi18nも使用できるようになります。 (format関数の代わりにformat_localized関数を使えたり)
[dependencies]
chrono = { version = "0.4", features = ["unstable-locales"] }
use chrono::{Local, Locale};
fn main() {
let now = Local::now();
println!("{}", now.format_localized("%a", Locale::ja_JP).to_string());
}
Swift
❯ swift --version
swift-driver version: 1.75.2 Apple Swift version 5.8.1 (swiftlang-5.8.0.124.5 clang-1403.0.22.11.100)
Target: arm64-apple-macosx13.0
Foundation.framework
の DateFormatter.locale
が有効です。
import Foundation
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ja-JP")
formatter.dateFormat = "EEE"
formatter.string(from: Date())
まとめ
思ったよりわりと言語 (処理系) によってマチマチでした。大まかにはJavaとかSwiftみたいになんでも入ってる処理系は当然使えたり、Rustみたいに最小構成にしておいて日時の文字列処理なんかはライブラリに切り出している、みたいなかんじでしょうか。
言語 | 外部ライブラリなしで可能か | 備考 |
---|---|---|
C | 👌 | グローバルなロケールを変更する必要がある |
C++ | 👌 | C++20の std::format が便利 |
Go | 🚫 | mondayなどの日時用のi18nライブラリが有効 |
Java | 👌 | |
JavaScript | 👌 | MDNの互換性リストからIEが消えてたのに気づいた |
PHP | 👌 | strftimeはPHP 8.1+から非推奨 |
Python | 👌 | グローバルなロケールを変更する必要がある |
Ruby | 🚫 | rails-i18nの config/*.yml だけgemにしてほしい |
Rust | 🚫 | chrono使っておけば大丈夫そう |
Swift | 👌 |
Discussion