h20y6m.github.io

2022年12月02日 最終更新:2022年12月04日

LaTeXで旧暦してみる

これは「TeX & LaTeX Advent Calendar 2022」の2日目の記事です。
(1日目は zr_tex8r さん、3日目は hid_alma1026 さんです。)

LaTeXで和暦してみる

LaTeXを使って文章を書いていると、時々和暦を出力したくなる、というのはよくあることでしょう。jarticleなどの標準和文文書クラスやjsarticleなどの和文文書クラスでは\和暦命令を使うことで今日の日付(\today)を和暦で出力することが出来るようになります。

\和暦
\today
% => 令和4年12月2日

しかし、これら以外の文書クラスや特定の日を出力したいこともあります。そんな時はbxwarekiパッケージが便利です。クロスエンジンでXeLaTeXやpdfLaTeXでも利用でき(もちろん和文文字を出力するためのパッケージが別途必要)、今日の日付だけでなく指定した日付の和暦を出力することが出来ます。

\warekitoday % 今日の日付
% => 令和4年12月2日

\warekisetdate{2022}{12}{25} % 指定の日付
\warekidate
% => 令和4年12月25日

過去の元号にも対応しています。

平成も、昭和も、大正も、明治も……

\warekisetdate{2018}{4}{31}
\warekidate
% => 平成31年4月31日

\warekisetdate{1983}{6}{19}
\warekidate
% => 昭和58年6月19日

\warekisetdate{1923}{9}{1}
\warekidate
% => 大正12年9月1日

\warekisetdate{1873}{1}{1}
\warekidate
% => 明治6年1月1日

明治も……

\warekisetdate{1872}{12}{31}
\warekidate
% => 1872年12月31日

……あれ?

なぜか西暦になってしまいました。どうしてでしょうか? とりあえずbxwarekiパッケージのドキュメント(なぜか英語しかない)を読んでみましょう。「1 Overview」に何やら注意事項が書かれています。

Note that the package only deals with dates in the year 1873 or later, ...

どうやら西暦1873年(明治6年)以降しかサポートされていないようです。それにしても明治6年というのはなんだか中途半端な感じがします。続きを読んでみます。

... where the Japanese calendar is really a Gregorian calendar with the different notation of years.

グレゴリオ暦というというのが出てきました。そうです日本がグレゴリオ暦(太陽暦、いわゆる新暦)に移行したのが明治6年1月1日なのです。それ以前は日本の暦は太陽太陰暦いわゆる旧暦が使用されていました。明治6年1月1日以降は西暦の年だけを和暦に変換するだけですが、明治5年以前は月日の変換も必要になってきます。例えば上記の西暦1873年12月31日は和暦では明治5年12月2日です。このように西暦から明治5年以前の和暦への変換は自明ではありません。おそらくこのような事情からbxwarekiは明治6年以降の日付しかサポートしていないのでなないかと思われます。

それではLaTeXでそれ以前の和暦を出力することはできないのでしょうか? ちょっと調べてみましたが残念ながら明治5年以前の和暦の表示をサポートしているLaTeXパッケージはなさそうです。

LaTeXで旧暦してみる

無ければ作ればいいじゃない。

ということでLaTeXで旧暦するパッケージを作ってみました。

インストール

l3buildを使うと簡単にインストールできます。以下のコマンドを実行するとビルドとテストが自動的に行われ、TEXMFHOMEにインストールされます。--fullオプションをつけるとドキュメントなども一緒にインストールされます。

l3build install --full

パッケージの読み込み

通常通り\usepackageで読み込みます。オプションはありません。

\usepackage{bxjakyureki}

使い方

\jakyureki命令が使えるようになります。書式は次の通りです。

\jakyureki{<year>}{<month>}{<day>}

指定した西暦の日付に対応する和暦の日付に展開されます。

\jakyureki{1872}{12}{31}
% => 明治5年12月2日

応永元年7月5日(西暦1394年8月2日)から明治5年12月2日(西暦1872年12月31日)までの日付をサポートしています。(それ以前は南北朝時代をどうすればいいのか分からなかったので……)

なお西暦については1582年10月15日(天正10年9月19日)以降はグレゴリオ暦、それ以前はユリウス暦として扱われます。

パッケージ作成

使ったもの

今回はexpl3を使ってパッケージを作成しました。またパッケージの開発にはl3buildというツールを使ってみました。

expl3はLaTeX3を開発するために作られたTeX上で動作するプログラミング言語です。LaTeX3は完成することなく消えてしまいましたが、expl3はLaTeX2eカーネルに取り込まれLaTeXの新機能の開発で使われています。TeX言語では複雑なコードを書かなくてはいけないような処理も標準で関数が用意されていたりするので便利です。

l3buildはTeXパッケージ向けのビルドツールです。dtxのstripやドキュメンのタイプセットのほか、リグレッションテスト(いじった所と全然違うところが壊れたというのを検出するテスト)のための仕組みを備えています。またクロスプラットフォームのLuaスクリプトで書かれているのでMakefileが使いずらいWindowsでも安心して使えます。

使ったデータ

西暦から旧暦への変換が必要なのですが、この変換は単純な計算式で出来るものではありません。太陽太陰暦は地球と月と太陽の位置関係で決まります。これを計算で求めるにはとてもとても複雑な天体運動のシミュレーション計算が必要になってします。それだけでなく時代によっては「縁起が悪い」などの理由で人為的な調整が行われてたことをもあるため計算だけでは不可能です。そのため旧暦の変換は何らかのデータベースが必須となります。今回は国立天文台暦計算室の「暦月・節月データベース」を参照することにしました。

また和暦の変換では元号の情報も必要です。いろいろ探してみたのですがよさそうなものが見つからず結局ウィキペディアの「元号一覧 (日本)」を参照することにしました。

実装

実装方法としてはまず西暦(グレゴリオ暦orユリウス暦)の日付からユリウス通日に変換しています。日付を年月日で扱うより連続する数値になっている方がなにかと扱いやすいためです。

次にユリウス通日からざっくりと旧暦の年を計算した後、あらかじめ作っておいた旧暦テーブルを参照し旧暦の日付を求めています。旧暦テーブルは以下のように「年(西暦換算)」毎に「1月1日のユリウス通日」「閏月」「閏月の大小」「各月の大小」となっています。

\cs_gset:cpn { g__bajakyureki_kyureki_data_1870 }
  { { 2404095 } { 10 } { 0 } { 011010101010 } }

さらに年が西暦換算になっているので、またあらかじめ作っておいた元号リストを検索します。元号リストは以下のように改元日の「年(西暦換算)」「閏月フラグ」「月」「日」と「元号」のリストとなっています。

\tl_gput_right:Nn \g__bxjakyureki_gengo_data_tl
  { { 1868 } { 0 } {  9 } {  8 } { 明治 } \s__bxjakyureki }

あとは必要もないのになぜか(先頭)完全展開可能になるように実装していたり、コードをASCIIのみにするために文字コード列から日本語文字列のトークン列を生成していたりと、ややこしいことをしていますが、そんなことをしなければそこまで難しい処理はないはずです。

はまったところとか

expl3とかl3buildとかを使っている中ではまったところとかです。

l3intの整数除算は四捨五入

l3intの\int_*関数の整数式の除算は四捨五入されます。整数除算の結果が整数になるプログラミング言語は切り捨てのことが多いので注意が必要です。切り捨てた結果が欲しいときは\int_div_truncate:nnを使います。

l3intのスケール演算

l3intの\int_*関数の整数式では途中も含めて絶対値が231 − 1の範囲を超えた場合はオーバーフローエラーが発生しますが、掛け算の直後に割り算が続く場合はスケール演算という一つの演算となり掛け算部分でこの範囲を超えてもエラーにはなりません。例えば365.2422で割りたいときに単純に2451545*10000/3652422のようにしてもオーバーフローの心配がありません。

l3buildのドキュメントのタイプセットに使えるのはPDFエンジンのみ

l3buildのドキュメントのタイプセットに使用するエンジンはbuild.luaのtypesetexeで指定できますが、ここに指定したコマンドはPDFを出力することが期待されています。また"\input <filename>"のようなコマンドライン引数を処理出来る必要があるためptex2pdfのようなコマンドを指定することもできません。このため日本語ドキュメントをタイプセットしたい場合はLuaLaTeX一択になります。

l3buildはテストログを改竄する

l3buildのテストではログファイルからテストと無関係な部分を削除してくれますが、これ以外にもログを改竄することがあります。どのようなものが書き換えられるのかはl3buildのドキュメントの「1.12 Output normalisation」に書かれています。この挙動はbuild.luaの設定等で無効にすることはできないようです。今回は日付に関するテストにおいて日付とみなされるパターン(9999-99-999999/99/99)が....-..-..に改竄されてしまいテストがまともにできていなかったということがありました。幸い関数の展開結果等のテストしたい部分には該当のパターンが含まれていなかったので出力する内容を変更することで回避できました。

pdfTeXのテストで非ASCII文字

l3buildのデフォルト設定ではpdfTeXで非ASCII文字をログに出力すると^^abのような形式で出力されます。UTF-8入力でログ出力もUTF-8にしたい場合はbuild.luaで以下のような設定を行います。

asciiengines = {}

dtxでCJKutf8パッケージを使うとエラー

pdfLaTeXでちょっとだけ日本語したい場合CJKutf8パッケージを使うことがありますが、これをdtxファイルで使おうとするとよくわからない謎のエラーが発生します。これは\begin{CJK}の実行時にファイルを読み込んでいるのですが、dtxファイルでは%のカテゴリコードが変更されているため、読み込んだファイルのコメント部分でエラーになってしまうためです。そこで以下のような命令を定義することにしました。

\newcommand*{\Ja}[1]{%
  \bgroup
    \catcode`\%=14
    \begin{CJK}{UTF8}{ipxm}#1\end{CJK}%
  \egroup}

おわりに

expl3で旧暦するパッケージを作ってみました。

「LaTeXでアレをするパッケージってないんだ」というのがあって、「プログラミングちょっとできる」という人は、ぜひTeX言語でもexpl3でも(Luaでも?)好きな言語でLaTeXパッケージを作ってみましょう。