📅

いろんなプログラミング言語での曜日のi18n対応状況

2023/08/03に公開

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以降なら標準ライブラリの chronoiomanipput_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を使ったコード例

https://go.dev/play/p/Cc3b0WRb9Sz

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.strftimelocale.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-i18nja.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.frameworkDateFormatter.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 👌
脚注
  1. https://github.com/mtgto/dotfiles/commit/fdf048939776f1b13b3da332744d8294b967d314 ↩︎

  2. Wandbox同様、日本語ロケールが有効になってないので apt-get install locales して /etc/locale.gen を編集して locale-gen してあげる必要があります ↩︎

Discussion