蝉は、やがて死ぬる午後に気づいた。ああ、私たち、もっと仕合せになってよかったのだ。:2019年05月03日分

2019/05/03(Fri)

[i18n] POSIX locale LC_TIME ERAそしてstrptime(3)の"%E"書式指定子は欠陥APIだったね

いまさら .NET Framework 用の日本の新元号対応更新プログラムの概要読んでたんだけど、 先日書いた

というややこしい実装になってるのは、すでに存在する「平成99年1月1日」みたいな文書のパースのためにやむをえないのかなぁと思った。 でも結局これだと2019年5月1日までに「平成99年1月1日」みたいな文書はすべて訂正する必要があるので、やっぱりあんま意味ないと思う。

なお POSIX localeのLC_TIME ERAフィールドとそれを内部で使ってる strptime(3)の"%Ec"書式指定子は、新元号をデータベースに追加した瞬間「平成99年1月1日」はパースできなくなるので欠陥APIだよなぁという思い、なんせPOSIX localeだって今回が改元初経験のはずなので許してクレメンス。

ただこれはstrptime(3)の実装をちょいちょい修正して

#include <locale.h>
#include <time.h>
#include <stdio.h>
#include <string.h>

int
main(void)
{
	const char *s[] = {
		"西暦2019年5月1日",
		"明治152年5月1日",
		"大正108年5月1日",
		"昭和94年5月1日",
		"平成31年5月1日",
		"令和元年5月1日",
		NULL
	}, **p;
	struct tm tm;
	char buf[1024];

	setlocale(LC_ALL, "");
	for (p = s; *p; ++p) {
		memset(&tm, 0, sizeof(struct tm));
		strptime(*p, "%Ec", &tm);
		strftime(&buf[0], sizeof(buf), "%Ec", &tm);
		puts(buf);
	}
}

を実行するとstrptime(3)での変換はすべて成功し、strftime(3)側の変換は

令和元年5月1日
令和元年5月1日
令和元年5月1日
令和元年5月1日
令和元年5月1日
令和元年5月1日

となるよう実装するのはわりと簡単にできるよね、エラーにしたい人の為には懐かしの環境変数POSIXLY_CORRECTあたりで挙動切替できるようにしときゃええやろ(鼻ホジ)。

だって年月の計算の方はstruct tmで-1月や13月に-1日や32日を扱うわけでさ、これやっぱカタテ=オチだったと思うよ。 ちなみにLC_TIME ERAは 西暦0年問題まで考慮して設計されてるので、令和0年が無くて令和-1年なんてのも正しく扱えるはずだがlocaledefデータベースは修正要るかも。

ンまぁ*BSDはどいつもこいつもstrftime/strptimeどちらも%Eなんぞ実装されてなかったはずなので、規格に欠陥があろうがなかろうが関係ない話であろう。 ワイの 完成目前のはずのコードもバックアップデータのどこに埋もれてんのか探すのめんどいし作業するとPTSDも発症するので永遠に日の目をみることはないだろう。

あとCygwinはWindows 7に新元号対応パッチ適用しても相変わらず平成なので、APIからひっぱってこずに自前で定義してるのね。 手元に転がってた何時のとも知れぬsnapshotのソース読んだらwinsup/cygwin/lc_era.hに定義があるようなので パッチ書いた、あとは好きにすればよい。