BXjaholiday: LaTeX で祝日・曜日判定

2019-10-14   #LaTeX  #expl3 

日本には祝日がたくさんあり,祝日数世界第3位のホリデー大国にあたるのだそうです.さらに,2000年以降はいわゆる「ハッピーマンデー制度」のために日付が固定されないものが多く,日本の祝日判定の難しさは世界でも類を見ないレベルと考えられます(推測です.他国がどうかは調べてません).

そうした背景もあってか,某アレ Users Slack で TeX/LaTeX のための祝日判定機実装が要望されていたため,約1年前に expl3 で祝日判定を実装しました.

台風19号の影響で,一昨日の土曜日に予定されていた TeXConf 2019 が中止になり,TeX 不足でのたうち回っている人もいるかもしれないので,これを機にこのパッケージの紹介をしてみたいと思います.

基本的な使い方

BXjaholiday はすでに TeX Live と MiKTeX に取り込まれており,これらのディストリビューションの最新版利用者であれば特別にインストール等を行う必要はありません.

BXjaholiday を使用するには,まずは通常通りプリアンブルでパッケージの読み込みを行います.

\usepackage{bxjaholiday}

パッケージオプションはありません.BXjaholiday パッケージは pdfTeX, XeTeX, LuaTeX, (u)pTeX をサポートしています1

祝日名

最も基本的な命令は \jaholidayname です.この命令は

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

の形で使用します.<year>, <month>, <day> は西暦で日付を表す数値です.例えば2019年1月1日(元日)の祝日名を得たい場合には次のように記述します.

\jaholidayname{2019}{1}{1}%=>元日

これにより,紙面に「元日」と印字されます2.与えた日付が祝日でない場合は,何も印字されません(つまり,展開結果は空文字列になります).

少し TeX 言語要素の入った使い方になりますが,この命令の引数には具体的な算用数字だけではなくカウンタレジスタ等を与えることも可能です.例えば,TeX 実行時の年・月・日をそれぞれ格納しているカウンタレジスタ \year, \month, \day を引数に与えると,実行日(つまり「今日」)の祝日判定を行うことが可能です.

\jaholidayname{\year}{\month}{\day}

本日(2019年10月14日)は体育の日なので,上記により「体育の日」という文字列が得られます3

来週は改元に伴い特殊な祝日がありますが,BXjaholiday は対応済みです.

\jaholidayname{2019}{10}{22}%=>即位礼正殿の儀

なお,存在しない日付を表す数値を指定した場合の動作は未定義です(ほかの命令についても同様).また BXjaholiday が扱う「祝日」は,1948年7月20日公布の国民の祝日に関する法律(通称・祝日法)により規定されるものを言います.したがって,1948年7月20日より前の日付はすべて非祝日扱い(すなわち \jaholidayname の展開結果は空文字列)となります.

曜日名

ハッピーマンデー制度の存在のため,日本の祝日を判定するためには曜日判定の実装が必要です.そのため,BXjaholiday は副産物として曜日判定を行う \jadayofweek 命令も提供します.

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

使い方は \jaholidayname とまったく同様です.この命令は,指定した日付の曜日を表す漢字1字に展開されます4

\jadayofweek{2019}{1}{1}%=>火

ちなみに奥村先生の開発された okumacro パッケージの \曜 コマンドを用いても,実行日(つまり「今日」)の曜日を得ることができます.両者の内部的なロジックにはまったく違いがありません.\曜 コマンドとの違いは,書式(容易に好きな日付を指定できる)と \caption 中など LaTeX の「動く引数」内でも使用できるという点ぐらいです.

祝日判定

\IfJaHolidayTF コマンドを用いると,指定した日付が祝日であるか否かによって条件分岐を行うことができます.

\IfJaHolidayTF{<year>}{<month>}{<day>}{<true code>}{<false code>}

具体的な使用例を示すと,次のようになります:

\IfJaHolidayTF{2019}{1}{1}{祝日}{非祝日}%=>祝日
\IfJaHolidayTF{2019}{1}{2}{祝日}{非祝日}%=>非祝日

用途としては「2019年1月1日(火・祝)」のように日付表示したい際などを想定しています.

\newcommand{\mydate}[3]{%
  #1年#2月#3日%
  \IfHolidayPriceTF{#1}{#2}{#3}
    {(\jadayofweek{#1}{#2}{#3}・祝)}
    {(\jadayofweek{#1}{#2}{#3})}}

\mydate{2019}{1}{1}%=>2019年1月1日(火・祝)
\mydate{2019}{1}{2}%=>2019年1月2日(水)

\IfJaHolidayTF には祝日判定が真の場合,あるいは偽の場合の分岐のみを記述するバージョンも用意されています5

\IfJaHolidayT{<year>}{<month>}{<day>}{<true code>}
\IfJaHolidayF{<year>}{<month>}{<day>}{<false code>}

expl3 インターフェースを利用した応用

祝日や曜日の判定を伴うより複雑な処理を行う場合のために,BXjaholiday はさらに多機能な expl3 インターフェース(bxjh モジュール)を提供しています6.利用可能な expl3 インターフェースの網羅的な説明についてはパッケージ文書 (bxjaholiday.pdf) を参照してください7.ここでは,expl3 インターフェースを利用した応用の一例を紹介します.

応用例:休日料金の判定

カラオケ店や宿泊施設などでは,需要の関係から休日(週末・祝日)およびその前日(金曜・祝前日)に休日料金が適用される場合があります.ここでは,指定した日付がこの「休日料金の適用日」に該当するか否かを判定する expl3 関数 \my_if_holiday_price:nnnTF を定義してみましょう.

\my_if_holiday_price:nnnTF {<year>} {<month>} {<day>}
  {<true code>} {<false code>}

expl3 で条件分岐命令を定義する場合,\prg_new_conditional:Npnn 関数を用いると便利です.雛形は次のようになります.

\prg_new_conditional:Npnn \my_if_holiday_price:nnn #1#2#3 { TF }
  {
    <何らかの条件分岐関数>:TF
      { \prg_return_true: }  % 休日料金
      { \prg_return_false: } % 平日料金
  }

\prg_new_conditional:Npnn の第1引数には定義したい \my_if_holiday_price:nnnTF のうち TF 部分を含まない名前を指定することに注意してください.続いてやはり TF 部分を除く引数についてのパラメタテキストを記述します.第3引数は,定義したい条件分岐命令のバリエーションをカンマ区切りで指定します.今回は \my_if_holiday_price:nnnTF のみを用意しますが,同時に \my_if_holiday_price:nnnT\my_if_holiday_price:nnnF も定義したい場合は { TF, T, F } のように指定します.

4つ目の引数は定義本体で,ここに条件分岐のコードを記述していき,真の分岐(すなわち,定義した関数の T 引数が実行されるべき条件のところ)には \prg_return_true: を,偽の分岐には \prg_return_false: を記述します.

さて休日料金判定に話を戻しますが,まず祝日とは無関係に金・土・日は祝日料金となります.「曜日を表す漢字1字」に展開される \jadayofweek の expl3 関数版 \bxjh_day_of_week_name:nnn もありますが,条件分岐に使う場合は「曜日を表す内部数値」に展開される \bxjh_day_of_week:nnn を利用した方が便利です.BXjaholiday で曜日を表す内部数値は次のように定義されています.

曜日 内部数値 expl3 の定数
0 \c_bxjh_monday_int
1 \c_bxjh_tuesday_int
2 \c_bxjh_wednesday_int
3 \c_bxjh_thursday_int
4 \c_bxjh_friday_int
5 \c_bxjh_saturday_int
6 \c_bxjh_sunday_int

したがって,金・土・日を判定するためには \bxjh_day_of_week:nnn で得られた数値が c_bxjh_thursday_int よりも大きいかどうかで判定するのが簡単です.

\prg_new_conditional:Npnn \my_if_holiday_price:nnn #1#2#3 { TF }
  {
    \int_compare:nNnTF
      { \bxjh_day_of_week:nnn {#1} {#2} {#3} } > { \c_bxjh_thursday_int }
      { \prg_return_true: }  % 金・土・日
      {
        % TODO: 月〜木は祝日の存在を考慮
      }
  }

続いて月〜木を考えます.月〜木はほとんどの場合平日料金が適用されますが,祝日および祝前日は休日料金となります.祝日の判定は \IfJaHolidayTF の expl3 関数版 \bxjh_if_holiday:nnnTF を用いて簡単です.

\bxjh_if_holiday:nnnTF {#1} {#2} {#3}
  { \prg_return_true: }  % 祝日
  {
    % TODO: 祝前日を判定
  }

鬼門は祝前日の判定ですが,これには \bxjh_apply_next_day:Nnnn 関数が便利です.この関数の書式はちょっとクセがあるのですが,次のようになっています:

\bxjh_apply_next_day:Nnnn <function> {<year>} {<month>} {<day>}

ここで <function> は日付を {<year>}{<month>}{<day>} の形で取る任意の関数です.例えば \bxjh_day_of_week:nnn\bxjh_if_holiday:nnnTF は該当します.\bxjh_apply_next_day:Nnnn を用いると,第1引数に指定した関数を,第2〜第3引数で指定した日付の「翌日」に適用することができます.つまり,次のように記述することで指定した日付の「翌日」が祝日かどうかを判定することができます.

\bxjh_apply_next_day:Nnnn \bxjh_if_holiday:nnnTF {#1} {#2} {#3}
  { \prg_return_true: }  % 祝前日
  { \prg_return_false: }  % 平日

ここまでのコードを1つにまとめれば,求めていた関数の定義になります.

\prg_new_conditional:Npnn \my_if_holiday_price:nnn #1#2#3 { TF }
  {
    \int_compare:nNnTF
      { \bxjh_day_of_week:nnn {#1} {#2} {#3} } > { \c_bxjh_thursday_int }
      { \prg_return_true: }  % 金・土・日
      {
        \bxjh_if_holiday:nnnTF {#1} {#2} {#3}
          { \prg_return_true: }  % 祝日
          {
            \bxjh_apply_next_day:Nnnn \bxjh_if_holiday:nnnTF {#1} {#2} {#3}
              { \prg_return_true: }  % 祝前日
              { \prg_return_false: }  % 平日
          }
      }
  }

まとめ

TeX でカレンダーを作る際や請求書・納品書等々の自動生成を行う際は,ぜひ BXjaholiday を活用してみてください!


  1. 要するに expl3 がサポート対象とするエンジンすべてです.expl3 が e-TeX 拡張を要求するため,e-TeX 拡張は必須です. [return]
  2. TeX 言語者向けの情報:\jaholidayname は先頭完全展開可能です. [return]
  3. ちなみにですが「体育の日」という名前の祝日は今年を最後に姿を消す予定です.以降は「スポーツの日」という名称になるそうです.さらに来年限定でスポーツの日は東京オリンピック開会式の行われる7月24日に移動されるらしいです.ちょっと意味がわかりませんが,BXjaholiday はこのイレギュラーにも対応しています. [return]
  4. TeX 言語者向けの情報:\jadayofweek も先頭完全展開可能です. [return]
  5. TeX 言語者向けの情報:\IfJaHolidayTF は先頭完全展開可能です.\IfJaHolidayT も先頭完全展開可能です.\IfJaHolidayF も先頭完全展開可(ry [return]
  6. TeX 言語者向けの情報:BXjaholiday の提供するすべての expl3 関数は先頭完全展開可能です. [return]
  7. 残念ながら,BXjaholiday のパッケージ文書は現状英語版しかありません.なぜなら l3doc クラスを日本語対応するのが面倒だからです(えっ) [return]