LoginSignup
0
0

月相・休日付きカレンダー

Last updated at Posted at 2024-05-06

Date に休日情報を追加した派生クラスを作ってカレンダーを表示する」に「月の満ち欠けの簡易表示」と月相を求める処理を追加して、背景に目安となる月相を表示してみる。

月相の計算 (JavaScript)
/*
 * 月相の計算
 *
 *   計算方法は下記のものを使用しています
 *   書籍:天体の位置計算(増補版) [長沢 工 著]
 *     【7-2表】月の位置の略算式(海上保安庁水路部による)
 *     【7-3表】太陽の位置の略算式(海上保安庁水路部による)
 */
class MoonPhaseRC {
	static Tbase = Date.UTC(1975, 1-1, 0);

	/*
	 * コンストラクタ
	 *	 引数は日時(Date コンストラクタに渡される)
	 */
	constructor() {
		const S = MoonPhaseRC;

		const date = new Date(...arguments);
		const t = (date - S.Tbase) / (365.25 * 86400000);
		const T = t + (0.0317 * t + 1.43) / 1000000;

		const sun = S.sunPosition(T);
		const moon = S.moonPosition(T);
		const phase = (moon.longitude - sun.longitude) % 360;

		this.T = T;
		this.sun = sun;
		this.moon = moon;
		this.phase = phase + ((phase < 0) ? 360 : 0);
	}

	/*
	 * 月の視黄経・視黄緯の算出
	 */
	static moonPosition(T) {
		const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
		const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
		const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));

		const A = [
			[0.0040,  93.80,  -1.330],
			[0.0020, 248.60, -19.340],
			[0.0006,  66.00,   0.200],
			[0.0006, 249.00, -19.300],
		].reduce(accumeSin, 0);

		const B = [
			[0.0267,  68.64, -19.341],
			[0.0043, 342.00, -19.360],
			[0.0040,  93.80,  -1.330],
			[0.0020, 248.60, -19.340],
			[0.0005, 358.00, -19.400],
		].reduce(accumeSin, 0);

		const longitude = [
			[1.2740, 107.248, -4133.3536],
			[0.6583,  51.668,  8905.3422],
			[0.2136, 317.831,  9543.9773],
			[0.1856, 176.531,   359.9905],

			[0.1143, 292.463,  9664.0404],
			[0.0588,  86.160,   638.6350],
			[0.0572, 103.780, -3773.3630],
			[0.0533,  30.580, 13677.3310],
			[0.0459, 124.860, -8545.3520],

			[0.0410, 342.380,  4411.9980],
			[0.0348,  25.830,  4452.6710],
			[0.0305, 155.450,  5131.9790],
			[0.0153, 240.790,   758.6980],
			[0.0125, 271.380, 14436.0290],

			[0.0110, 226.450, -4892.0520],
			[0.0107,  55.580,-13038.6960],
			[0.0100, 296.750, 14315.9660],
			[0.0085,  34.500, -8266.7100],
			[0.0079, 290.700, -4493.3400],

			[0.0068, 228.200,  9265.3300],
			[0.0052, 133.100,   319.3200],
			[0.0050, 202.400,  4812.6600],
			[0.0048,  68.600,   -19.3400],
			[0.0040,  34.100, 13317.3400],

			[0.0040,   9.500, 18449.3200],
			[0.0040,  93.800,    -1.3300],
			[0.0039, 103.300, 17810.6800],
			[0.0037,  65.100,  5410.6200],
			[0.0027, 321.300,  9183.9900],

			[0.0026, 174.800,-13797.3900],
			[0.0024,  82.700,   998.6300],
			[0.0024,   4.700,  9224.6600],
			[0.0022, 121.400, -8185.3600],
			[0.0021, 134.400,  9903.9700],

			[0.0021, 173.100,   719.9800],
			[0.0021, 100.300, -3413.3700],
			[0.0020, 248.600,   -19.3400],
			[0.0018,  98.100,  4013.2900],
			[0.0016, 344.100, 18569.3800],

			[0.0012,  52.100,-12678.7100],
			[0.0011, 250.300, 19208.0200],
			[0.0009,  81.000, -8586.0000],
			[0.0008, 207.000, 14037.3000],
			[0.0008,  31.000, -7906.7000],

			[0.0007, 346.000,  4052.0000],
			[0.0007, 294.000, -4853.3000],
			[0.0007,  90.000,   278.6000],
			[0.0006, 237.000,  1118.7000],
			[0.0005,  82.000, 22582.7000],

			[0.0005, 276.000, 19088.0000],
			[0.0005,  73.000,-17450.7000],
			[0.0005, 112.000,  5091.3000],
			[0.0004, 116.000,  -398.7000],
			[0.0004,  25.000,  -120.1000],

			[0.0004, 181.000,  9584.7000],
			[0.0004,  18.000,   720.0000],
			[0.0003,  60.000, -3814.0000],
			[0.0003,  13.000, -3494.7000],
			[0.0003,  13.000, 18089.3000],

			[0.0003, 152.000,  5492.0000],
			[0.0003, 317.000,   -40.7000],
			[0.0003, 348.000, 23221.3000],
		].reduce(
			accumeSin,
			+ 124.8754 + 4812.67881 * T
				+ 6.2887 * Math.sin(degToRad(338.915 + 4771.9886 * T + A))
		);

		const latitude = [
			[0.2806, 215.147,  9604.0088],
			[0.2777,  77.316,    60.0316],
			[0.1732,   4.563, -4073.3220],
			[0.0554, 308.980,  8965.3740],

			[0.0463, 343.480,   698.6670],
			[0.0326, 287.900, 13737.3620],
			[0.0172, 194.060, 14375.9970],
			[0.0093,  25.600, -8845.3100],
			[0.0088,  98.400, -4711.9600],

			[0.0082,   1.100, -3713.3300],
			[0.0043, 322.400,  5470.6600],
			[0.0042, 266.800, 18509.3500],
			[0.0034, 188.000, -4433.3100],
			[0.0025, 312.500,  8605.3800],

			[0.0022, 291.400, 13377.3700],
			[0.0021, 340.000,  1058.6600],
			[0.0019, 218.600,  9244.0200],
			[0.0018, 291.800, -8206.6800],
			[0.0018,  52.800,  5192.0100],

			[0.0017, 168.700, 14496.0600],
			[0.0016,  73.800,   420.0200],
			[0.0015, 262.100,  9284.6900],
			[0.0015,  31.700,  9964.0000],
			[0.0014, 260.800,  -299.9600],

			[0.0013, 239.700,  4472.0300],
			[0.0013,  30.400,   379.3500],
			[0.0012, 304.900,  4812.6800],
			[0.0012,  12.400, -4851.3600],
			[0.0011, 173.000, 19147.9900],

			[0.0010, 312.900,-12978.6600],
			[0.0008,   1.000, 17870.7000],
			[0.0008, 190.000,  9724.1000],
			[0.0007,  22.000, 13098.7000],
			[0.0006, 117.000,  5590.7000],

			[0.0006,  47.000,-13617.3000],
			[0.0005,  22.000, -8485.3000],
			[0.0005, 150.000,  4193.4000],
			[0.0004, 119.000, -9483.9000],
			[0.0004, 246.000, 23281.3000],

			[0.0004, 301.000, 10242.6000],
			[0.0004, 126.000,  9325.4000],
			[0.0004, 104.000, 14097.4000],
			[0.0003, 340.000, 22642.7000],
			[0.0003, 270.000, 18149.4000],

			[0.0003, 358.000, -3353.3000],
			[0.0003, 148.000, 19268.0000],
		].reduce(
			accumeSin,
			+ 5.1282 * Math.sin(degToRad(236.231 + 4832.0202 * T + B))
		);

		const sine = Math.sin(degToRad([
			[0.0518, 338.920,  4771.9890],
			[0.0095, 287.200, -4133.3500],
			[0.0078,  51.700,  8905.3400],
			[0.0028, 317.800,  9543.9800],
			[0.0009,  31.000, 13677.3000],
			[0.0005, 305.000, -8545.4000],
			[0.0004, 284.000, -3773.4000],
			[0.0003, 342.000,  4412.0000],
		].reduce(accumeCos, 0.9507)));

		return {
			longitude: longitude % 360,
			latitude: latitude,
			distance: 6378.140 / sine, // km
		}
	}

	/*
	 * 太陽の視黄経・視黄緯の算出
	 */
	static sunPosition(T) {
		const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
		const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
		const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));

		const longitude = [
			[+0.0200, 356.531,   719.981],
			[-0.0048, 248.640,   -19.341],
			[+0.0020, 285.000,   329.640],
			[+0.0018, 334.200, -4452.670],

			[+0.0018, 293.700,    -0.200],
			[+0.0015, 242.400,   450.370],
			[+0.0013, 211.100,   225.180],
			[+0.0008, 208.000,   659.290],
			[+0.0007,  53.500,   90.380],

			[+0.0007,  12.100,   -30.350],
			[+0.0006, 239.100,   337.180],
			[+0.0005,  10.100,    -1.500],
			[+0.0005,  99.100,   -22.810],
			[+0.0004, 264.800,   315.560],

			[+0.0004, 233.800,   299.300],
			[-0.0004, 198.100,   720.020],
			[+0.0003, 349.600,  1079.970],
			[+0.0003, 241.200,   -44.430],
		].reduce(accumeSin,
				 + (279.0358 + 360.00769 * T)
				 + (1.9159 - 0.00005 * T)
				 * Math.sin(degToRad(356.531 + 359.991 * T))
				);

		const q = [
			[-0.000091, 353.1,  719.98],
			[+0.000013, 205.8, 4452.67],
			[+0.000007,	 62.0,  450.40],
			[+0.000007, 105.0,  329.60],
		].reduce(accumeCos,
				 + (-0.007261 + 0.0000002 * T)
				 * Math.cos(degToRad(356.53 + 359.991 * T))
				 + 0.000030
				);
		const au = Math.pow(10, q);

		return {
			longitude: longitude % 360,
			latitude: 0,
			distance: au * 149597870.700, // km
		}
	}
}

See the Pen カレンダー (月/15週/年 + 月相) by Ikiuo (@ikiuo) on CodePen.

Google Chrome 拡張機能は moonphase ブランチとした。

HTML ファイル版
<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8">
    <title>カレンダー</title>
    <style>
     body {
         background-color: #000000;
         color: #ffffff;
     }

     button {
         border: solid 1px #d3d3d3;
         background-color: #000000;
     }
     button:hover {
         background-color: #0000bf;
     }
     .selector {
         text-align: center;
         padding: 4px;
     }
     .monthly {
         margin-left: auto;
         margin-right: auto;
         width: 200px;
     }
     .yearly {
         margin-left: auto;
         margin-right: auto;
         width: 600px;
     }

     table.jhcalyear {
         border: solid 1px #d3d3d3;
     }
     td.jhcalyhbox {
         cursor: default;
         text-align: center;
         font-size: x-large;
     }
     span.jhcalyallow:hover {
         color: #ff0000;
     }
     span.jhcalyheader:hover {
         color: #ff0000;
     }
     td.jhcalymonth {
         padding: 2px;
     }

     table.jhcalmonth {
         border: solid 1px #d3d3d3;
         border-right: 0px;
         border-bottom: 0px;
         border-collaspe: collaspe;
         border-spacing: 0;
     }
     table.jhcalmonth td {
         border: solid 1px #d3d3d3;
         border-left: 0px;
         border-top: 0px;
     }
     td.jhcalmheader {
         cursor: default;
         text-align: center;
         padding: 4px;
     }
     td.jhcalmheader:hover {
         color: red;
     }
     td.jhcalmarrow {
         cursor: default;
         text-align: center;
     }
     td.jhcalmarrow:hover {
         color: #ff0000;
     }

     td.jhcalwodbox {
         cursor: default;
         background-color: #4f4f4f;
         text-align: center;
     }
     div.jhcalweekofday {
         font-size: small;
     }
     div.jhcalwsunday {
         color: #ff0000;
     }
     div.jhcalwmonday {
         /**/
     }
     div.jhcalwtuesday {
         /**/
     }
     div.jhcalwwednesday {
         /**/
     }
     div.jhcalwthursday {
         /**/
     }
     div.jhcalwfriday {
         /**/
     }
     div.jhcalwsaturday {
         color: #009fff;
     }

     td.jhcaldaybox {
         position: relative;
         padding: 0px;
     }
     div.jhcalday {
         cursor: default;
         border: solid 1px rgba(0, 0, 0, 0);
         text-align: right;
         text-shadow: 1px 1px 5px #000000;
         padding: 4px;
     }
     div.jhcaltoday {
         border: solid 1px #00ffff;
         background-color: #0000bf;
     }
     div.jhcalholiday {
         color: #ff00bf;
         font-weight: bold;
     }
     div.jhcalinfo {
         color: #ff0000;
     }
     div.jhcalsunday {
         color: #ff0000;
     }
     div.jhcalmonday {
         /**/
     }
     div.jhcaltuesday {
         /**/
     }
     div.jhcalwednesday {
         /**/
     }
     div.jhcalthursday {
         /**/
     }
     div.jhcalfriday {
         /**/
     }
     div.jhcalsaturday {
         color: #00bfff;
     }
     div.jhcalother {
         color: #bfbfbf;
     }

     td.jhcalwhbox {
         cursor: default;
         text-align: center;
     }
     td.jhcalwhbox:hover {
         color: #ff0000;
     }
     div.jhcalmeven {
         background-color: #00005f;
     }
     div.jhcalmodd {
         /**/
     }
    </style>
  </head>
  <body>
    <div class="selector">
      <button id="tagButton0" onclick="onSelect(0)">月間</button>
      <button id="tagButton1" onclick="onSelect(1)">15</button>
      <button id="tagButton2" onclick="onSelect(2)">年間</button>
    </div>
    <div id="tagMonthly" class="monthly"></div>
    <div id="tagWeekly" class="monthly"></div>
    <div id="tagYearly" class="yearly"></div>

    <script>

     /*
      * 月相の計算
      *
      *   計算方法は下記のものを使用しています
      *   書籍:天体の位置計算(増補版) [長沢 工 著]
      *     【7-2表】月の位置の略算式(海上保安庁水路部による)
      *     【7-3表】太陽の位置の略算式(海上保安庁水路部による)
      */
     class MoonPhaseRC {
         static Tbase = Date.UTC(1975, 1-1, 0);

         /*
          * コンストラクタ
          *       引数は日時(Date コンストラクタに渡される)
          */
         constructor() {
             const S = MoonPhaseRC;

             const date = new Date(...arguments);
             const t = (date - S.Tbase) / (365.25 * 86400000);
             const T = t + (0.0317 * t + 1.43) / 1000000;

             const sun = S.sunPosition(T);
             const moon = S.moonPosition(T);
             const phase = (moon.longitude - sun.longitude) % 360;

             this.T = T;
             this.sun = sun;
             this.moon = moon;
             this.phase = phase + ((phase < 0) ? 360 : 0);
         }

         /*
          * 月の視黄経・視黄緯の算出
          */
         static moonPosition(T) {
             const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
             const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
             const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));

             const A = [
                 [0.0040,  93.80,  -1.330],
                 [0.0020, 248.60, -19.340],
                 [0.0006,  66.00,   0.200],
                 [0.0006, 249.00, -19.300],
             ].reduce(accumeSin, 0);

             const B = [
                 [0.0267,  68.64, -19.341],
                 [0.0043, 342.00, -19.360],
                 [0.0040,  93.80,  -1.330],
                 [0.0020, 248.60, -19.340],
                 [0.0005, 358.00, -19.400],
             ].reduce(accumeSin, 0);

             const longitude = [
                 [1.2740, 107.248, -4133.3536],
                 [0.6583,  51.668,  8905.3422],
                 [0.2136, 317.831,  9543.9773],
                 [0.1856, 176.531,   359.9905],

                 [0.1143, 292.463,  9664.0404],
                 [0.0588,  86.160,   638.6350],
                 [0.0572, 103.780, -3773.3630],
                 [0.0533,  30.580, 13677.3310],
                 [0.0459, 124.860, -8545.3520],

                 [0.0410, 342.380,  4411.9980],
                 [0.0348,  25.830,  4452.6710],
                 [0.0305, 155.450,  5131.9790],
                 [0.0153, 240.790,   758.6980],
                 [0.0125, 271.380, 14436.0290],

                 [0.0110, 226.450, -4892.0520],
                 [0.0107,  55.580,-13038.6960],
                 [0.0100, 296.750, 14315.9660],
                 [0.0085,  34.500, -8266.7100],
                 [0.0079, 290.700, -4493.3400],

                 [0.0068, 228.200,  9265.3300],
                 [0.0052, 133.100,   319.3200],
                 [0.0050, 202.400,  4812.6600],
                 [0.0048,  68.600,   -19.3400],
                 [0.0040,  34.100, 13317.3400],

                 [0.0040,   9.500, 18449.3200],
                 [0.0040,  93.800,    -1.3300],
                 [0.0039, 103.300, 17810.6800],
                 [0.0037,  65.100,  5410.6200],
                 [0.0027, 321.300,  9183.9900],

                 [0.0026, 174.800,-13797.3900],
                 [0.0024,  82.700,   998.6300],
                 [0.0024,   4.700,  9224.6600],
                 [0.0022, 121.400, -8185.3600],
                 [0.0021, 134.400,  9903.9700],

                 [0.0021, 173.100,   719.9800],
                 [0.0021, 100.300, -3413.3700],
                 [0.0020, 248.600,   -19.3400],
                 [0.0018,  98.100,  4013.2900],
                 [0.0016, 344.100, 18569.3800],

                 [0.0012,  52.100,-12678.7100],
                 [0.0011, 250.300, 19208.0200],
                 [0.0009,  81.000, -8586.0000],
                 [0.0008, 207.000, 14037.3000],
                 [0.0008,  31.000, -7906.7000],

                 [0.0007, 346.000,  4052.0000],
                 [0.0007, 294.000, -4853.3000],
                 [0.0007,  90.000,   278.6000],
                 [0.0006, 237.000,  1118.7000],
                 [0.0005,  82.000, 22582.7000],

                 [0.0005, 276.000, 19088.0000],
                 [0.0005,  73.000,-17450.7000],
                 [0.0005, 112.000,  5091.3000],
                 [0.0004, 116.000,  -398.7000],
                 [0.0004,  25.000,  -120.1000],

                 [0.0004, 181.000,  9584.7000],
                 [0.0004,  18.000,   720.0000],
                 [0.0003,  60.000, -3814.0000],
                 [0.0003,  13.000, -3494.7000],
                 [0.0003,  13.000, 18089.3000],

                 [0.0003, 152.000,  5492.0000],
                 [0.0003, 317.000,   -40.7000],
                 [0.0003, 348.000, 23221.3000],
             ].reduce(
                 accumeSin,
                        + 124.8754 + 4812.67881 * T
                        + 6.2887 * Math.sin(degToRad(338.915 + 4771.9886 * T + A))
             );

             const latitude = [
                 [0.2806, 215.147,  9604.0088],
                 [0.2777,  77.316,    60.0316],
                 [0.1732,   4.563, -4073.3220],
                 [0.0554, 308.980,  8965.3740],

                 [0.0463, 343.480,   698.6670],
                 [0.0326, 287.900, 13737.3620],
                 [0.0172, 194.060, 14375.9970],
                 [0.0093,  25.600, -8845.3100],
                 [0.0088,  98.400, -4711.9600],

                 [0.0082,   1.100, -3713.3300],
                 [0.0043, 322.400,  5470.6600],
                 [0.0042, 266.800, 18509.3500],
                 [0.0034, 188.000, -4433.3100],
                 [0.0025, 312.500,  8605.3800],

                 [0.0022, 291.400, 13377.3700],
                 [0.0021, 340.000,  1058.6600],
                 [0.0019, 218.600,  9244.0200],
                 [0.0018, 291.800, -8206.6800],
                 [0.0018,  52.800,  5192.0100],

                 [0.0017, 168.700, 14496.0600],
                 [0.0016,  73.800,   420.0200],
                 [0.0015, 262.100,  9284.6900],
                 [0.0015,  31.700,  9964.0000],
                 [0.0014, 260.800,  -299.9600],

                 [0.0013, 239.700,  4472.0300],
                 [0.0013,  30.400,   379.3500],
                 [0.0012, 304.900,  4812.6800],
                 [0.0012,  12.400, -4851.3600],
                 [0.0011, 173.000, 19147.9900],

                 [0.0010, 312.900,-12978.6600],
                 [0.0008,   1.000, 17870.7000],
                 [0.0008, 190.000,  9724.1000],
                 [0.0007,  22.000, 13098.7000],
                 [0.0006, 117.000,  5590.7000],

                 [0.0006,  47.000,-13617.3000],
                 [0.0005,  22.000, -8485.3000],
                 [0.0005, 150.000,  4193.4000],
                 [0.0004, 119.000, -9483.9000],
                 [0.0004, 246.000, 23281.3000],

                 [0.0004, 301.000, 10242.6000],
                 [0.0004, 126.000,  9325.4000],
                 [0.0004, 104.000, 14097.4000],
                 [0.0003, 340.000, 22642.7000],
                 [0.0003, 270.000, 18149.4000],

                 [0.0003, 358.000, -3353.3000],
                 [0.0003, 148.000, 19268.0000],
             ].reduce(
                 accumeSin,
            + 5.1282 * Math.sin(degToRad(236.231 + 4832.0202 * T + B))
             );

             const sine = Math.sin(degToRad([
                 [0.0518, 338.920,  4771.9890],
                 [0.0095, 287.200, -4133.3500],
                 [0.0078,  51.700,  8905.3400],
                 [0.0028, 317.800,  9543.9800],
                 [0.0009,  31.000, 13677.3000],
                 [0.0005, 305.000, -8545.4000],
                 [0.0004, 284.000, -3773.4000],
                 [0.0003, 342.000,  4412.0000],
             ].reduce(accumeCos, 0.9507)));

             return {
                 longitude: longitude % 360,
                 latitude: latitude,
                 distance: 6378.140 / sine, // km
             }
         }

         /*
          * 太陽の視黄経・視黄緯の算出
          */
         static sunPosition(T) {
             const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
             const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
             const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));

             const longitude = [
                 [+0.0200, 356.531,   719.981],
                 [-0.0048, 248.640,   -19.341],
                 [+0.0020, 285.000,   329.640],
                 [+0.0018, 334.200, -4452.670],

                 [+0.0018, 293.700,    -0.200],
                 [+0.0015, 242.400,   450.370],
                 [+0.0013, 211.100,   225.180],
                 [+0.0008, 208.000,   659.290],
                 [+0.0007,  53.500,   90.380],

                 [+0.0007,  12.100,   -30.350],
                 [+0.0006, 239.100,   337.180],
                 [+0.0005,  10.100,    -1.500],
                 [+0.0005,  99.100,   -22.810],
                 [+0.0004, 264.800,   315.560],

                 [+0.0004, 233.800,   299.300],
                 [-0.0004, 198.100,   720.020],
                 [+0.0003, 349.600,  1079.970],
                 [+0.0003, 241.200,   -44.430],
             ].reduce(accumeSin,
                 + (279.0358 + 360.00769 * T)
                 + (1.9159 - 0.00005 * T)
                 * Math.sin(degToRad(356.531 + 359.991 * T))
             );

             const q = [
                 [-0.000091, 353.1,  719.98],
                 [+0.000013, 205.8, 4452.67],
                 [+0.000007,     62.0,  450.40],
                 [+0.000007, 105.0,  329.60],
             ].reduce(accumeCos,
                 + (-0.007261 + 0.0000002 * T)
                 * Math.cos(degToRad(356.53 + 359.991 * T))
                 + 0.000030
             );
             const au = Math.pow(10, q);

             return {
                 longitude: longitude % 360,
                 latitude: 0,
                 distance: au * 149597870.700, // km
             }
         }
     }

     /*
      * 月相の形状を生成する
      */
     class MoonPhaseShape {

         /*
          * コンストラクタ
          *
          * 中心座標を(0,0)として生成する
          *
          * 引数 (プロパティとして保存)
          *   radius: 円半径
          *   phase: 月光の位置
          *     0 (新月) → 180 (満月) → 360 (新月)
          *     範囲外は [0,360) の範囲に変更される
          *   distance: 視距離
          *     null の場合は 380000 とする
          *   sphereSize: 球体半径
          *     null の場合は 1738 とする
          *
          * インスタンス プロパティ
          *   lightPath: 月光部分の Path2D (null の場合あり)
          *   shadowPath: 月影部分の Path2D (null の場合あり)
          */
         constructor(radius, phase, distance, sphereSize) {
             const S = MoonPhaseShape;

             phase = phase ?? 0;
             if ((phase %= 360) < 0)
                 phase += 360;

             this.S = S;
             this.radius = radius;
             this.phase = phase;
             this.distance = distance ?? 380000;
             this.sphereSize = sphereSize ?? 1738;
             this.latitudeMax = Math.acos(this.sphereSize / this.distance);

             this.initCircle();
             this.initPhase();

             this.lightPath = S.createPath(this.light);
             this.shadowPath = S.createPath(this.shadow);
         }

         initCircle() {
             const PI = Math.PI;
             const quaterPI = PI * 0.25;
             const radius = this.radius;
             const count = radius >> 1;
             const co1 = [...Array(count + 1)].map(function(_, n){
                 const r = quaterPI * n / count;
                 return [radius * Math.sin(r), radius * Math.cos(r)]
             });
             const co2 = co1.map(v => [v[1], v[0]]);
             const cq1 = co1.concat(co2.toReversed().slice(1));
             const cq2 = cq1.map(v => [v[0], -v[1]]);
             const ch1 = cq1.concat(cq2.toReversed().slice(1));
             const ch2 = ch1.map(v => [-v[0], v[1]]);

             this.circleLeft = ch2;
             this.circleRight = ch1;
         }

         initPhase() {
             const PI = Math.PI;
             const halfPI = PI * 0.5;
             const twoPI = PI * 2.0;

             const radius = this.radius;
             const distance = this.distance;
             const sphereSize = this.sphereSize;
             const mr = halfPI - this.latitudeMax;
             {
                 const x = sphereSize * Math.cos(mr);
                 const z = distance - sphereSize * Math.sin(mr);
                 this.viewScale = radius * z / x;
             }

             const pl = this.phase * PI / 180;
             const pr = (pl >= PI) ? pl - PI : pl + PI;

             const ql = pl - mr;
             const qr = pr - mr;
             const qm = PI - mr * 2;
             const qn = PI + mr;

             let ptype, sep;
             if (0 <= qr && qr < qm) {
                 ptype = 0;
                 sep = qr;
             } else if (0 <= ql && ql < qm) {
                 ptype = 1;
                 sep = ql;
             } else {
                 ptype = 2 | (0 <= ql && ql < qn);
             }

             const cl = this.circleLeft;
             const cr = this.circleRight;
             if ((ptype & 2) != 0) {
                 const cd = cr.concat(cl.toReversed().slice(1, -1));
                 this.light  = (ptype & 1) ? cd : null;
                 this.shadow = (ptype & 1) ? null : cd;
             } else {
                 const cc = this.getSeparator(sep);
                 const vr = cr.concat(cc.toReversed());
                 const vl = cc.concat(cl.toReversed());
                 this.light  = (ptype & 1) ? vr : vl;
                 this.shadow = (ptype & 1) ? vl : vr;
             }
         }

         getSeparator(sep) {
             const radius = this.radius;
             const distance = this.distance;
             const sphereSize = this.sphereSize;
             const viewScale = this.viewScale;
             const latitudeMax = this.latitudeMax;
             const sepCos = Math.cos(sep);
             const sepSin = Math.sin(sep);

             const count = radius >> 0;
             const shp = [...Array(count + 1)].map(function(_, n) {
                 const t = latitudeMax * n / count;
                 const x = sphereSize * Math.cos(t) * sepCos;
                 const y = sphereSize * Math.sin(t);
                 const z = distance - sphereSize * Math.cos(t) * sepSin;
                 const v = viewScale / z;
                 return [v * x, v * y];
             });
             const shm = shp.map(v => [v[0], -v[1]]);

             return shp.toReversed().concat(shm.slice(1))
         }

         static createPath(pos) {
             if (pos == null)
                 return null;
             const plen = pos.length;
             if (!plen)
                 return null;

             const path = new Path2D();
             const p0 = pos[0];
             path.moveTo(p0[0], p0[1]);
             for (let i = 1; i < plen; i++) {
                 const p = pos[i];
                 path.lineTo(p[0], p[1]);
             }
             path.closePath();
             return path;
         }

         createBitmap(option) {
             const width = option.width ?? this.radius * 2 + 4;
             const height = option.height ?? width;
             const cx = option.offset_x ?? width / 2;
             const cy = option.offset_y ?? height / 2;
             const background = option.background ?? '#00000000';
             const shadow = option.shadow ?? '#00000000';
             const light = option.light ?? '#00000000';
             const rotate = option.rotate ?? 0;

             const canvas = new OffscreenCanvas(width, height);
             const ctx = canvas.getContext('2d');

             ctx.clearRect(0, 0, width, height);

             ctx.fillStyle = background;
             ctx.fillRect(0, 0, width, height);

             ctx.translate(cx, cy);
             ctx.rotate(rotate * Math.PI / 180);
             if (this.shadowPath) {
                 ctx.fillStyle = shadow;
                 ctx.fill(this.shadowPath);
             }
             if (this.lightPath) {
                 ctx.fillStyle = light;
                 ctx.fill(this.lightPath);
             }
             return canvas.convertToBlob();
         }

         static createPhaseTable(option) {
             if (option == null)
                 option = {}

             const count = option.count ?? 60;
             const width = option.width ?? 128;
             const height = option.height ?? width;
             const radius = Math.max(4, Math.min(width, height) - 1) / 2;
             const rotate = option.rotate ?? 0;

             const popt = {
                 width: width,
                 height: height,
                 offset_x: option.offset_x,
                 offset_y: option.offset_y,
                 background: option.background,
                 shadow: option.shadow,
                 light: option.light,
             }

             return Promise.all([...Array(count)].map((_, i) => {
                 popt.rotate = rotate * (1 - 2 * i / count);
                 const mps = new MoonPhaseShape(radius, i * 360 / count);
                 return mps.createBitmap(popt).then(o => URL.createObjectURL(o));
             }));
         }
     }

     /*
      * 休日データ
      */

     class JHoliday {
         static #startDate = (new Date(1970, 1-1, 1)).getTime() * 10;
         static #offsetVE = JHoliday.#startDate +  68619916800;
         static #offsetAE = JHoliday.#startDate + 229674407040;
         static #yEquinox = 315569255616;

         static #monthTable = [...Array(12)].map(() => ({}));
         static #createParameter(name, month, day, start, end) {
             const S = JHoliday;

             month -= 1;
             const holidy = {
                 name: name,
                 month: month,
                 day: day,
                 start: Math.max(1948, (start ?? 0)),
                 end: end ?? 10000,
             }
             const table = S.#monthTable;
             const mtab = table[month];
             if (!mtab[day])
                 mtab[day] = [];
             mtab[day].push(holidy);
             return holidy;
         }

         static #mainTable = [
             ['元日', 1, 1],
             ['成人の日', 1, 15, 0, 1999],
             ['成人の日', 1, 'M2', 2000],

             ['建国記念の日', 2, 11],

             ['春分の日', 3, 'VE'],

             ['憲法記念日', 5, 3],
             ['みどりの日', 5, 4, 2007],
             ['こどもの日', 5, 5],

             ['海の日', 7, 20, 1996, 2002],
             ['海の日', 7, 'M3', 2003],

             ['山の日', 8, 11, 2016],

             ['敬老の日', 9, 15, 1966, 2002],
             ['敬老の日', 9, 'M3', 2003],
             ['秋分の日', 9, 'AE'],

             ['体育の日', 10, 10, 0, 1999],
             ['体育の日', 10, 'M2', 2000, 2019],
             ['スポーツの日', 10, 'M2', 2020],

             ['文化の日', 11, 3],
             ['勤労感謝の日', 11, 23],

             /* 昭和 */
             ['天皇誕生日', 4, 29, 0, 1988],
             ['みどりの日', 4, 29, 1989, 2006],
             ['昭和の日', 4, 29, 2007],

             /* 平成 */
             ['天皇誕生日', 12, 23, 1989, 2018],

             /* 令和 */
             ['天皇の即位の日', 5, 1, 2019, 2019],
             ['即位礼正殿の儀が行われる日', 10, 22, 2019, 2019],
             ['天皇誕生日', 2, 23, 2020],

         ].map((v) => JHoliday.#createParameter(...v));

         static #altname = '休日';

         static #getBasic(date) {
             const S = JHoliday;

             const year = date.getFullYear();
             const month = date.getMonth();
             const mday = date.getDate();
             const wday = date.getDay();

             const mtab = S.#monthTable[month]
             const dtab = mtab[mday];
             if (dtab) {
                 const t = dtab.filter((h) =>
                     (h.start <= year && year <= h.end));
                 if (t.length)
                     return t[0].name;
             }
             if (wday == 1) {
                 const mweek = Math.trunc((mday - 1) / 7);
                 const monday = mtab[`M${mweek+1}`];
                 if (monday) {
                     const t = monday.filter((h) =>
                         (h.start <= year && year <= h.end));
                     if (t.length)
                         return t[0].name;
                 }
             }

             const ve = mtab['VE'];
             if (ve) {
                 const equniox = S.getVernalEquinox(year);
                 if (mday == equniox.getDate())
                     return ve[0].name;
             }

             const ae = mtab['AE'];
             if (ae) {
                 const equniox = S.getAutumnEquinox(year);
                 if (mday == equniox.getDate())
                     return ae[0].name;
             }

             return undefined;
         }

         static #weekCache = {}
         static #getWeek(date) {
             const S = JHoliday;

             const year = date.getFullYear();
             const month = date.getMonth();
             const mday = date.getDate();
             const wday = date.getDay();
             const wtop = new Date(year, month, 1).getDay();
             const mweek = Math.trunc((wtop + mday - 1) / 7);
             const index = (year * 12 + month) * 6 + mweek;

             const cache = S.#weekCache[index];
             if (cache)
                 return cache;

             const line = [...Array(8)].map((_, i) =>
                 S.#getBasic(new Date(year, month, mday - wday + i)));
             if (year >= 1973 && line[0]) {
                 for (let i = 1; i < 7; i++) {
                     if (!line[i]) {
                         line[i] = S.#altname;
                         break;
                     }
                 }
             }
             if (year >= 1986)
                 for (let i = 1; i < 7; i++)
                     if (!line[i] && line[i - 1] && line[i + 1])
                         line[i] = S.#altname;

             S.#weekCache[index] = line;
             return line;
         }

         // 年から春分の日(Date型)を取得.
         static getVernalEquinox(year) {
             const S = JHoliday;
             return new Date(((year - 1970) * S.#yEquinox + S.#offsetVE) / 10);
         }

         // 年から秋分の日(Date型)を取得.
         static getAutumnEquinox(year) {
             const S = JHoliday;
             return new Date(((year - 1970) * S.#yEquinox + S.#offsetAE) / 10);
         }

         // Date 型から休日を取得.
         static getHoliday(date) {
             const S = JHoliday;
             return S.#getWeek(date)[date.getDay()];
         }
     }

     /*
      * 休日ありの Date 派生クラス
      */

     class JHDate extends Date {
         #holiday;

         constructor() {
             super(...arguments);
             this.#holiday = JHoliday.getHoliday(this);
         }

         // 休日を取得.
         getHoliday() {
             return this.#holiday;
         }
     }

     /*
      * 週/月
      */

     // リストを週単位の行列に変換.
     function toWeekly(l) {
         return [...Array(Math.trunc(l.length / 7))]
             .map((_, w) => l.slice(w * 7, w * 7 + 7));
     }

     // 月間 JHDate のスリとを取得.
     function getMonthlyJHDates(date) {
         const year = date.getFullYear();
         const month = date.getMonth();
         return [...Array(new Date(year, month + 1, 0).getDate())]
             .map((_, i) => new JHDate(year, month, i + 1));
     }

     // 週の前方を補間するためのリストを取得.
     function getWeeklyPrefix(date) {
         const y = date.getFullYear();
         const m = date.getMonth();
         const ns = date.getDay();
         return [...Array(ns)]
             .map((_, i) => new JHDate(y, m, 1 + i - ns));
     }

     // 週の後方を補間するためのリストを取得.
     function getWeeklySuffix(date) {
         const y = date.getFullYear();
         const m = date.getMonth();
         return [...Array(6 - date.getDay())]
             .map((_, i) => new JHDate(y, m + 1, 1 + i));
     }

     // 月間カレンダー用 JHDate 行列の取得.
     function getMonthlyMatrix(date) {
         const mlist = getMonthlyJHDates(date);
         const s = mlist[0];
         const e = mlist[mlist.length - 1];
         const pfx = getWeeklyPrefix(s);
         const sfx = getWeeklySuffix(e);
         return toWeekly(pfx.concat(mlist, sfx));
     }

     /*
      * DOM 操作
      */

     const createTag = {};
     [
         'div', 'p', 'span',
         'table', 'td', 'tr',
         'img',
     ].forEach((name) =>
         createTag[name] = (() => document.createElement(name))
     );

     // 月間カレンダー
     class JHMonthlyCalendar {
         constructor(date, option) {
             this.today = new Date();
             this.date = date ?? this.today;
             this.option = option ?? {};
             this.getHeader = (this.option.month_header ??
                               ((y, m) => `${y}${m}月`));
             const cpfx = this.option.prefix ?? '';

             this.className = {
                 month: {
                     table: `${cpfx}month`,
                 },
                 arrow: {
                     td: `${cpfx}marrow`,
                 },
                 header: {
                     td: `${cpfx}mheader`,
                 },
                 weekofday: {
                     td: `${cpfx}wodbox`,
                     div: `${cpfx}weekofday`,
                 },
                 wodheader: [
                     { div: `${cpfx}wsunday` },
                     { div: `${cpfx}wmonday` },
                     { div: `${cpfx}wtuesday` },
                     { div: `${cpfx}wwednesday` },
                     { div: `${cpfx}wthursday` },
                     { div: `${cpfx}wfriday` },
                     { div: `${cpfx}wsaturday` },
                 ],

                 day: {
                     td: `${cpfx}daybox`,
                     div: `${cpfx}day`,
                 },
                 today: {
                     div: `${cpfx}today`,
                 },
                 holiday: {
                     div: `${cpfx}holiday`,
                 },
                 popup: {
                     div: `${cpfx}info`,
                 },
                 other: {
                     div: `${cpfx}other`,
                 },
                 week: [
                     { div: `${cpfx}sunday` },
                     { div: `${cpfx}monday` },
                     { div: `${cpfx}tuesday` },
                     { div: `${cpfx}wednesday` },
                     { div: `${cpfx}thursday` },
                     { div: `${cpfx}friday` },
                     { div: `${cpfx}saturday` },
                 ],
             }

             this.tagTable = createTag.table();
             this.tagTable.className = this.className.month.table;

             this.setCalendar();
         }

         clearTable() {
             while (this.tagTable.firstChild)
                 this.tagTable.removeChild(this.tagTable.firstChild);
         }

         setCalendar() {
             this.year = this.date.getFullYear();
             this.month = this.date.getMonth();
             this.weekly = getMonthlyMatrix(this.date);
             this.tagHeader = null;
             this.header = null;

             this.setHeader();
             this.setWeek();
             this.setMatrix();
         }

         updateCalendar() {
             this.clearTable()
             this.setCalendar()
         }

         setHeader() {
             const header = this.getHeader(this.year, this.month + 1);
             if (!header || !header.length)
                 return;
             this.header = header;

             const fixed = this.option.fixed;
             const tr = createTag.tr();
             if (!fixed) {
                 const td = createTag.td();
                 td.className = this.className.arrow.td;
                 td.innerText = '';
                 td.onclick = (() => this.prevMonth());
                 tr.appendChild(td);
             }
             {
                 const td = createTag.td();
                 this.tagHeader = td;

                 td.className = this.className.header.td;
                 td.setAttribute('colspan', fixed ? 7 : 5);
                 td.innerText = header;
                 if (!fixed)
                     td.onclick = (() => this.moveToday());
                 tr.appendChild(td);
             }
             if (!fixed) {
                 const td = createTag.td();
                 td.className = this.className.arrow.td;
                 td.innerText = '';
                 td.onclick = (() => this.nextMonth());
                 tr.appendChild(td);
             }
             this.tagTable.appendChild(tr);
         }

         setWeek() {
             if (!this.option.weekofday)
                 return;

             const tr = createTag.tr();
             [...'日月火水木金土'].forEach((c, i) => {
                 const td = createTag.td();
                 td.className = this.className.weekofday.td;

                 const cn_weekofday = this.className.weekofday.div;
                 const cn_wodheader = this.className.wodheader[i].div;
                 const div = createTag.div();
                 div.className = `${cn_weekofday} ${cn_wodheader}`.trim();
                 div.innerText = c;

                 td.appendChild(div);
                 tr.appendChild(td);
             });

             this.tagTable.appendChild(tr);
         }

         setMatrix() {
             const today = this.today;
             const chktoday = ((today.getFullYear() == this.year &&
                                today.getMonth() == this.month)
                             ? today.getDate() : null);
             const tagHeader = this.tagHeader;
             const c_day_td = this.className.day.td;
             const c_day_div = this.className.day.div;
             const c_today_div = this.className.today.div;
             const c_holiday_div = this.className.holiday.div;
             const c_week = this.className.week;
             const c_other_div = this.className.other.div;
             const moon = this.option.moonshape;
             const moonshapes = (moon != null) ? moon.length : 0;

             this.weekly.forEach((week, y) => {
                 const tr = createTag.tr();
                 week.forEach((day, x) => {
                     const holiday = day.getHoliday();
                     const td = createTag.td();
                     td.className = c_day_td;
                     {
                         const div = createTag.div();
                         const cn_other = ((day.getMonth() != this.month) ? c_other_div : null);
                         const cn_holiday = (holiday ? c_holiday_div : null);
                         const cn_week = c_week[day.getDay()].div;
                         const cn_day = cn_other ?? cn_holiday ?? cn_week;
                         const cn_today = ((day.getDate() == chktoday) ? c_today_div : '');
                         div.className = `${c_day_div} ${cn_day} ${cn_today}`.trim();
                         div.innerText = day.getDate();
                         if (moonshapes > 0) {
                             const phase = new MoonPhaseRC(day + 43200000).phase;
                             const ipos = Math.round(moonshapes * phase / 360) % moonshapes;
                             const url = moon[ipos + (ipos < 0 ? moonshapes : 0)];
                             div.style.backgroundPosition = 'center center';
                             div.style.backgroundRepeat = 'no-repeat';
                             div.style.backgroundSize = 'auto';
                             div.style.backgroundImage = `url(${url})`;
                         }
                         td.appendChild(div);
                     }
                     if (holiday && tagHeader) {
                         td.onmouseenter = (() => this.tagHeader.innerText = holiday);
                         td.onmouseleave = (() => this.tagHeader.innerText = this.header);
                     }
                     tr.appendChild(td);
                 });
                 this.tagTable.appendChild(tr);
             });
         }

         // Event

         prevMonth() {
             this.moveMonth(new Date(this.year, this.month - 1, 1));
         }
         nextMonth() {
             this.moveMonth(new Date(this.year, this.month + 1, 1));
         }
         moveMonth(date) {
             this.today = new Date();
             this.date = date;
             this.updateCalendar();
         }
         moveToday() {
             this.today = new Date();
             this.date = this.today;
             this.updateCalendar();
         }
     }

     // 年間カレンダー
     class JHYearlyCalendar {
         constructor(date, option) {
             this.today = new Date();
             this.date = date ?? this.today;
             this.option = option ?? {};
             const cpfx = this.option.prefix ?? '';

             this.className = {
                 year: {
                     table: `${cpfx}year`,
                 },
                 arrow: {
                     span: `${cpfx}yallow`,
                 },
                 header: {
                     td: `${cpfx}yhbox`,
                     span: `${cpfx}yheader`,
                 },
                 month: {
                     td: `${cpfx}ymonth`,
                 },
             }

             this.tagTable = createTag.table();
             this.tagTable.className = this.className.year.table;

             this.setCalendar();
         }

         clearTable() {
             while (this.tagTable.firstChild)
                 this.tagTable.removeChild(this.tagTable.firstChild);
         }

         setCalendar() {
             this.year = this.date.getFullYear();

             const prefix = this.option.prefix ?? '';
             const year = this.date.getFullYear();

             this.yearly = [...Array(12)].map((_, m) =>
                 new JHMonthlyCalendar(new Date(this.year, m), {
                     prefix: prefix,
                     month_header: (y, m) => `${m}月`,
                     fixed: true,
                     moonshape: this.option.moonshape,
                 }).tagTable);

             this.tagHeader = null;
             this.header = null;

             this.setHeader();
             this.setMatrix();
         }

         updateCalendar() {
             this.clearTable()
             this.setCalendar()
         }

         setHeader() {
             const space = '&nbsp;'.repeat(4);
             const tr = createTag.tr();
             const td = createTag.td();
             td.className = this.className.header.td;
             td.setAttribute('colspan', '3');
             {
                 const span = createTag.span();
                 span.className = this.className.arrow.span;
                 span.innerText = '';
                 span.onclick = (() => this.prevYear());
                 td.appendChild(span);
             }
             td.insertAdjacentHTML('beforeend', `<span>${space}</span>`)
             {
                 const span = createTag.span();
                 span.className = this.className.header.span;
                 span.innerText = `${this.year}年`;
                 span.onclick = (() => this.moveToday());
                 td.appendChild(span);
             }
             td.insertAdjacentHTML('beforeend', `<span>${space}</span>`)
             {
                 const span = createTag.span();
                 span.className = this.className.arrow.span;
                 span.onclick = (() => this.nextYear());
                 span.innerText = '';
                 td.appendChild(span);
             }
             tr.appendChild(td);

             this.tagTable.appendChild(tr);
         }

         setMatrix() {
             for (let y = 0; y < 4; y++) {
                 const tr = createTag.tr();
                 this.yearly.slice(y * 3, y * 3 + 3).forEach((m) => {
                     const td = createTag.td();
                     td.className = this.className.month.td;
                     td.style.setProperty('vertical-align', 'top');
                     td.appendChild(m);
                     tr.appendChild(td);
                 });
                 this.tagTable.appendChild(tr);
             }
         }

         // Event

         prevYear() {
             this.moveYear(new Date(this.year - 1, 0, 1));
         }
         nextYear() {
             this.moveYear(new Date(this.year + 1, 0, 1));
         }
         moveYear(date) {
             this.today = new Date();
             this.date = date;
             this.updateCalendar();
         }
         moveToday() {
             this.today = new Date();
             this.date = this.today;
             this.updateCalendar();
         }
     }

     // 15週カレンダー
     class JHWeek15Calendar {
         constructor(date, option) {
             this.today = new Date();
             this.date = date ?? this.today;
             this.option = option ?? {};
             this.getHeader = (this.option.weeks_header ??
                               ((d) => `${d.getFullYear()}${d.getMonth()+1}${d.getDate()}日`));
             const cpfx = this.option.prefix ?? '';

             this.className = {
                 weeks: {
                     table: `${cpfx}month`,
                 },
                 arrow: {
                     td: `${cpfx}marrow`,
                 },
                 header: {
                     td: `${cpfx}whbox`,
                     span: `${cpfx}hinfo`,
                 },

                 weekofday: {
                     td: `${cpfx}wodbox`,
                     div: `${cpfx}weekofday`,
                 },
                 wodheader: [
                     { div: `${cpfx}wsunday` },
                     { div: `${cpfx}wmonday` },
                     { div: `${cpfx}wtuesday` },
                     { div: `${cpfx}wwednesday` },
                     { div: `${cpfx}wthursday` },
                     { div: `${cpfx}wfriday` },
                     { div: `${cpfx}wsaturday` },
                 ],

                 month: [
                     { div: `${cpfx}modd` },
                     { div: `${cpfx}meven` },
                 ],
                 day: {
                     td: `${cpfx}daybox`,
                     div: `${cpfx}day`,
                 },
                 today: {
                     div: `${cpfx}today`,
                 },
                 holiday: {
                     div: `${cpfx}holiday`,
                 },
                 popup: {
                     div: `${cpfx}info`,
                 },
                 other: {
                     div: `${cpfx}other`,
                 },
                 week: [
                     { div: `${cpfx}sunday` },
                     { div: `${cpfx}monday` },
                     { div: `${cpfx}tuesday` },
                     { div: `${cpfx}wednesday` },
                     { div: `${cpfx}thursday` },
                     { div: `${cpfx}friday` },
                     { div: `${cpfx}saturday` },
                 ],
             }

             this.pastWeeks = this.option.past ?? 7;
             this.nextWeeks = this.option.next ?? 7;
             this.totalWeeks = this.pastWeeks + 1 + this.nextWeeks;

             this.tagTable = createTag.table();
             this.tagTable.className = this.className.weeks.table;

             this.setCalendar();
         }

         clearTable() {
             while (this.tagTable.firstChild)
                 this.tagTable.removeChild(this.tagTable.firstChild);
         }

         setCalendar() {
             this.year = this.date.getFullYear();
             this.month = this.date.getMonth();
             const mday = this.date.getDate();
             const wday = this.date.getDay();

             const cday = mday - wday;
             const sday = cday - this.pastWeeks * 7;
             this.weekly = [...Array(this.totalWeeks)].map(
                 (_, y) => [...Array(7)].map((_, x) =>
                     new JHDate(this.year, this.month, sday + y * 7 + x)));

             this.tagHeader = null;
             this.header = null;

             this.setHeader();
             this.setWeek();
             this.setMatrix();
         }

         setHeader() {
             const header = this.getHeader(this.today);
             if (!header || !header.length)
                 return;
             this.header = header + '(本日)';

             const fixed = true;
             const tr = createTag.tr();
             const td = createTag.td();
             this.tagHeader = td;

             td.className = this.className.header.td;
             td.setAttribute('colspan', 7);
             td.innerText = this.header;
             td.onclick = (() => this.moveToday());

             tr.appendChild(td);
             this.tagTable.appendChild(tr);
         }

         setWeek() {
             if (!this.option.weekofday)
                 return;

             const tr = createTag.tr();
             [...'日月火水木金土'].forEach((c, i) => {
                 const td = createTag.td();
                 td.className = this.className.weekofday.td;

                 const cn_weekofday = this.className.weekofday.div;
                 const cn_wodheader = this.className.wodheader[i].div;
                 const div = createTag.div();
                 div.className = `${cn_weekofday} ${cn_wodheader}`.trim();
                 div.innerText = c;

                 td.appendChild(div);
                 tr.appendChild(td);
             });

             this.tagTable.appendChild(tr);
         }

         setMatrix() {
             const today = this.today;
             const tagHeader = this.tagHeader;
             const c_day_td = this.className.day.td;
             const c_day_div = this.className.day.div;
             const c_month = this.className.month;
             const c_today_div = this.className.today.div;
             const c_holiday_div = this.className.holiday.div;
             const c_week = this.className.week;
             const moon = this.option.moonshape;
             const moonshapes = (moon != null) ? moon.length : 0;

             this.weekly.forEach((w, y) => {
                 const tr = createTag.tr();
                 w.forEach((day, x) => {
                     const holiday = day.getHoliday();
                     const td = createTag.td();
                     td.className = this.className.day.td;
                     {
                         const div = createTag.div();
                         const cn_holiday = (holiday ? c_holiday_div : null);
                         const cn_week = c_week[day.getDay()].div;
                         const cn_day = cn_holiday ?? cn_week;
                         const cn_month = c_month[day.getMonth() & 1].div;
                         const cn_today = ((today.getFullYear() == day.getFullYear() &&
                                            today.getMonth() == day.getMonth() &&
                                            today.getDate() == day.getDate())
                                         ? c_today_div : '');
                         div.className = `${c_day_div} ${cn_day} ${cn_month} ${cn_today}`.trim();
                         div.innerText = day.getDate();
                         if (this.header) {
                             div.onmouseenter = (() => this.tagHeader.innerText = holiday ?? this.getHeader(day));
                             div.onmouseleave = (() => this.tagHeader.innerText = this.header);
                         }
                         div.onclick = (() => this.moveDay(day));
                         if (moonshapes > 0) {
                             const phase = new MoonPhaseRC(day + 43200000).phase;
                             const ipos = Math.round(moonshapes * phase / 360) % moonshapes;
                             const url = moon[ipos + (ipos < 0 ? moonshapes : 0)];
                             div.style.backgroundPosition = 'center center';
                             div.style.backgroundRepeat = 'no-repeat';
                             div.style.backgroundSize = 'auto';
                             div.style.backgroundImage = `url(${url})`;
                         }
                         td.appendChild(div);
                     }
                     /**/
                     tr.appendChild(td);
                 });
                 this.tagTable.appendChild(tr);
             });
         }

         updateCalendar() {
             this.clearTable()
             this.setCalendar()
         }

         moveDay(date) {
             this.today = new Date();
             this.date = date;
             this.updateCalendar();
         }
         moveToday() {
             this.today = new Date();
             this.date = this.today;
             this.updateCalendar();
         }
     }

     function onSelect(n) {
         [tagMonthly, tagWeekly, tagYearly].forEach((tag, i) => tag.hidden = (i != n));
         [tagButton0, tagButton1, tagButton2].forEach((tag, i) =>
             tag.style.setProperty('color', (i == n) ? 'red' : 'white'));
     }

     function onLoad(moonshape) {
         const today = new Date();
         const option = {
             prefix: 'jhcal', // クラス名の前置文字.
             // header_format: (y, m) => `${m}月`, // 年月部の文字列.
             // fixed: true, // 月移動しない.
             weekofday: true, // 曜日を表示.
             moonshape: moonshape, // 月相
         }

         tagMonthly.appendChild(new JHMonthlyCalendar(today, option).tagTable);
         tagWeekly.appendChild(new JHWeek15Calendar(today, option).tagTable);
         tagYearly.appendChild(new JHYearlyCalendar(today, option).tagTable);
         onSelect(2);
     }

     window.onload = function() {
         MoonPhaseShape.createPhaseTable({
             width: 24, height: 20,
             light: '#FFFF00A0',
             shadow: '#0000FFA0',
         }).then(onLoad);
     }

    </script>
  </body>
</html>
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0