見出し画像

旧暦正月だから年号電卓を作ってみた

今まで一度も出会ったことのなかった西暦から和暦に変換をする必要がありそうな局面。プログラミングに馴染みのない人からは「え、そんなこと簡単にできそうじゃん」と思われるかもしれませんが、結構難しいです。

なぜならプログラミング言語の大半は日本で生まれていないので、日本の環境・文化・慣習に最適化されている訳ではありません。ましてや元号を採用している国は日本のみで、お隣の韓国や中国でも現在は元号を採用していないのです。日本だけがいまだに元号を採用している背景については本筋から逸れるので触れません。

それだけではありません。
現代では当たり前に確認できる「時間」という概念は歴史の中で数多の努力が注がれて形成された賜物です。僕自身もプログラミングを触るまで気づかなかったですが、時間の計算は1+1=2みたいな簡単な計算ではありませんでした。まだまだ時間をシンプルに扱うための人類の努力は続いているんだなと感じます。

最近の年号変換に関してはググれば秒で見つかりましたが、歴史を学んでいた身・・・これで満足していいのか?という自問が湧いてきた。より厳密な年号電卓を作ってみたい。(わざわざ作らなくても便利なサイトはあるんだけど・・・)

ただ、史料に記載されている日付に厳密にしようとすると暦の問題にぶち当たり無理ゲーなので、あくまでも現在利用されているグリゴレオ暦に厳密にしたいと思います。

暦はその時その時で使っているものが違うので日付の算出方法が微妙に違います。「旧暦 = 天保暦」の前提で進めます。
天保14年までは寛政暦、寛政9年までは宝暦暦、宝暦4年までは貞享暦、貞享元年以前は唐の時代に作られた宣明暦と、江戸時代だけでも4回の暦の改定が行われています。

宣明暦と天保暦では1日〜2日のズレが生じているので、1844年以前ではグレゴリオ暦を旧暦変換すると歴史上の日付と1日ほどズレる可能性があります。

ちなみに旧暦では京都時間 = 日本時間なので、世界標準時との時差は現在の日本時間より3分早いGMT+0903らしい。へぇー。

1. 元号対照表の作成

●各年号が始まった時間を”求める”

現在の年号「令和」が始まったのは2019年5月1日 0時00分。
令和が始まった時間がわかると、同時に「平成」が終わった時間もわかります。年号の切り替えに隙間時間はないから2019年4月30日 23時59分59秒までが平成ということになります。

この考え方をベースに、入力された時間が各年号の開始時間より早いか遅いかを1つずつ突合していけば西暦→和暦の変換ができそうだと考えました。

時間の突合にはミリ秒(ms)を利用します。
ミリ秒で突合するために、下記のようなデータベースを事前に作成することにしました。
※JavaScriptのDateオブジェクトは1970年1月1日 0時00分00.000秒(世界標準時) = 0ms
日本時間では1970年1月1日 9時00分 0時00分00.000秒(GMT+09)= 0ms

var array = [
  {
    name: "平成", 
    eng: "H",
    start: 600188400000,  //1989年1月8日 0時00分00.000秒 GMT+0900
    end: 1556636400000    //2019年5月1日 0時00分00.000秒 GMT+0900(令和が始まった時間)
  },
  {
    name: "昭和",
    eng: "S",
    start: -1357635600000,//1926年12月25日 0時00分00.000秒 GMT+0900
    end: 600188400000     //1989年1月8日 0時00分00.000秒 GMT+0900
  }
];

入力された時間が、
・改元より後
・次の元号への改元より早い
どちらにも該当した場合のみ年号が出力されます。

/*年号の確認*/
var d = new Date("2019/12/29");                   //入力時間:2019/12/29
var ms = d.getTime();                             //入力時間をミリ秒へ変換

var y, nengo;
for(var i = 0; i < array.length; i++){
  if(ms >= array[i].start && ms < array[i].end){  //一致年号を探す条件式
    y = new Date(array[i].start).getFullYear()-1; //改元の年 - 1
    nengo = array[i][key] + (d.getFullYear - y) + "年"; //2019 - 2018 = 1  
  }
}
console.log(nengo);
//結果:"令和1年"

●旧暦/新暦の差

データベースを作るに際して令和、平成、昭和、大正までは現在の新暦(太陽暦/グレゴリオ暦)カレンダー通りなので特に考えることはありません。コピペでOK。問題なのは明治からです。

明治が終わった1912年7月30日は新暦の日付なので良いとして。
明治が始まった1868年9月8日は旧暦(天保暦)での日付になっているので、このままJavaScriptでnew Date("1868/9/8")を読み込んでミリ秒を取ると、新暦の1868年9月8日として時間が算出されてしまい厳密な変換ができなくなります。新暦の1868年9月8日は旧暦に直すとまだ慶応4年なので「慶応4年」が出力されるのが理想です。

※日本が新暦に切り替えたのは明治6年1月1日から(旧暦は明治5年12月2日まで)

(旧暦)1868年9月8日 → (新暦)1868年10月23日

旧暦の日付を新暦の日付に直して算出することでJavaScriptでも厳密な時間を算出できます。この旧暦→新暦の部分はWikipediaにも記載してあるので江戸時代の年号までは悩むことはなかったです。

//旧暦1868年9月8日時点でのミリ秒が取得される
var time = new Date("1868/10/23").getTime();
console.log(time)
/*結果:-3193291139000*/

●ユリウス暦/グレゴリオ暦の差

ヨーロッパ世界でグレゴリオ暦を採用したのは1582年以降。それ以前にはユリウス暦という1年の平均日数が365.25日の旧暦を採用していました。4の倍数の年に閏年を設定してズレを修正する暦です。一方、グレゴリオ暦では1年の平均日数は365.2425日。よりズレが修正されたものになっています。

途中で気づいたけど、Wikipediaでは1582年以前の元号は日本の旧暦日付とユリウス暦の日付で併記されている。ということはグレゴリオ暦の日付に修正してから盛り込まないとズレが発生することになる。可能な限り厳密な電卓を作りたいのでデータベース側はすべてグレゴリオ暦の時間である必要があります。

境になるのはグレゴリオ暦が始まった1582年(天正10年)。
天正の改元日はユリウス暦の日付ということになる。下記参考。

天正以前の元号ではソースになるWikipedia記載の日付を、ユリウス暦→グレゴリオ暦(または旧暦→新暦)に変換してからデータベースに書き込む必要がでてきます。JavaScriptで作ってみてもいいかと思ったけどさすがにめんどくさいのでこちらのサイトで変換します。

●完成した年号一覧

era()で年号の一覧を呼び出し
function era(){
 return [
   {name: "令和", eng: "R",start: 1556636400000,end: 9999999999999},
   {name: "平成", eng: "H",start: 600188400000, end: 1556636400000},
   {name: "昭和", eng: "S",start: -1357635600000, end: 600188400000},
   {name: "大正", eng: "T",start: -1812186000000, end: -1357635600000},
   {name: "明治", eng: "M",start: -3193291139000, end: -1812186000000}, /*新暦10月23日*/
   {name: "慶応", eng: null,start: -3303105539000, end: -3193291139000},
   {name: "元治", eng: null,start: -3337924739000, end: -3303105539000},
   {name: "文久", eng: null,start: -3432187139000, end: -3337924739000},
   {name: "万延", eng: null,start: -3462859139000, end: -3432187139000},
   {name: "安政", eng: null,start: -3627883139000, end: -3462859139000},
   {name: "安政", eng: null,start: -3627883139000, end: -3462859139000},
   {name: "嘉永", eng: null,start: -3842155139000, end: -3627883139000},
   {name: "弘化", eng: null,start: -3943934339000, end: -3842155139000},
   {name: "天保", eng: null,start: -4384574339000, end: -3943934339000},
   {name: "文政", eng: null,start: -4784174339000, end: -4384574339000},
   {name: "文化", eng: null,start: -4784174339000, end: -4384574339000},
   {name: "享和", eng: null,start: -5326507139000, end: -4784174339000},
   {name: "寛政", eng: null,start: -5707531139000, end: -5326507139000},
   {name: "天明", eng: null,start: -5954375939000, end: -5707531139000},
   {name: "安永", eng: null,start: -6218587139000, end: -5954375939000},
   {name: "明和", eng: null,start: -6485131139000, end: -6218587139000},
   {name: "宝暦", eng: null,start: -6881015939000, end: -6485131139000},
   {name: "寛延", eng: null,start: -6986942339000, end: -6881015939000},
   {name: "延享", eng: null,start: -7123886339000, end: -6986942339000},
   {name: "寛保", eng: null,start: -7217803139000, end: -7123886339000},
   {name: "元文", eng: null,start: -7370731139000, end: -7217803139000},
   {name: "享保", eng: null,start: -7996439939000, end: -7370731139000},
   {name: "正徳", eng: null,start: -8159390339000, end: -7996439939000},
   {name: "宝永", eng: null,start: -8366059139000, end: -8159390339000},
   {name: "元禄", eng: null,start: -8873486339000, end: -8366059139000},
   {name: "貞享", eng: null,start: -9017083139000, end: -8873486339000},
   {name: "天和", eng: null,start: -9092942339000, end: -9017083139000},
   {name: "延宝", eng: null,start: -9346267139000, end: -9092942339000},
   {name: "寛文", eng: null,start: -9738782339000, end: -9346267139000},
   {name: "万治", eng: null,start: -9825700739000, end: -9738782339000},
   {name: "明暦", eng: null,start: -9928603139000, end: -9825700739000},
   {name: "承応", eng: null,start: -10009819139000, end: -9928603139000},
   {name: "慶安", eng: null,start: -10152983939000, end: -10009819139000},
   {name: "正保", eng: null,start: -10254935939000, end: -10152983939000},
   {name: "寛永", eng: null,start: -10909502339000, end: -10254935939000},
   {name: "元和", eng: null,start: -11181403139000, end: -10909502339000},
   {name: "慶長", eng: null,start: -11772119939000, end: -11181403139000},
   {name: "文禄", eng: null,start: -11896190339000, end: -11772119939000},
   {name: "天正", eng: null,start: -12506865539000, end: -11896190339000}, //スタート時間がユリウス暦
   {name: "元亀", eng: null,start: -12609335939000, end: -12506865539000},
   {name: "永禄", eng: null,start: -12994075139000, end: -12609335939000},
   {name: "弘治", eng: null,start: -13068551939000, end: -12994075139000},
   {name: "天文", eng: null,start: -13800359939000, end: -13068551939000},
   {name: "享禄", eng: null,start: -13926158339000, end: -13800359939000},
   {name: "大永", eng: null,start: -14145355139000, end: -13926158339000},
   {name: "永正", eng: null,start: -14698315139000, end: -14145355139000},
   {name: "文亀", eng: null,start: -14792836739000, end: -14698315139000},
   {name: "明応", eng: null,start: -15064132739000, end: -14792836739000},
   {name: "延徳", eng: null,start: -15155803139000, end: -15064132739000},
   {name: "長享", eng: null,start: -15222244739000, end: -15155803139000},
   {name: "文明", eng: null,start: -15795595139000, end: -15222244739000},
   {name: "応仁", eng: null,start: -15863937539000, end: -15795595139000},
   {name: "文正", eng: null,start: -15897719939000, end: -15863937539000},
   {name: "寛正", eng: null,start: -16090651139000, end: -15897719939000},
   {name: "長禄", eng: null,start: -16163054339000, end: -16090651139000},
   {name: "康正", eng: null,start: -16229668739000, end: -16163054339000},
   {name: "享徳", eng: null,start: -16326609539000, end: -16229668739000},
   {name: "宝徳", eng: null,start: -16420785539000, end: -16326609539000},
   {name: "文安", eng: null,start: -16593671939000, end: -16420785539000},
   {name: "嘉吉", eng: null,start: -16686983939000, end: -16593671939000},
   {name: "永享", eng: null,start: -17047790339000, end: -16686983939000},
   {name: "正長", eng: null,start: -17089262339000, end: -17047790339000},
   {name: "応永", eng: null,start: -18157684739000, end: -17089262339000},
   {name: "明徳", eng: null,start: -18293591939000, end: -18157684739000},
   /* 南朝元号 */
   {name: "元中(南朝)", eng: null,start: -18479783939000, end: -18293591939000},
   {name: "弘和(南朝)", eng: null,start: -18580785539000, end: -18479783939000},
   {name: "天授(南朝)", eng: null,start: -18760497539000, end: -18580785539000},
   {name: "文中(南朝)", eng: null,start: -18859943939000, end: -18760497539000},
   {name: "建徳(南朝)", eng: null,start: -18913857539000, end: -18859943939000},
   {name: "興国(南朝)", eng: null,start: -19867713539000, end: -19657675139000},
   {name: "延元(南朝)", eng: null,start: -19997745539000, end: -19867713539000},
   {name: "建武(南朝/北朝)", eng: null,start: -20064100739000, end: -19997745539000},
   {name: "元弘(南朝)", eng: null,start: -20142379139000, end: -20064100739000},
   /* 北朝元号 */
   {name: "康応(北朝)", eng: null,start: -18328238339000, end: -18293591939000},
   {name: "嘉慶(北朝)", eng: null,start: -18373079939000, end: -18328238339000},
   {name: "至徳(北朝)", eng: null,start: -18484967939000, end: -18373079939000},
   {name: "永徳(北朝)", eng: null,start: -18579575939000, end: -18484967939000},
   {name: "康暦(北朝)", eng: null,start: -18641006339000, end: -18579575939000},
   {name: "永和(北朝)", eng: null,start: -18768187139000, end: -18641006339000}

   /*ここから先は暇な時に追加する*/

 ];
}

2.旧暦⇆新暦の変換

西暦から和暦に変換するにあたって壁になるのが、入力された年月日を旧暦の日付に直して返すのか、そのままの新暦日付で返すのか?という点。

●新暦→旧暦の変換

旧暦に変換するサンプルはないものかとあれこれと探していたところ上記のライブラリを発見しました。これ作った人やべぇなと思うような精巧な作りになっていて感動しました。

●旧暦の仕組み

旧暦に馴染みのない人が大半だと思いますが、旧暦は「太陽太陰暦」と呼ばれます。一言で表現するならば「月の満ち欠けで日付を取る」暦です。

--- 【朔望周期】 ---

「1ヶ月」という言葉も月の満ち欠けに由来しています。新月から晦(つごもり)になるまでを1ヶ月として、新月の日を「朔日(さくじつ/ついたち)」と呼びます。朔日の1日前を「晦日(みそか)」と呼びます。ちなみに半月は「弦月(げんげつ)」、満月は「望月(ぼうげつ/もちづき)」と呼びます。

1ヶ月は、新月→上弦→望月→下弦→晦の周期(朔望周期)を示したものということになります。

--- 【小月/大月】 ---

朔望周期は平均で29.530589日なので、12ヶ月で約354日。
29日の月(小月)と30日の月(大月)を交互に繰り返すことで1年をうまく埋めることができるのですが、毎月0.030589日分のズレが生じてしまい、どこかに大月→大月や小月→小月と連続する月を作ることで調整します。

現代のカレンダーでは「2,4,6,9,11」と小月は決まっていますが、旧暦では毎年調整が行われるので小月と大月は固定されていません。大変ですね。

毎年調整を行なっていたのが幕府の天文方と朝廷の陰陽寮・土御門家。江戸時代では暦を作成することが1つの重要な仕事になっていました。一昔前に冲方丁さんの『天地明察』が有名になりましたが、その世界観です。

--- 【閏月の設定】 ---

旧暦では閏月が存在します。先に書いたように旧暦では1年が354日なので、新暦と11日ズレが生じます。閏月とは1年のズレを修正するもので、閏月がある年は1年間が13ヶ月になります。

閏月は各月の二十四節気の中気のズレによって差し込まれる月が決まります。二十四節気は1太陽年を24分割したもので各月に照合する節気と中気が存在します。節気(立春や啓蟄など)は朔日の近辺、中気(春分や夏至など)は1ヶ月に1つ入る必要があります。毎年11日ずつズレるとどこかで中気よりも日付が先に進む月や中気を含まない月が発生するので、そこに閏月を設定することで翌月以降の中気に対して修正を行います。

二十四節気の確認は地球が太陽を1周する周期を単純に24分割する訳ではなく、太陽の黄道周期に合わせて調整が行われます。だいたい15日ごとに節気と中気は配置されるけど、地球が太陽の周りを周回するスピードは必ずしも一定ではないのでそれぞれの節気・中気に合わせて24分割される塩梅が変化します。

以下、参考

サクッと旧暦の難しさをまとめるとこんな感じです。
これをJavaScriptで再現するのは正気の沙汰じゃねぇ・・・

●旧暦ライブラリを用いた結果

var day = new Date(2019,5,29);
var jd = d.getJD();
console.log(new kyureki(jd));
/*結果:{
  magenoon : 25.70537236891687, 
  mage : 25.20537236891687, 
  month : 5.0, 
  year : 2019, 
  mphase : 24, 
  illumi : 20.02236997670665, 
  rokuyo : "先勝", 
  uruu : false, 
  day : 27
}*/

年月日だけではなく、閏月、六曜、月齢、月の輝面比も計算してくれます。正気の沙汰とは思えませんが、ただただすげぇぇ・・・先人の努力はありがたい。

●旧暦→新暦の変換

せっかくだし、旧暦から新暦への変換もできないかなと思ったけど、さすがに難しすぎて心が折れた。。。

3.完成した年号電卓(粗め)

そんなこんな作ったのはこちら
・西暦→和暦への変換。japaneseYear(年,月,日,詳細)の形で呼び出し可能。
 詳細 = {type : true}で旧暦に変換
    = {lang : true}で年月日じやなくてスラッシュで繋ぐ
    = {type: true, lang : true}でどっちも適用

・元号→西暦への変換。parseWesternCalendar(元号, 年)で呼び出し可能
 ※本当はここに旧暦→新暦への変換もつけたかった・・・

//////////西暦→和暦への変換//////////
function japaneseYear(year, month, date, option){
 var type = option.type, lang = option.lang;
 var nengo, other, ms, array = era(), result = [];
 var n = lang ? {y:"/",m:"/",d:"/"} : {y:"年",m:"月",d:"日"};
 
 /*日付部分の作成*/
 var d        = new Date([year,month,date].join("/"));
 var days     = [year, month, date];
 if(type)days = qurekiMonthAndDate(d);
     other    = days[1] + n.m + days[2] + n.d;
 
 /*年号の確認*/
 ms = d.getTime();
 for(var i=0;i<array.length;i++){
   if(ms >= array[i].start && ms < array[i].end){
     var key, y, eng = array[i].eng;
         key   = !!lang && eng != null? "eng" : "name";
         y     = new Date(array[i].start).getFullYear()-1;
         nengo = array[i][key] + (days[0] - y) + n.y;
     result.push(nengo.replace(/(?<=\d)1(?=年)/,"元"));
   }
 }
 return result.join("/") + other;
}

//////////元号→西暦年への変換//////////
function parseWesternCalendar(eraName,year){
var y = parseInt(year), array = era(), result = 0;
for(var i=0;i<array.length;i++){
  if(array[i].name === eraName)
  result = new Date(array[i].start).getFullYear() + y;
}
if(!result)result = new Error("失敗");
return result;
}

//////////新暦→旧暦への変換//////////
function qurekiMonthAndDate(obj){
 var dd, qreki;
     dd    = obj.getJD();
     qreki = new kyureki(dd);
 if(!!qreki.uruu)qreki.month = "閏" + qreki.month;
 return [qreki.year , qreki.month , qreki.day];
}

4.さいごに

旧暦正月の今日は2020年初の新月の日です。
東京に住んでいると空を見ないから月の満ち欠けも全く気にしなくなったけど、たまに空を見上げて月を眺めてみるのも良いのではないでしょうか。

以上、誰も得しない約1万3000字のnote。ここまで読む人いるのだろうか・・・

この記事が気に入ったらサポートをしてみませんか?