六曜のカレンダーを表示する(書きかけ)

六曜の結果なのだが、今月は正確に表示されないらしい。
見直す羽目になる。

http://jsdo.it/babu_baboo/EP6b

<!DOCTYPE html>
<meta charset="utf-8">
<title>六曜を計算</title>
<style>
caption { font-size: x-large; text-align: center; }
caption label { margin: 0 1em }
th { height: 2em; background: #aea; }
td { height: 3em; width: 3em; vertical-align: top; background: #ffd; }
td:nth-of-type(1)   { background: #fdd; color: #c00; }
td:nth-of-type(7) { background: #eef; color: #008; }
td.prev, td.next { color: #ccc; }
</style>


<body>
<table border="1">
  <caption>
    <input type="button" value="←" id ="PREV_MONTH">
    <label></label>
    <input type="button" value="→" id ="NEXT_MONTH">
  </caption>
  <thead><tr><th>日<th>月<th>火<th>水<th>木<th>金<th>土
  <tbody>
</table>

<script>

"use strict"
// 六曜を返す

{
  const
    abs = Math.abs,
    deg = Math.PI / 180,
    int = num => num > 0 ? Math.floor (num): Math.ceil (num),
    sum = (a, b) => a + b,
    COS = t => Math.cos (NORMALIZATION_ANGLE (t) * deg),
    TMZ = 9 / 24,
    TM_LIMIT = 1 / 86400,

    ROKUYOU_STR_JP = ['大安', '赤口', '先勝', '友引', '先負', '仏滅'],

    SEKKI24_STR_JP = [
      '春分', '清明', '穀雨', '立夏', '小満', '芒種',
      '夏至', '小暑', '大暑', '立秋', '処暑', '白露',
      '秋分', '寒露', '霜降', '立冬', '小雪', '大雪',
      '冬至', '小寒', '大寒', '立春', '雨水', '啓蟄'
    ],

    LONGITUDE_SUN_PARAMETER = [
      [ 0.0004,  31557, 161], [ 0.0004,  29930,  48], [ 0.0005,   2281, 221], [ 0.0005,    155, 118],
      [ 0.0006,  33718, 316], [ 0.0007,   9038,  64], [ 0.0007,   3035, 110], [ 0.0007,  65929,  45],
      [ 0.0013,  22519, 352], [ 0.0015,  45038, 254], [ 0.0018, 445267, 208], [ 0.0018,     19, 159],
      [ 0.0020,  32964, 158], [ 0.0200,  71998, 265], [-0.0048, 35999.05, 267.52], [1.9147, 35999.05, 267.52]
    ],

    LONGITUDE_MOON_PARAMETER = [
      [0.0003, 2322131, 191], [0.0003,    4067,  70], [0.0003,  549197, 220], [0.0003, 1808933,  58],
      [0.0003,  349472, 337], [0.0003,  381404, 354], [0.0003,  958465, 340], [0.0004,   12006, 187],
      [0.0004,   39871, 223], [0.0005,  509131, 242], [0.0005, 1745069,  24], [0.0005, 1908795,  90],
      [0.0006, 2258267, 156], [0.0006,  111869,  38], [0.0007,   27864, 127], [0.0007,  485333, 186],
      [0.0007,  405201,  50], [0.0007,  790672, 114], [0.0008, 1403732,  98], [0.0009,  858602, 129],
      [0.0011, 1920802, 186], [0.0012, 1267871, 249], [0.0016, 1856938, 152], [0.0018,  401329, 274],
      [0.0021,  341337,  16], [0.0021,   71998,  85], [0.0021,  990397, 357], [0.0022,  818536, 151],
      [0.0023,  922466, 163], [0.0024,   99863, 122], [0.0026, 1379739,  17], [0.0027,  918399, 182],
      [0.0028,    1934, 145], [0.0037,  541062, 259], [0.0038, 1781068,  21], [0.0040,     133,  29],
      [0.0040, 1844932,  56], [0.0040, 1331734, 283], [0.0050,  481266, 205], [0.0052,   31932, 107],
      [0.0068,  926533, 323], [0.0079,  449334, 188], [0.0085,  826671, 111], [0.0100, 1431597, 315],
      [0.0107, 1303870, 246], [0.0110,  489205, 142], [0.0125, 1443603,  52], [0.0154,   75870,  41],
      [0.0304,  513197.9, 222.5], [0.0347,  445267.1,  27.9], [0.0409,  441199.8,  47.4], [0.0458,  854535.2, 148.2],
      [0.0533, 1367733.1, 280.7], [0.0571,  377336.3,  13.2], [0.0588,   63863.5, 124.2], [0.1144,  966404.0, 276.5],
      [0.1851,   35999.05, 87.53], [0.2136,  954397.74, 179.93], [0.6583,  890534.22, 145.7 ],
      [1.2740, 413335.35,  10.74], [6.2888, 477198.868, 44.963]
    ],


//_________

    //六曜を数値で返す
    get_rokuyou = (date) => {
      let [,, m, d] = calc_kyureki (date);
      return (m + d) % 6; //0:大安 1:赤口 2:先勝 3:友引 4:先負 5:仏滅
    },


//_________

    //六曜を漢字文字列で返す
    get_rokuyou_kanji = (date) => {
      return ROKUYOU_STR_JP[get_rokuyou (date)];
    },


//_________

    //新暦に対応する、旧暦を求める。戻値: [0:旧暦年, 1: 0|1 (平月/閏月), 2:旧暦月, 3:旧暦日]
    calc_kyureki = (date) => {
      let
        kyureki = [ ], saku = [ ], chu = [ ],
        /*----------------------------------------------------------------------
        * 計算対象の直前にあたる二分二至(夏至・冬至・春分・秋分)の時刻を求める
        * chu[0,0]:二分二至の時刻  chu[0,1]:その時の太陽黄経(Sun yellow through)
        *---------------------------------------------------------------------*/
        tm0 = YMDT2JD (date),
        int_tm0 = int (tm0);

      // 中気の時刻を計算(4回計算する) chu[i,0]:中気の時刻  chu[i,1]:太陽黄経
      let [time] = chu[0] = before_nibun (tm0);
      for (let i = 1; i < 4; i++)
        [time] = chu[i] = calc_chu (time + 32);


      //計算対象の直前にあたる二分二至の直前の朔の時刻を求める
      let tm = saku[0] = calc_saku (chu[0][0]);
      for (let i = 1; i < 5; i++) {
        let s = calc_saku (tm + 30);
        tm = saku[i] = (abs (int (tm) - int (s)) <= 26) ? calc_saku (tm + 35): s;
        //前と同じ時刻を計算した場合(両者の差が26日以内)には、初期値を+33日にして再実行させる。
      }

      /*----------------------------------------------------------------------
      # saku[1]が二分二至の時刻以前になってしまった場合には、朔をさかのぼり過ぎ
      # たと考えて、朔の時刻を繰り下げて修正する。
      # その際、計算もれ(saku[4])になっている部分を補うため、朔の時刻を計算
      # する。(近日点通過の近辺で朔があると起こる事があるようだ...?)
      #---------------------------------------------------------------------*/
      if (int (saku[1]) <= int (chu[0][0])) {
        saku.shift ();
        saku.push (calc_saku (saku[3] + 35));//配列の先頭を削除し最後に追加
      }
      /*----------------------------------------------------------------------
      # saku[0]が二分二至の時刻以後になってしまった場合には、朔をさかのぼり足
      # りないと見て、朔の時刻を繰り上げて修正する。
      # その際、計算もれ(saku[0])になっている部分を補うため、朔の時刻を計算
      # する。(春分点の近辺で朔があると起こる事があるようだ...?)
      #---------------------------------------------------------------------*/
      else if (int (saku[0]) > int (chu[0][0])) {
        saku.pop ();
        saku.unshift (calc_saku (saku[0] - 27)); //配列の最後を削除し先頭に追加
      }

      /*----------------------------------------------------------------------
      # 閏月検索Flagセット
      # (節月で4ヶ月の間に朔が5回あると、閏月がある可能性がある。)
      # lap=0:平月  lap=1:閏月
      #---------------------------------------------------------------------*/
      let
        lap = (int (saku[4]) <= int (chu[3][0])) ? 1: 0,
        /*----------------------------------------------------------------------
        * 朔日行列の作成
        * m[i,0] ... 月名(1:正月 2:2月 3:3月 ....)
        * m[i,1] ... 閏フラグ(0:平月 1:閏月)
        * m[i,2] ... 朔日のjd
        *---------------------------------------------------------------------*/
        bm = int (chu[0][1] / 30) + 2,
        m = [[bm, 0, int (saku[0])]];

      for (let i = 1; i < 5; i++) {
        if (lap == 1 && i != 1) {
          if (int (chu[i - 1][0]) <= int (saku[i - 1]) || int (chu[i - 1][0]) >= int (saku[i])) {
            m[i - 1] = [m[i - 2][0], 1, int (saku[i - 1])];
            lap = 0;
          }
        }
        [bm] = m[i] = [bm % 12 + 1, 0, int (saku[i])]; //bm = m[i][0];
      }

      // 朔日行列から旧暦を求める。
      let M;
      for (let cnt = 0; cnt < 5; cnt += 1) {
        M = m[cnt];
        let m2 = M[2];
        if (int_tm0 < m2) {
          M = m[cnt - 1];
          break;
        }
        if (int_tm0 == m2) {
          break;
        }
      }
      if (! M) throw new Error ();

      let
        [qYear, month] = JD2YMDT (tm0),
        [qMonth, qMuru, sakuDay] = M,
        qDay   = int (tm0) - int (sakuDay) + 1;

      //旧暦年の計算(旧暦月が10以上でかつ新暦月より大きい場合には、まだ年を越していないはず...)
      if (qMonth > 9 && qMonth > month)
        qYear -= 1;

      return [qYear, qMuru, qMonth, qDay];
    },


//_________

    // 中気の時刻を求める
    calc_chu = tm => { // tm = ユリウス日
      let rm_sun = getSunYellowThrough (tm);//λsun 黄経を計算
      return correctionTime (tm, rm_sun, 30 * int (rm_sun / 30));//中気の黄経 λsun0
    },


//_________

    // 直前の二分二至の時刻を求める
    before_nibun = tm => { //ユリウス日
      let rm_sun = getSunYellowThrough (tm);//λsun 黄経を計算
      return correctionTime (tm, rm_sun, 90 * int (rm_sun / 90));
    },

//_________

    //時間補正
    correctionTime = (tm, rm_sun, rm_sun0) => {
      let
        tm1 = int (tm), //時刻引数を分解する
        tm2 = tm - tm1 - TMZ, //JST ==> DT (補正時刻=0.0sec と仮定して計算)
        dt, dt1, dt2;

      do {
        [dt, dt1, dt2] = angleCorrection (rm_sun - rm_sun0);//黄経から時刻の補正
        tm1 -= dt1;
        tm2 -= dt2;

        if (tm2 < 0) {
          tm2 += 1;
          tm1 -= 1;
        }

        rm_sun = getLongitudeSun (tm1, tm2);//λsun を計算
      } while (dt > TM_LIMIT); // 直前の二分二至の時刻を計算する(誤差が±1.0 sec以内になったら打ち切る。)
      return [tm1 + tm2 + TMZ, rm_sun0]; // nibun[0:二分二至の時刻, 1:その時の黄経]
    },

    //角度の補正
    angleCorrection = angle => {
      angle += (angle > 180) ? -360: (angle < -180) ? 360: 0;//角度の範囲を補正(±180°)
      let t0 = angle * 365.2 / 360;

      return [abs (t0), int (t0), t0 % 1];
    },

    //λsun を計算
    getLongitudeSun = (tm1, tm2) => LONGITUDE_SUN ((tm1 + tm2 + 0.5 - 2451545) / 36525),


//_________

    // 与えられた時刻の直近の朔の時刻(JST)計算  戻り値:ユリウス日で表し、時分秒は日の小数
    calc_saku = (tm) => {//ユリウス日
      let
        lc = 1, //ループカウンタのセット
        tm1 = int (tm), //時刻引数を分解する
        tm2 = tm - tm1 - TMZ, //JST ==> DT (補正時刻=0.0sec と仮定して計算)
        t0;

      //朔の時刻を計算する(誤差が±1.0 sec以内になったら打ち切る)
      do {
        // ループ回数が15回になったら、初期値 tm を tm-26 とする。
        if (lc === 15) {
          tm1 = int (tm - 26);
          tm2 = 0;
        }

        let
          t = (tm1 + tm2 + 0.5 - 2451545) / 36525,
          rm_sun = LONGITUDE_SUN (t),   //太陽の黄経λsun
          rm_moon = LONGITUDE_MOON (t), //月の黄経λmoon を計算
          delta_rm = rm_moon - rm_sun;  //月と太陽の黄経差Δλ (Δλ=λmoon−λsun)

        if (lc === 1 && delta_rm < 0 )  //ループの1回目(lc=1)で delta_rm < 0.0 の場合には引き込み範囲に補正
          delta_rm = NORMALIZATION_ANGLE (delta_rm);

        else if (0 <= rm_sun && rm_sun <= 20 && 300 <= rm_moon) //春分の近くで朔がある場合(0 ≦λsun≦ 20)で、月の黄経λmoon≧300 の場合には、Δλ= 360.0 − Δλ と計算して補正
          delta_rm = 360 - NORMALIZATION_ANGLE (delta_rm);

        else if (abs (delta_rm) > 40)   //Δλの引き込み範囲(±40°)を逸脱した場合には、補正を行う
          delta_rm = NORMALIZATION_ANGLE (delta_rm);

        // 時刻引数の補正 (tm -= delta_t)
        t0 = delta_rm * 29.530589 / 360;//時刻引数の補正値 Δt
        tm1 -= int (t0);
        tm2 -= t0 % 1;
        if (tm2 < 0) {
          tm2 += 1;
          tm1 -= 1;
        }

        if (30 < lc++)//補正後も振動する場合は、初期値を答えとして返して強制的にループを抜け出して異常終了させる。
           return tm + TMZ; //このTMZが増えるのは原本が間違い?

      } while (abs (t0) > TM_LIMIT);

      return tm1 + tm2 + TMZ;// 時刻変数の合成(DT ==> JST 変換(補正時刻=0.0sec と仮定して計算))
    },


//_________

    //角度の正規化(範囲を 0≦θ<360 にする)
    NORMALIZATION_ANGLE = angle => {
      return 0 < angle ? angle % 360: 360 + angle % 360;
    },


//_________

    //太陽の黄経 λsun を計算する
    LONGITUDE_SUN = t => {
      let th = LONGITUDE_SUN_PARAMETER.map (([a, b, c]) => a * COS (b * t + c)); // 摂動項の計算
      return NORMALIZATION_ANGLE (th.reduce (sum, 36000.7695 * t + 280.4659/*比例項*/));
    },


//_________

    // 月の黄経 λmoon を計算する
    LONGITUDE_MOON = (t) => {
      // 摂動項の計算
      let th = LONGITUDE_MOON_PARAMETER.map (([a, b, c]) => a * COS (b * t + c)); // 摂動項の計算
      return NORMALIZATION_ANGLE (th.reduce (sum, 481267.8809 * t) + 218.3162/*比例項*/);
    },


//_________

    // 年月日、時分秒(世界時)からユリウス日(JD)を計算する
    // ※ この関数では、グレゴリオ暦法による年月日から求めるものである。
    //(ユリウス暦法による年月日から求める場合には使用できない。)
    YMDT2JD = date => {
      let
        [year, month, day, hour = 0, min = 0, sec = 0] =
          ['getFullYear', 'getMonth', 'getDate' /*, 'getHours', 'getMinutes', 'getSeconds'*/]
            .map (func => date[func]());

      if (++month < 3) {
        year -= 1;
        month += 12;
      }

      return (
          int (365.25 * year)
        + int (year / 400)
        - int (year / 100)
        + int (30.59 * (month - 2))
        + 1721088
        + day
        + (sec / 3600 + min / 60 + hour) / 24
      );
    },


//_________

    // ユリウス日(jd)から年月日、時分秒(世界時)を計算する
    // 戻値 0:年, 1:月, 2:日, 3:時, 4:分, 5:秒
    // ※ この関数で求めた年月日は、グレゴリオ暦法によって表されている。
    JD2YMDT = jd => {
      let
        x0 = int (jd + 68570),
        x1 = int (x0 / 36524.25),
        x2 = x0 - int (36524.25 * x1 + .75),
        x3 = int ((x2 + 1) / 365.2425),
        x4 = x2 - int (365.25 * x3) + 31,
        x5 = int (int (x4) / 30.59),
        x6 = int (int (x5) / 11),
        y = 100 * (x1 - 49) + x3 + x6,
        m = x5 - 12 * x6 + 2,
        d = x4 - int (30.59 * x5),
        tm = 86400 * (jd % 1);

      if (m === 2) //# 2月30日の補正
        d = (y % (y % 100 ? 400: 4)) ? 29: 28;

      return [y, m, d, int (tm / 3600), int ((tm % 3600) / 60), tm % 60];
    },


//_________

    // 太陽の黄経
    getSunYellowThrough = tm => {
      let
        tm1 = int (tm), // 時刻引数を分解する
        tm2 = tm - tm1 - TMZ,
        t = (tm1 + tm2 + 0.5 - 2451545) / 36525;

      return LONGITUDE_SUN (t);
    },


//_________

    // 今日が24節気かどうか調べる
    // 戻り値 .... 24節気の名称
    check_24sekki = date => {
      let
        tm = YMDT2JD (date),
        rm_sun_today    = 15 * int (getSunYellowThrough (tm) / 15),     // 今日の太陽の黄経
        rm_sun_tommorow = 15 * int (getSunYellowThrough (tm + 1) / 15); // 明日の太陽の黄経

      return (rm_sun_today != rm_sun_tommorow)
             ? SEKKI24_STR_JP[rm_sun_tommorow / 15]
             : '';
    };


  //_________

  this.get_rokuyou_kanji = get_rokuyou_kanji;
  this.check_24sekki     = check_24sekki; //24節気
  this.getKyureki        = calc_kyureki; //旧暦を返す

}

//____________________________________________
const
  datetimeFunc = ['getFullYear', 'getMonth', 'getDate', 'getDay'],
  getVal = function (func) { return this[func](); },
  
  Calendar = {
    current_date: null,
    

    init: function () {
      document.addEventListener ('click', this, false);
      this.current_date = new Date;
      this.setCalendarDate ();
    },

    
    setCalendarDate: function (offset = 0) {
      let d = this.current_date;
      d.setMonth (d.getMonth () + offset);
      this.current_date = d;
      this.dispCalendar (d);
    },

    
    dispCalendar: function (date) {
      let
        current_date = date,
        [year, month] = datetimeFunc.map (getVal, current_date),
        [,,lastDay, lastWeek] = datetimeFunc.map (getVal, new Date (year, month + 1, 0)),
        indexDate = new Date (year, month, (lastDay % 7) - lastWeek),
        maxCnt = Math.floor ((lastDay + 6) / 7) * 7,
        [caption, thead, tbody, tr] = document.querySelectorAll ('caption > label, thead, tbody');

      while (tbody.hasChildNodes())
        tbody.removeChild (tbody.firstChild);

      caption.textContent = `${month+1}月 (${year}年)`;
      for (let i = 0; i < maxCnt; i++) {
        if (! (i % 7))
          tr = tbody.insertRow ();

        let
          td = tr.insertCell (),
          [,m, d] = datetimeFunc.map (getVal, indexDate);

        td.innerHTML = [
          d,
          get_rokuyou_kanji (indexDate),
          check_24sekki (indexDate)
        ].join ('<br>');
        
        td.classList.add ((m === month) ? 'current': m < month ? 'prev': 'next');
        
        indexDate.setDate (indexDate.getDate () + 1);
      }
    
    },

    
    handleEvent: function ({target}) {
      switch (target.id) {
      case 'PREV_MONTH' : this.setCalendarDate (-1); break;
      case 'NEXT_MONTH' : this.setCalendarDate (1);  break; 
      }
    }
  
  };

Calendar.init ();
</script>