年号変換プログラム(OCaml)

OCamlの勉強にと、プログラムをつくってみた。
年号も令和となったことだし、年号変換プログラムをつくることにした。
で、せっかくだし、江戸時代の年号も西暦と変換可能にしてみた。
これで、赤穂浪士の吉良邸討ち入りの元禄15年は、1702年だとわかる。

$ ./yearx -g
和暦を入力(例:元禄14)> 元禄15
西暦 1702年

(しかし、旧暦の12月14日なので、新暦にすると1703年1月末日になるのかな?)
長谷川平蔵が火付け盗賊改方長官となったのは、天明7年。西暦にすると1787年。

$ ./yearx -g
和暦を入力(例:元禄14)> 天明7
西暦 1787年

架空だが中村主水が仕事をしていたのは文政ごろ。文政3年で1820年

$ ./yearx -g
和暦を入力(例:元禄14)> 文政3
西暦 1820年

ヨーロッパでフランス革命が起こったのは、1789年。その時の江戸は…。

$ ./yearx -w
西暦を数字で入力 > 1789
寛政 元年

西暦から和暦を求めることもできるし、和暦から西暦を求めることができるようにした。

プログラム作成上のこと

西暦を元号に変換するときに真っ先に思いつくのは、if文を使うことである。

if (year > 1925 && year < 1988) then "昭和"

これは、昭和・平成・令和くらいならまだいいが、文禄以後の年号を扱うとなると、この方法では無理である。
今回は、OCamlの組(tuple)を使うことにした。連想配列みたいな使い方でやった。他にいい方法があるのかもしれない

(*
 * 年号変換プログラム ver 0.2
 *
 *     2019 Seiichi Nukayama
 *)
let ver = "0.2";;

(* ware = true --> グレゴリオ暦から和暦を求める処理
 * gre  = true --> 和暦からグレゴリオ暦を求める処理 *)
let ware = ref false
and gre = ref false;;

(* 和暦の換算リスト *)
let wareki_list =
    [ ("文禄", 1591); ("慶長", 1594); ("元和", 1614);
      ("寛永", 1623); ("正保", 1643); ("慶安", 1647);
      ("承応", 1651); ("明暦", 1654); ("万治", 1657);
      ("寛文", 1660); ("延宝", 1672); ("天和", 1680);
      ("貞享", 1683); ("元禄", 1687); ("宝永", 1703);
      ("正徳", 1710); ("享保", 1715); ("元文", 1735);
      ("寛保", 1740); ("延享", 1743); ("寛延", 1747);
      ("宝歴", 1750); ("明和", 1763); ("安永", 1771);
      ("天明", 1780); ("寛政", 1788); ("享和", 1800);
      ("文化", 1803); ("文政", 1817); ("天保", 1829);
      ("弘化", 1843); ("嘉永", 1847); ("安政", 1853);
      ("万延", 1859); ("文久", 1860); ("元治", 1863);
      ("慶応", 1864); ("明治", 1866); ("大正", 1911);
      ("昭和", 1925); ("平成", 1988); ("令和", 2018);
      ("未来", 2100) ];;

(*
 * 和暦からグレゴリオ暦を求める
 * @param: nengo -- (string) たとえば "元禄14" などの文字列
 *         l     -- (list)   wareki_list を想定している
 * @return: suuji + num -- (int) gregorian year
 *)
let rec gregorio nengo l = 
    let nengo_len = String.length nengo in
    let moji = String.sub nengo 0 6 in
    let suuji = String.sub nengo 6 (nengo_len - 6) in
    let num = int_of_string suuji in
    match l with
    [] -> 0
    | (gengo, suuji) :: rest ->
            if moji = gengo then suuji + num
            else gregorio nengo rest;;
            
(*
 * グレゴリオ暦から和暦を求める
 * @param:  year -- (int) ex.1985 
 * @return:      -- ex.("昭和", 31)
 *)
let this_gengo = ref "" and this_year = ref 0;;

let rec wareki year = function
    [] -> ("", 0)
    | (gengo, nen) :: rest ->
        if nen < year 
        then
            (this_gengo := gengo;
            this_year := nen;
            wareki year rest)
        else
            (!this_gengo, year - !this_year);;

  
(*
 * コマンドライン引数処理
 * Arg.parse に渡す第1引数 spec の中身
 *)
let spec = [("-w", Arg.Set ware, "グレゴリオ暦(西暦) -> 和暦");
            ("-wareki" , Arg.Set ware, "グレゴリオ暦(西暦) -> 和暦");
            ("-g", Arg.Set gre, "和暦 -> グレゴリオ暦(西暦)");
            ("-gregorian", Arg.Set gre, "和暦 -> グレゴリオ暦(西暦)");
            ("-version",
            Arg.Unit (fun () -> Printf.printf "年号変換 ver: %s\n" ver),
            "Display version number")];;

(*
 * 画面処理 -- wareki 関数を呼び出す
 *)
let gre_wareki () =
    print_string "西暦を数字で入力 > ";
    flush stdout;
    let line = input_line stdin
    and y = ref "" in
    let year = int_of_string line in
    let (nengo, nen) = wareki year wareki_list in
    if nen = 1 then  y := "元" else y := string_of_int nen;
    print_endline (nengo ^ " " ^ !y ^ "年");;

(*
 * 画面処理 -- gregorio 関数を呼び出す
 *)
let wareki_gre () =
    print_string "和暦を入力(例:元禄14)> ";
    flush stdout;
    let wa_year = input_line stdin in
    let gre_year = string_of_int (gregorio wa_year wareki_list) in
    print_endline ("西暦 " ^ gre_year ^ "年");;

(*
 * 引数が何も指定されなかった場合、これを画面に表示する
 *)
let pr_help () =
    print_endline "yearx の使い方";
    print_endline "yearx [-w | -wareki] [-g | -gregorian]";
    print_endline "yearx [-version] [-help | --help]";
    print_newline ();
    print_endline "オプション";
    print_string "-w | -wareki   ";
    print_endline "  グレゴリオ暦(西暦)-> 和暦";
    print_string "-g | -gregorian";
    print_endline "  和暦 -> グレゴリオ暦(西暦)";
    print_newline ();
    print_endline "    Copyright 2019 Seiichi Nukayama";
    flush stdout;;

(*
 * コマンド実行
 *)
let _ =
    Arg.parse spec
    (* 引数が何もなかった場合 *)
    (fun y -> if y = "" then pr_help() else ())
    (* 想定外の引数、あるいはエラーの場合 *)
    "Usage: yearx [-w] [-wareki] [-g] [-gregorian] [-version] ";
    if !ware then gre_wareki ()  (* !ware = true *)
    else
        if !gre then wareki_gre () else ();  (* !gre = true *)
    if (Array.length Sys.argv) = 1 then pr_help ();;