PHPで3ヶ月カレンダーを作る

(1/1)
PHP を使って、今月から再来月までの 3 ヶ月カレンダーを作る。
PHP で祝日を求める」「PHP で二十四節気一覧を作成」に、七十二候や旧暦、六曜、干支の計算を加えた。

(2019 年 2 月 9 日)pahooCalendar::makeLunarCalendar の不具合を修正、getSolarTerm72 の漢字表記を変更した。

サンプル・プログラムの実行例

PHPで3ヶ月カレンダーを作る

サンプル・プログラム

各種暦計算、太陽や月の位置計算は、クラス pahooCalendar として分離している。
コマンドラインから、year(西暦年),month(月)を直接指定してやると、カレンダーだけを表示する。

サンプル・プログラムの解説:旧暦の計算

1365: /**
1366:  * グレゴオリオ暦=旧暦テーブル 作成
1367:  * @param int $year 西暦年
1368:  * @return double 太陽の黄経(視黄経)
1369: */
1370: function makeLunarCalendar($year) {
1371:     unset($this->tblmoon);
1372:     $this->tblmoon = array();
1373: 
1374:     //前年の冬至を求める
1375:     for ($day = 1; $day <= 31; $day++) {
1376:         $lsun = $this->longitude_sun($year - 1, 12, $day, 0, 0, 0);
1377:         if (floor($lsun / 15.0) > 17)    break;
1378:     }
1379:     $d1 = $day - 1;   //冬至
1380: 
1381:     //翌年の雨水を求める
1382:     for ($day = 1; $day <= 31; $day++) {
1383:         $lsun = $this->longitude_sun($year + 1, 2, $day, 0, 0, 0);
1384:         if (floor($lsun / 15.0) > 22)    break;
1385:     }
1386:     $d2 = $day - 1;   //雨水
1387: 
1388:     //朔の日を求める
1389:     $cnt = 0;
1390:     $dd = $d1;
1391:     $mm = 12;
1392:     $yy = $year - 1;
1393:     while ($yy <= $year + 1) {
1394:         $dm = $this->getDaysInMonth($yy$mm);
1395:         while ($dd <= $dm) {
1396:             $age1 = $this->moon_age($yy$mm$dd,  0 - $this->TDIFF, 0, 0);    //Ver.3.11 bug-fix
1397:             $age2 = $this->moon_age($yy$mm$dd, 23 - $this->TDIFF, 59, 59);  //Ver.3.11 bug-fix
1398:             if ($age2 <= $age1) {
1399:                 $this->tblmoon[$cnt]['year']  = $yy;
1400:                 $this->tblmoon[$cnt]['month'] = $mm;
1401:                 $this->tblmoon[$cnt]['day']   = $dd;
1402:                 $this->tblmoon[$cnt]['age']   = $age1;
1403:                 $this->tblmoon[$cnt]['jd']    = $this->Gregorian2JD($yy$mm$dd, 0, 0, 0);
1404:                 $cnt++;
1405:             }
1406:             $dd++;
1407:         }
1408:         $mm++;
1409:         $dd = 1;
1410:         if ($mm > 12) {
1411:             $yy++;
1412:             $mm = 1;
1413:         }
1414:     }
1415: 
1416:     //二十四節気(中)を求める
1417:     $tblsun = array();
1418:     $cnt = 0;
1419:     $dd = $d1;
1420:     $mm = 12;
1421:     $yy = $year - 1;
1422:     while ($yy <= $year + 1) {
1423:         $dm = $this->getDaysInMonth($yy$mm);
1424:         while ($dd <= $dm) {
1425:             $l1 = $this->longitude_sun($yy$mm$dd,  0, 0, 0);
1426:             $l2 = $this->longitude_sun($yy$mm$dd, 24, 0, 0);
1427:             $n1 = floor($l1 / 15.0);
1428:             $n2 = floor($l2 / 15.0);
1429:             if (($n2 != $n1) && ($n2 % 2 == 0)) {
1430:                 $tblsun[$cnt]['jd'] = $this->Gregorian2JD($yy$mm$dd, 0, 0, 0);
1431:                 $oldmonth = floor($n2 / 2) + 2;
1432:                 if ($oldmonth > 12) $oldmonth -= 12;
1433:                 $tblsun[$cnt]['oldmonth']  = $oldmonth;
1434:                 $cnt++;
1435:             }
1436:             $dd++;
1437:         }
1438:         $mm++;
1439:         $dd = 1;
1440:         if ($mm > 12) {
1441:             $yy++;
1442:             $mm = 1;
1443:         }
1444:     }
1445: 
1446:     //月の名前を決める
1447:     $n1 = count($this->tblmoon);
1448:     $n2 = count($tblsun);
1449:     for ($i = 0; $i < $n1 - 1; $i++) {
1450:         for ($j = 0; $j < $n2$j++) {
1451:             if (($this->tblmoon[$i]['jd'] <= $tblsun[$j]['jd'])
1452:                 && ($this->tblmoon[$i + 1]['jd'] > $tblsun[$j]['jd'])) {
1453:                 $this->tblmoon[$i]['oldmonth'] = $tblsun[$j]['oldmonth'];
1454:                 $this->tblmoon[$i]['oldleap']  = FALSE;
1455:                 $this->tblmoon[$i + 1]['oldmonth'] = $tblsun[$j]['oldmonth'];
1456:                 $this->tblmoon[$i + 1]['oldleap']  = TRUE;
1457:                 break;
1458:             }
1459:         }
1460:     }
1461: }
1462: 
1463: /**
1464:  * 旧暦を求める
1465:  * @param int $year  西暦年
1466:  * @param int $month 月
1467:  * @param int $day   日
1468:  * @return array(旧暦月,日,閏月フラグ)
1469: */
1470: function Gregorian2Lunar($year$month$day) {
1471:     $jd = $this->Gregorian2JD($year$month$day, 0, 0, 0);
1472:     $str = '';
1473:     $n1 = count($this->tblmoon);
1474:     for ($i = 0; $i < $n1 - 1; $i++) {
1475:         if ($jd < $this->tblmoon[$i + 1]['jd']) {
1476:             $day = floor($jd - $this->tblmoon[$i]['jd']) + 1;
1477:             $items = array($this->tblmoon[$i]['oldmonth'], $day$this->tblmoon[$i]['oldleap']);
1478:             break;
1479:         }
1480:     }
1481:     return $items;
1482: }

グレゴリオ暦から旧暦を求める手順は、「旧暦と六曜を作りましょう」を参考にした。

変換のための方程式を導出できないため、あらかじめ計算したい西暦年から変換テーブルを作る必要がある。これを行うのがユーザー関数 makeLunarCalendar である。
  1. 前年の冬至を求める。
  2. 翌年の雨水を求める。
  3. 1~2 の期間中の二十四節気(中)を配列 $tblsun に格納する。
  4. 朔日と中を比較して、月の名前と閏月を決めてゆく。


変換テーブルが用意できたら、ユーザー関数 Gregorian2Lunar を使って旧暦を求める。

この旧暦計算法は天保暦に基づいているが、天保暦には 2033 年問題がある。西暦 2033 年 10 月は旧暦 9 月だが、11 月に入ると旧暦 11 月と、10 月が無くなってしまう問題である。
これは、1844 年(天保 14 年)に天保暦が導入されてから初めて起きる事態だが、天保暦は 1872 年(明治 5 年)に廃止されているために正式な解決法は用意されていない。
いくつかの解決案が提示されているが、本プログラムには組み込んでいない。

サンプル・プログラムの解説:干支の計算

1503: /**
1504:  * 干支を求める(下請け関数)
1505:  * @param int $a1 十干の基準値
1506:  * @param int $a2 十二支の基準値
1507:  * @param int $n  計算したい値
1508:  * @return string 干支
1509: */
1510: function __eto($a1$a2$n) {
1511: //十干
1512: static $table1 = array(
1513:  0 =>'',
1514:  1 =>'',
1515:  2 =>'',
1516:  3 =>'',
1517:  4 =>'',
1518:  5 =>'',
1519:  6 =>'',
1520:  7 =>'',
1521:  8 =>'',
1522:  9 =>''
1523: );
1524: 
1525: //十二支
1526: static $table2 = array(
1527:  0 =>'',
1528:  1 =>'',
1529:  2 =>'',
1530:  3 =>'',
1531:  4 =>'',
1532:  5 =>'',
1533:  6 =>'',
1534:  7 =>'',
1535:  8 =>'',
1536:  9 =>'',
1537: 10 =>'',
1538: 11 =>''
1539: );
1540: 
1541:     return $table1[abs($n - $a1) % 10] . $table2[abs($n - $a2) % 12];
1542: }
1543: 
1544: /**
1545:  * 年の干支を求める
1546:  * @param int $year  年
1547:  * @return string 干支
1548: */
1549: function eto_year($year) {
1550:     return $this->__eto(1904, 1900, $year);
1551: }
1552: 
1553: /**
1554:  * 月の干支を求める
1555:  * @param int $year  年
1556:  * @param int $month 月
1557:  * @return string 干支
1558: */
1559: function eto_month($year$month) {
1560:     return $this->__eto(0, 0, ($year - 1903) * 12 + $month - 11);
1561: }
1562: 
1563: /**
1564:  * 日の干支を求める
1565:  * @param int $year  年
1566:  * @param int $month 月
1567:  * @param int $day   日
1568:  * @return string 干支
1569: */
1570: function eto_day($year$month$day) {
1571:     $j1 = $this->Gregorian2JD(1902, 4, 11, 0, 0, 0);
1572:     $j2 = $this->Gregorian2JD($year$month$day, 0, 0, 0);
1573: 
1574:     return $this->__eto(0, 0, $j2 - $j1);
1575: }

干支には、年の干支月の干支日の干支 の 3種類がある。
干支は、甲・乙・丙・丁・戊・己・庚・辛・壬・癸の 10要素からなる十干 (じっかん) と、子・丑・寅・卯・辰・巳・午・未・申・酉・戌・亥の 12要素からなる十二支 (じゅうにし) の組み合わせからなる。
つまり、十干は年/月/日が 10 回ごとに繰り返され、十二支は年/月/日が 12 回ごとに繰り返されることになる。これを計算するのが __eto である。

次に、基準となる年月日を調べておく。
甲の年は西暦 1904 年(明治 37 年)、子の年は西暦 1900 年(明治 33 年)である。年の干支は、この 2 つを基準に計算する。
甲子の月は 1903 年(明治 36 年)11 月である。月の干支は、ここを基準に計算する。
甲子の日は 1902 年(明治 35 年)4 月 11 日である。日の干支は、ここを基準に計算する。

サンプル・プログラムの解説:カレンダー作成

0152: /**
0153:  * 1ヶ月分のカレンダーを作成
0154:  * @param object $pcl  pahooCalendarオブジェクト
0155:  * @param int $start 週の開始曜日(0:日曜日, 1:月曜日...6:土曜日)
0156:  * @param int $year  西暦年
0157:  * @param int $month 月
0158:  * @return string HTMLコンテンツ/FALSE:エラー
0159: */
0160: function makeCalendar($pcl$start$year$month) {
0161:     if ($month < 1 && $month > 12)      return FALSE;
0162: 
0163:     $eto_year  = $pcl->eto_year($year);
0164:     $eto_month = $pcl->eto_month($year$month);
0165: 
0166: $html =<<< EOT
0167: <table class="calendar">
0168: <tr>
0169: <th colspan="7"><span class="large">{$year}年({$eto_year}) {$month}月({$eto_month})</span></th>
0170: </tr>
0171: 
0172: EOT;
0173: 
0174:     //曜日の行
0175:     for ($i = 0; $i < 7; $i++) {
0176:         $n  = ($start + $i) % 7;
0177:         if ($n == 6)        $class = 'blue';
0178:         else if ($n == 0)   $class = 'red';
0179:         else                $class = 'black';
0180:         $str = $pcl->__getWeekString($n);
0181:         $html .= "<th><span class=\"{$class}\">{$str}</span></th>";
0182:     }
0183:     $html .= "</tr>\n";
0184: 
0185:     //カレンダー本体
0186:     $wn1 = $pcl->getWeekNumber($year$month, 1); //月の最初の曜日
0187:     $dim = $pcl->getDaysInMonth($year$month);   //月の最後の日
0188:     $cnt = 0;
0189:     $flag = FALSE;
0190:     $n = $start;
0191:     $day = 1;
0192:     while (1) {
0193:         if ($cnt % 7 == 0)      $html .= "<tr>\n";
0194:         //曜日の色
0195:         if ($n % 7 == 6)        $class = 'blue';
0196:         else if ($n % 7 == 0)   $class = 'red';
0197:         else                    $class = 'black';
0198:         if ($n % 7 == $wn1$flag = TRUE;
0199:         //表示開始
0200:         if ($flag) {
0201:             //祝日
0202:             $holiday = $pcl->getHoliday($year$month$day);
0203:             if ($holiday != '') $class = 'red';
0204:             $holiday = ($holiday == FALSE) ? '<br />' :
0205:                 "<span class=\"small red\">{$holiday}</span><br />";
0206:             //二十四節気
0207:             $solarterm = $pcl->getSolarTerm($year$month$day);
0208:             $solarterm = ($solarterm == '') ? '<br />' :
0209:                 "<span class=\"small\">{$solarterm}</span><br />";
0210:             //七十二候
0211:             $solarterm72 = $pcl->getSolarTerm72($year$month$day);
0212:             $solarterm72 = ($solarterm72 == '') ? '<br />' :
0213:                 "<span class=\"small\">{$solarterm72}</span><br />";
0214:             //旧暦
0215:             list($oldmonth$oldday$oldleap) = $pcl->Gregorian2Lunar($year$month$day);
0216:             $oldleap = $oldleap ? '' : '';
0217:             $rokuyou = $pcl->rokuyou($oldmonth$oldday);
0218:             $oldcal = sprintf('%s%d月%d日<br />(%s)', $oldleap$oldmonth$oldday$rokuyou);
0219:             //日の干支
0220:             $eto_day = $pcl->eto_day($year$month$day);
0221:             //表示
0222: $html .=<<< EOT
0223: <td>
0224: <span class="large {$class}">{$day}</span><br />
0225: {$holiday}
0226: {$solarterm}
0227: {$solarterm72}
0228: <span class="small">{$eto_day}<br /></span>
0229: <span class="small">{$oldcal}</span>
0230: </td>
0231: 
0232: EOT;
0233:             $day++;
0234:             if ($day > $dim)    break;
0235:         } else {
0236:             $html .= "<td>&nbsp;</td>";
0237:         }
0238:         $cnt++;
0239:         $n++;
0240:         if ($cnt % 7 == 0)  $html .= "</tr>\n";
0241:     }
0242:     //最後の日以降の処理
0243:     $cnt++;
0244:     while ($cnt % 7 != 0) {
0245:         $html .= "<td>&nbsp;</td>";
0246:         $cnt++;
0247:         if ($cnt % 7 == 0)  $html .= "</tr>\n";
0248:     }
0249: 
0250:     $html .= "</table>\n";
0251: 
0252:     return $html;
0253: }

ユーザー関数 makeCalendar は 1 ヶ月分のカレンダーを作成する。
週の開始曜日を自由に設定できるようにするための工夫をしている。

カレンダー本体を作成する処理では、まず、月の最初の曜日 $wn1 と、月の最後の日 $dim を計算しておく。
$wn1 と週の開始曜日 $start が一致するまでは変数 $flag が FALSE のままで、この時は日付を入れずに空のままにしておく。
$flag が TRUE になったら日付を入れてゆく。あわせて、二十四節気、旧暦の計算を行う。

$dim に達したらループを抜け出し、行末まで空白のセルを追加してゆく。

活用例

二十四節気・七十二候・干支・旧暦・六曜カレンダー:みんなの知識 ちょっと便利帳」では、このサンプル・プログラムを活用している。ありがとうございます。

参考サイト

(この項おわり)
header