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

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

(2022年5月1日)彼岸,社日,八十八夜,入梅,二百十日,二百二十日を追加
(2022年4月9日)一粒万倍日の基準となる節月の計算で、境界条件を修正
(2022年4月2日)一粒万倍日の基準となる節月の計算で、太陽黄経の小数点以下を切り上げるようにした
(2022年3月28日)一粒万倍日を追加,表示改良
(2021年8月8日)土用の入り,丑の日を追加
(2021年5月8日)PHP8対応,リファラ・チェック改良

目次

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

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

サンプル・プログラム

圧縮ファイルの内容
tripleCalendar.phpサンプル・プログラム本体。
pahooCalendar.php暦計算クラス pahooCalendar。
暦計算クラスの使い方は「PHPで日出没・月出没・月齢・潮を計算」を参照。include_path が通ったディレクトリに配置すること。

解説:旧暦の計算

1777: /**
1778:  * グレゴオリオ暦=旧暦テーブル 作成
1779:  * @param   int $year 西暦年
1780:  * @return  double 太陽の黄経(視黄経)
1781: */
1782: function makeLunarCalendar($year) {
1783:     //旧暦の2033年問題により、2033年以降はエラーフラグを立てる
1784:     if ($year >= 2033) {
1785:         $this->error = TRUE;
1786:         $this->errmsg = '2033年以降は正しい旧暦計算ができません';
1787:     }
1788: 
1789:     unset($this->tblmoon);
1790:     $this->tblmoon = array();
1791: 
1792:     //前年の冬至を求める
1793:     for ($day = 1; $day <= 31; $day++) {
1794:         $lsun = $this->longitude_sun($year - 1, 12, $day, 0, 0, 0);
1795:         if (floor($lsun / 15.0) > 17)    break;
1796:     }
1797:     $d1 = $day - 1;   //冬至
1798: 
1799:     //翌年の雨水を求める
1800:     for ($day = 1; $day <= 31; $day++) {
1801:         $lsun = $this->longitude_sun($year + 1, 2, $day, 0, 0, 0);
1802:         if (floor($lsun / 15.0) > 22)    break;
1803:     }
1804:     $d2 = $day - 1;   //雨水
1805: 
1806:     //朔の日を求める
1807:     $cnt = 0;
1808:     $dd = $d1;
1809:     $mm = 12;
1810:     $yy = $year - 1;
1811:     while ($yy <= $year + 1) {
1812:         $dm = $this->getDaysInMonth($yy$mm);
1813:         while ($dd <= $dm) {
1814:             $age1 = $this->moon_age($yy$mm$dd,  0 - $this->TDIFF, 0, 0);    //Ver.3.11 bug-fix
1815:             $age2 = $this->moon_age($yy$mm$dd, 23 - $this->TDIFF, 59, 59);  //Ver.3.11 bug-fix
1816:             if ($age2 <= $age1) {
1817:                 $this->tblmoon[$cnt]['year']  = $yy;
1818:                 $this->tblmoon[$cnt]['month'] = $mm;
1819:                 $this->tblmoon[$cnt]['day']   = $dd;
1820:                 $this->tblmoon[$cnt]['age']   = $age1;
1821:                 $this->tblmoon[$cnt]['jd']    = $this->Gregorian2JD($yy$mm$dd, 0, 0, 0);
1822:                 $cnt++;
1823:             }
1824:             $dd++;
1825:         }
1826:         $mm++;
1827:         $dd = 1;
1828:         if ($mm > 12) {
1829:             $yy++;
1830:             $mm = 1;
1831:         }
1832:     }
1833: 
1834:     //二十四節気(中)を求める
1835:     $tblsun = array();
1836:     $cnt = 0;
1837:     $dd = $d1;
1838:     $mm = 12;
1839:     $yy = $year - 1;
1840:     while ($yy <= $year + 1) {
1841:         $dm = $this->getDaysInMonth($yy$mm);
1842:         while ($dd <= $dm) {
1843:             $l1 = $this->longitude_sun($yy$mm$dd,  0, 0, 0);
1844:             $l2 = $this->longitude_sun($yy$mm$dd, 24, 0, 0);
1845:             $n1 = floor($l1 / 15.0);
1846:             $n2 = floor($l2 / 15.0);
1847:             if (($n2 != $n1) && ($n2 % 2 == 0)) {
1848:                 $tblsun[$cnt]['jd'] = $this->Gregorian2JD($yy$mm$dd, 0, 0, 0);
1849:                 $oldmonth = floor($n2 / 2) + 2;
1850:                 if ($oldmonth > 12) $oldmonth -= 12;
1851:                 $tblsun[$cnt]['oldmonth']  = $oldmonth;
1852:                 $cnt++;
1853:             }
1854:             $dd++;
1855:         }
1856:         $mm++;
1857:         $dd = 1;
1858:         if ($mm > 12) {
1859:             $yy++;
1860:             $mm = 1;
1861:         }
1862:     }
1863: 
1864:     //月の名前を決める
1865:     $n1 = count($this->tblmoon);
1866:     $n2 = count($tblsun);
1867:     for ($i = 0; $i < $n1 - 1; $i++) {
1868:         for ($j = 0; $j < $n2$j++) {
1869:             if (($this->tblmoon[$i]['jd'] <= $tblsun[$j]['jd'])
1870:                 && ($this->tblmoon[$i + 1]['jd'] > $tblsun[$j]['jd'])) {
1871:                 $this->tblmoon[$i]['oldmonth'] = $tblsun[$j]['oldmonth'];
1872:                 $this->tblmoon[$i]['oldleap']  = FALSE;
1873:                 $this->tblmoon[$i + 1]['oldmonth'] = $tblsun[$j]['oldmonth'];
1874:                 $this->tblmoon[$i + 1]['oldleap']  = TRUE;
1875:                 break;
1876:             }
1877:         }
1878:     }
1879: }

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

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

1881: /**
1882:  * 旧暦を求める
1883:  * @param   int $year  西暦年
1884:  * @param   int $month 月
1885:  * @param   int $day   日
1886:  * @return  array(旧暦月,日,閏月フラグ)/FALSE:旧暦計算不能
1887: */
1888: function Gregorian2Lunar($year$month$day) {
1889:     //2033年問題チェック
1890:     if ($this->error)    return  FALSE;
1891: 
1892:     $jd = $this->Gregorian2JD($year$month$day, 0, 0, 0);
1893:     $str = '';
1894:     $n1 = count($this->tblmoon);
1895:     for ($i = 0; $i < $n1 - 1; $i++) {
1896:         if ($jd < $this->tblmoon[$i + 1]['jd']) {
1897:             $day = floor($jd - $this->tblmoon[$i]['jd']) + 1;
1898:             $items = array($this->tblmoon[$i]['oldmonth'], $day$this->tblmoon[$i]['oldleap']);
1899:             break;
1900:         }
1901:     }
1902:     return $items;
1903: }

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

この旧暦計算法は天保暦に基づいているが、天保暦には2033年問題がある。西暦2033年(令和15年)10月は旧暦9月だが、11月に入ると旧暦11月と、10月が無くなってしまう問題である。
これは、1844年(天保14年)に天保暦が導入されてから初めて起きる事態だが、天保暦は1872年(明治5年)に廃止されているために正式な解決法は用意されていない。
そこで、2033年(令和15年)以降の旧暦計算はスキップするようにした。

解説:干支と十二支の計算

1924: /**
1925:  * 干支を求める(下請け関数)
1926:  * @param   int $a1 十干の基準値
1927:  * @param   int $a2 十二支の基準値
1928:  * @param   int $n  計算したい値
1929:  * @return  string 干支
1930: */
1931: function __eto($a1$a2$n) {
1932: //十干
1933: static $table1 = array(
1934:  0 =>'',
1935:  1 =>'',
1936:  2 =>'',
1937:  3 =>'',
1938:  4 =>'',
1939:  5 =>'',
1940:  6 =>'',
1941:  7 =>'',
1942:  8 =>'',
1943:  9 =>''
1944: );
1945: 
1946: //十二支
1947: static $table2 = array(
1948:  0 =>'',
1949:  1 =>'',
1950:  2 =>'',
1951:  3 =>'',
1952:  4 =>'',
1953:  5 =>'',
1954:  6 =>'',
1955:  7 =>'',
1956:  8 =>'',
1957:  9 =>'',
1958: 10 =>'',
1959: 11 =>''
1960: );
1961: 
1962:     return $table1[abs($n - $a1) % 10] . $table2[abs($n - $a2) % 12];
1963: }

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

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

ちなみに、干支も十二支も古代中国で考え出されたものだが、十二支の方は、木星が黄道を1周するのがほぼ12年(公転周期11.86年)ということに対応している。古代中国では木星を暦に関わる重要な天体と位置づけており、歳星 (さいせい) という名で呼ばれていた。

解説:土用の判定

0854: /**
0855:  * その日が土用かどうか
0856:  * @param   int $year, $month, $day  グレゴリオ暦による年月日
0857:  * @return  array(季節, 種類)
0858: */
0859: function isDoyo($year$month$day) {
0860:     static $table1 = array('' ,'', '', '');
0861:     static $table2 = array('in'=>'入り', 'ushi'=>'', 'out'=>'明け');
0862:     static $year0 = -1;           //西暦年保存用
0863:     static $doyo = array();      //土用情報保存用
0864: 
0865:     //土用情報を取得
0866:     if ($year0 != $year) {
0867:         $year0 = $year;
0868:         $doyo = $this->getDoyo($year0);
0869:     }
0870: 
0871:     //土用判定
0872:     foreach ($doyo as $key1=>$arr1) {
0873:         foreach ($arr1 as $key2=>$arr2) {
0874:             if (($arr2['year'] == $year0) && ($arr2['month'] == $month) && ($arr2['day'] == $day)) {
0875:                 return array($table1[$key1]$table2[$key2]);
0876:             }
0877:         }
0878:     }
0879:     return array('', '');
0880: }

土用の入り、明け、丑の日の求め方については、その日が土用かどうかを判定する isDoyo メソッドを用意した。

isDoyo メソッドは、まず、「PHPで土用を求める」で作った getDoyo メソッドを呼び出し、その年の土用に関する情報をstatic配列 $doyo にストックしておく。計算量を減らす目的で、次に呼び出されたときも同じ年なら、この配列 $doyo を参照する。

引数として与えられた年月日が土用の入り、明け、丑の日であるかどうか、この配列 $doyo をスキャンして判定する。

解説:彼岸の計算

0882: /**
0883:  * その年の彼岸を求める
0884:  * @param   int $year 西暦年
0885:  * @return  array   [0]['in']['year','month','day']     春の彼岸入り
0886:  *                     ['out']['year','month','day']    春の彼岸明け
0887:  *                  [1]                                 秋の土用の入り/明け
0888: */
0889: function getHigan($year) {
0890:     static $higan = array();     //彼岸情報保存用
0891: 
0892:     //春の彼岸
0893:     $day = $this->getVernalEquinox($year);
0894:     $higan[0]['in']['year']   = (int)$year;
0895:     $higan[0]['in']['month']  = (int)3;
0896:     $higan[0]['in']['day']    = (int)($day - 3);
0897:     $higan[0]['out']['year']  = (int)$year;
0898:     $higan[0]['out']['month'] = (int)3;
0899:     $higan[0]['out']['day']   = (int)($day + 3);
0900: 
0901:     //秋の彼岸
0902:     $day = $this->getAutumnalEquinox($year);
0903:     $higan[1]['in']['year']   = (int)$year;
0904:     $higan[1]['in']['month']  = (int)9;
0905:     $higan[1]['in']['day']    = (int)($day - 3);
0906:     $higan[1]['out']['year']  = (int)$year;
0907:     $higan[1]['out']['month'] = (int)9;
0908:     $higan[1]['out']['day']   = (int)($day + 3);
0909: 
0910:     return $higan;
0911: }

彼岸とは、雑節の1つで、春分の日または秋分の日を中日 (ちゅうにち) として、前後各3日を合わせた各7日間を指す。各々の初日を彼岸(の)入り、最終日を彼岸明けと呼ぶ。

彼岸入り、明けの求め方については、その年の彼岸を求める getHigan メソッドを用意した。カレンダー計算の冒頭で実行する。
まず、getVernalEquinox メソッドで春分の日を求め、そこから3日前を彼岸入りに、3日後を彼岸明けとして登録する。
秋の彼岸については、getAutumnalEquinox メソッドで秋分の日を求め、同様に登録する。

解説:雑節の計算

0913: /**
0914:  * その年の雑節を求める
0915:  * @param   int $year 西暦年
0916:  * @return  array [雑節]['year','month','day']
0917:  *                  雑節‥‥社日,八十八夜,入梅,二百十日,二百二十日
0918:  *                  次の雑節は個別のメソッドを利用する
0919:  *                  節分→ isSetsubun()
0920:  *                  彼岸→ getHigan()
0921:  *                  半夏生→ getSolarTerm72()
0922:  *                  土用→isDoyo()
0923: */
0924: function getZassetsu($year) {
0925:     static $zassetsu = array();      //雑節情報保存用
0926: 
0927:     //社日計算テーブル
0928:     static $table_sha = array(
0929:         '' => +4,
0930:         '' => +3,
0931:         '' => +2,
0932:         '' => +1,
0933:         '' => 0,
0934:         '' => -1,
0935:         '' => -2,
0936:         '' => -3,
0937:         '' => -4,
0938:     );
0939: 
0940:     //春社を求める
0941:     $label = '春社';
0942:     $day = $this->getVernalEquinox($year);        //春分の日
0943:     $month = 3;
0944: 
0945:     $eto = $this->eto_day($year$month$day);
0946:     //癸の日の場合
0947:     if (preg_match('/癸/ui', $eto) > 0) {
0948:         $zassetsu[$label]['year']  = $year;
0949:         $zassetsu[$label]['month'] = $month;
0950:         $l1 = $this->longitude_sun($year$month$day, 12, 0, 0);
0951:         //春分が午前中なら前の戊の日
0952:         if (($l1 < 0) || ($l1 >= 360)) {
0953:             $zassetsu[$label]['day'] = $day - 5;
0954:         //春分が午後なら前の戊の日
0955:         } else {
0956:             $zassetsu[$label]['day'] = $day + 5;
0957:         }
0958:     //それ以外の場合
0959:     } else {
0960:         foreach ($table_sha as $key=>$val) {
0961:             if (preg_match('/' . $key . '/ui', $eto) > 0) {
0962:                 $zassetsu[$label]['year']  = $year;
0963:                 $zassetsu[$label]['month'] = $month;
0964:                 $zassetsu[$label]['day']   = $day + $val;
0965:                 break;
0966:             }
0967:         }
0968:     }
0969: 
0970:     //秋社を求める
0971:     $label = '秋社';
0972:     $day = $this->getAutumnalEquinox($year);      //秋分の日
0973:     $month = 9;
0974:     $eto = $this->eto_day($year$month$day);
0975:     //癸の日の場合
0976:     if (preg_match('/癸/ui', $eto) > 0) {
0977:         $zassetsu[$label]['year']  = $year;
0978:         $zassetsu[$label]['month'] = $month;
0979:         $l1 = $this->longitude_sun($year$month$day, 12, 0, 0);
0980:         //秋分が午前中なら前の戊の日
0981:         if ($l1 <= 180) {
0982:             $zassetsu[$label]['day'] = $day - 5;
0983:         //秋分が午後なら前の戊の日
0984:         } else {
0985:             $zassetsu[$label]['day'] = $day + 5;
0986:         }
0987:     //それ以外の場合
0988:     } else {
0989:         foreach ($table_sha as $key=>$val) {
0990:             if (preg_match('/' . $key . '/ui', $eto) > 0) {
0991:                 $zassetsu[$label]['year']  = $year;
0992:                 $zassetsu[$label]['month'] = $month;
0993:                 $zassetsu[$label]['day']   = $day + $val;
0994:                 break;
0995:             }
0996:         }
0997:     }
0998: 
0999:     //立春を求める
1000:     $month = 2;
1001:     $day   = 1;
1002:     while ($day < 10) {
1003:         if ($this->getSolarTerm($year$month$day) == '立春')       break;
1004:         $day++;
1005:     }
1006:     $ve = $this->AD2JD($year$month$day, 0, 0, 0);
1007: 
1008:     //八十八夜
1009:     $label = '八十八夜';
1010:     list($zassetsu[$label]['year'], $zassetsu[$label]['month'], $zassetsu[$label]['day']) = $this->JD2Gregorian($ve + 87);
1011: 
1012:     //入梅
1013:     $label = '入梅';
1014:     $month = 6;
1015:     $day = 1;
1016:     while ($day < 30) {
1017:         //太陽黄経
1018:         $l2 = $this->longitude_sun($year$month$day, 24, 0, 0);
1019:         if ($l2 >= 80) {
1020:             $zassetsu[$label]['year'] = $year;
1021:             $zassetsu[$label]['month'] = $month;
1022:             $zassetsu[$label]['day'] = $day;
1023:             break;
1024:         }
1025:         $day++;
1026:     }
1027: 
1028:     //二百十日
1029:     $label = '二百十日';
1030:     list($zassetsu[$label]['year'], $zassetsu[$label]['month'], $zassetsu[$label]['day']) = $this->JD2Gregorian($ve + 209);
1031: 
1032:     //二百二十日
1033:     $label = '二百二十日';
1034:     list($zassetsu[$label]['year'], $zassetsu[$label]['month'], $zassetsu[$label]['day']) = $this->JD2Gregorian($ve + 219);
1035: 
1036:     return $zassetsu;
1037: }

雑節のうち、節分isSetsubunメソッドで、彼岸は getHiganメソッドで、半夏生は getSolarTerm72メソッドで、土用は isDoyoメソッドを使って、それぞれ求めることができる。
ここでは、残る社日、八十八夜、入梅、二百十日、二百二十日の求め方を紹介する。

社日は、産土神 (うぶすながみ) を祀る日で、春・秋の2回ある。春のものを春社 (しゅんしゃ) 、秋のものを秋社 (しゅうしゃ) と呼ぶ。
春分に最も近い (つちのえ) の日が社日となる。ただし戊と戊のちょうど中間、つまり (みずのと) の日が秋分の日に重なる場合は、春分の瞬間が午前中ならば前の戊の日、午後ならば後の戊の日とする。
秋分の日についても同様にして求める。

入梅 (にゅうばい) は太陽黄経が80度の日である。

立春を第1日目として、八十八夜は88日目、二百十日は210日目、二百二十日は220日目を、それぞれ指す。

これらの雑節を求めるのが getZassetsu メソッドである。カレンダー計算の冒頭で実行する。
上記で説明した雑節の求め方をプログラムに実装した。

解説:一粒万倍日

一粒万倍日 (いちりゅうまんばいび) とは、種籾 (たねもみ) 一粒から何万倍ものお米が穫れるという意味で、古くから吉日のひとつにされてきた。種まき、開店、出資など、何かを始めるのに最適な日とされている。逆に、苦労の種が万倍になって返ってくる事から、借金をしたり、物を借りてはいけない日とされる。
1685年(貞享2年)に渋川春海が編纂した貞享暦から外されたが、近年、宝くじを買うのに良い日と宣伝され、再び脚光を浴びるようになった。

一粒万倍日は、節月と日の干支の組み合わせで決まる。
組み合わせ(計算方式)は2つあり、下表に整理する。
節月 二十四節気 計算方式I 計算方式II
1 立春~啓蟄 丑・午
2 啓蟄~清明 酉・寅
3 清明~立夏 子・卯
4 立夏~芒種 卯・辰
5 芒種~小暑 巳・午
6 小暑~立秋 酉・午
7 立秋~白露 子・未
8 白露~寒露 卯・申
9 寒露~立冬 酉・午
10 立冬~大雪 酉・戌
11 大雪~小寒 亥・子
12 小寒~立春 卯・子

0631: /**
0632:  * その日の節月を求める
0633:  * @param   int $year, $month, $day グレゴリオ暦による年月日
0634:  * @return  int 節月/0:計算失敗
0635: */
0636: function getSetsugetsu($year$month$day) {
0637:     static $table = array(
0638:   1 => 315,
0639:   2 => 345,
0640:   3 =>  15,
0641:   4 =>  45,
0642:   5 =>  75,
0643:   6 => 105,
0644:   7 => 135,
0645:   8 => 165,
0646:   9 => 195,
0647:  10 => 225,
0648:  11 => 255,
0649:  12 => 285,
0650:  13 => 315
0651: );
0652: 
0653:     //太陽黄経
0654:     $ls = $this->longitude_sun($year$month$day, 24, 0, 0);
0655: 
0656:     //節月
0657:     foreach ($table as $key=>$val) {
0658:         if ($key == 2) {
0659:             if (($ls >= $table[2]|| ($ls < $table[3]))    return $key;
0660:         } else {
0661:             if (($ls >= $table[$key]) && ($ls < $table[$key + 1]))    return $key;
0662:         }
0663:     }
0664:     return 0;
0665: }

ユーザー関数 getSetsugetsu は、西暦年月日を引数として、その日の節月を求める。既存の暦に合うよう太陽黄経の小数点以下を切り上げるようにしているが、切り上げに関する根拠がないのでご留意いただきたい。

1998: /**
1999:  * 一粒万倍日かどうか
2000:  * @param   int $year, $month, $day グレゴリオ暦による年月日
2001:  * @param   int $method 計算方式(1または2)(省略可能:デフォルトは1)
2002:  * @return  bool TRUE:一粒万倍日/FALSE:ではない
2003: */
2004: function ichiryumanbai($year$month$day$method=1) {
2005:     //計算方式I
2006:     $table[1] = array(
2007:  1 => array('', ''),
2008:  2 => array('', ''),
2009:  3 => array('', ''),
2010:  4 => array('', ''),
2011:  5 => array('', ''),
2012:  6 => array('', ''),
2013:  7 => array('', ''),
2014:  8 => array('', ''),
2015:  9 => array('', ''),
2016: 10 => array('', ''),
2017: 11 => array('', ''),
2018: 12 => array('', ''),
2019: );
2020:     //計算方式II
2021:     $table[2] = array(
2022:  1 => array(''),
2023:  2 => array(''),
2024:  3 => array(''),
2025:  4 => array(''),
2026:  5 => array(''),
2027:  6 => array(''),
2028:  7 => array(''),
2029:  8 => array(''),
2030:  9 => array(''),
2031: 10 => array(''),
2032: 11 => array(''),
2033: 12 => array(''),
2034: );
2035: 
2036:     //計算方式のチェック
2037:     if (($method != 1) && ($method != 2)) return FALSE;
2038: 
2039:     //節月を求める
2040:     $setsu = $this->getSetsugetsu($year$month$day);
2041:     //干支の右1文字取得
2042:     $eto = mb_substr($this->eto_day($year$month$day), 1, 1);
2043: 
2044:     return in_array($eto$table[$method][$setsu]);
2045: }

ユーザー関数 ichiryumanbai は、西暦年月日を引数として、getSetsugetsuにより節月を計算し、eto_day で求めたその日の干支に合致するかどうかを調べ、一粒万倍日かどうかを判定する。

解説:カレンダー作成

0187: /**
0188:  * 1ヶ月分のカレンダーを作成
0189:  * @param   object $pcl  pahooCalendarオブジェクト
0190:  * @param   int $start 週の開始曜日(0:日曜日, 1:月曜日...6:土曜日)
0191:  * @param   int $year  西暦年
0192:  * @param   int $month 月
0193:  * @return  string HTMLコンテンツ/FALSE:エラー
0194: */
0195: function makeCalendar($pcl$start$year$month) {
0196:     //雑節テーブル
0197:     $table_za = array('春社', '秋社', '八十八夜', '入梅', '二百十日', '二百二十日');
0198: 
0199:     //月のエラーチェック
0200:     if ($month < 1 && $month > 12)      return FALSE;
0201: 
0202:     $eto_year  = $pcl->eto_year($year);
0203:     $eto_month = $pcl->eto_month($year$month);
0204: 
0205:     //彼岸
0206:     $higan = $pcl->getHigan($year);
0207:     //雑節
0208:     $zassetsu = $pcl->getZassetsu($year);
0209: 
0210: $html =<<< EOT
0211: <table class="calendar">
0212: <tr>
0213: <th colspan="7"><span class="large">{$year}年({$eto_year}) {$month}月({$eto_month})</span></th>
0214: </tr>
0215: <tr>
0216: 
0217: EOT;
0218: 
0219:     //曜日の行
0220:     for ($i = 0; $i < 7; $i++) {
0221:         $n  = ($start + $i) % 7;
0222:         if ($n == 6)        $class = 'blue';
0223:         else if ($n == 0)   $class = 'red';
0224:         else                $class = 'black';
0225:         $str = $pcl->__getWeekString($n);
0226:         $html .= "<th><span class=\"{$class}\">{$str}</span></th>";
0227:     }
0228:     $html .= "</tr>\n";
0229: 
0230:     //カレンダー本体
0231:     $wn1 = $pcl->getWeekNumber($year$month, 1); //月の最初の曜日
0232:     $dim = $pcl->getDaysInMonth($year$month);       //月の最後の日
0233:     $cnt = 0;
0234:     $flag = FALSE;
0235:     $n = $start;
0236:     $day = 1;
0237:     while (1) {
0238:         if ($cnt % 7 == 0)      $html .= "<tr>\n";
0239:         //曜日の色
0240:         if ($n % 7 == 6)        $class = 'blue';
0241:         else if ($n % 7 == 0)   $class = 'red';
0242:         else                    $class = 'black';
0243:         if ($n % 7 == $wn1)     $flag = TRUE;
0244:         //表示開始
0245:         if ($flag) {
0246:             $ss = '';
0247:             //祝日
0248:             $holiday = $pcl->getHoliday($year$month$day);
0249:             if ($holiday != '') {
0250:                 $ss .= "<span class=\"small red\">{$holiday}<br /></span>\n";
0251:             }
0252:             //二十四節気
0253:             $solarterm = $pcl->getSolarTerm($year$month$day);
0254:             //土用
0255:             list($ss1$ss2) = $pcl->isDoyo($year$month$day);
0256:             if ($ss2 == '明け') {
0257:                 $ss2 = '';
0258:             } else if (($ss1 != '') && ($ss2 == '')) {
0259:                 $ss2 = '';
0260:             }
0261:             if ($ss1 != '' && $ss2 != '') {
0262:                 if ($solarterm != '') {
0263:                     $solarterm .= '<br />土用の' .  $ss2;
0264:                 } else {
0265:                     $solarterm = '土用の' . $ss2;
0266:                 }
0267:             }
0268:             if ($solarterm != '') {
0269:                 $ss .= "<span class=\"small\">{$solarterm}<br /></span>\n";
0270:             }
0271:             //七十二候
0272:             $solarterm72 = $pcl->getSolarTerm72($year$month$day);
0273:             if ($solarterm72 != '') {
0274:                 $ss .= "<span class=\"small\">{$solarterm72}<br /></span>\n";
0275:             }
0276:             //彼岸
0277:             if (($higan[0]['in']['month'] == $month) && ($higan[0]['in']['day'] == $day)) {
0278:                 $solarterm = '彼岸入り';
0279:                 $ss .= "<span class=\"small\">彼岸入り<br /></span>\n";
0280:             } else if (($higan[0]['out']['month'] == $month) && ($higan[0]['out']['day'] == $day)) {
0281:                 $solarterm = '彼岸明け';
0282:                 $ss .= "<span class=\"small\">彼岸明け<br /></span>\n";
0283:             } else if (($higan[1]['in']['month'] == $month) && ($higan[1]['in']['day'] == $day)) {
0284:                 $solarterm = '彼岸入り';
0285:                 $ss .= "<span class=\"small\">彼岸入り<br /></span>\n";
0286:             } else if (($higan[1]['out']['month'] == $month) && ($higan[1]['out']['day'] == $day)) {
0287:                 $solarterm = '彼岸明け';
0288:                 $ss .= "<span class=\"small\">彼岸明け<br /></span>\n";
0289:             }
0290:             //雑節
0291:             foreach ($table_za as $label) {
0292:                 if (($zassetsu[$label]['month'] == $month) && ($zassetsu[$label]['day'] == $day)) {
0293:                     $solarterm = $label;
0294:                     $ss .= "<span class=\"small\">{$label}<br /></span>\n";
0295:                 }
0296:             }
0297:             //一粒万倍日
0298:             $manbai = $pcl->ichiryumanbai($year$month$day, 1) ? '一粒万倍日' : '';
0299:             if ($manbai != '') {
0300:                 $ss .= "<span class=\"small\">{$manbai}<br /></span>\n";
0301:             }
0302:             //旧暦
0303:             list($oldmonth$oldday$oldleap) = $pcl->Gregorian2Lunar($year$month$day);
0304:             if (!$pcl->error) {
0305:                 $oldleap = $oldleap ? '' : '';
0306:                 $rokuyou = $pcl->rokuyou($oldmonth$oldday);
0307:                 $oldcal = sprintf('%s%d月%d日<br />(%s)', $oldleap$oldmonth$oldday$rokuyou);
0308:             //2033年問題回避
0309:             } else {
0310:                 $oldcal = '';
0311:             }
0312:             //日の干支
0313:             $eto_day = $pcl->eto_day($year$month$day);
0314: 
0315:             //表示
0316: $html .=<<< EOT
0317: <td>
0318: <span class="large {$class}">{$day}</span>
0319: <div class="narrow">
0320: <span class="small red">{$holiday}</span><br />
0321: <span class="small blue">{$manbai}</span><br />
0322: <span class="small">{$solarterm}</span><br />
0323: <span class="small">{$solarterm72}</span><br />
0324: <span class="small">{$eto_day}</span><br />
0325: <span class="small">{$oldcal}</span>
0326: </div>
0327: </td>
0328: 
0329: EOT;
0330:             $day++;
0331:             if ($day > $dim)    break;
0332:         } else {
0333:             $html .= "<td>&nbsp;</td>";
0334:         }
0335:         $cnt++;
0336:         $n++;
0337:         if ($cnt % 7 == 0)  $html .= "</tr>\n";
0338:     }
0339:     //最後の日以降の処理
0340:     $cnt++;
0341:     while ($cnt % 7 != 0) {
0342:         $html .= "<td>&nbsp;</td>";
0343:         $cnt++;
0344:         if ($cnt % 7 == 0)  $html .= "</tr>\n";
0345:     }
0346: 
0347:     $html .= "</table>\n";
0348: 
0349:     return $html;
0350: }

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

カレンダー本体を作成する処理では、まず、月の最初の曜日 $wn1 と、月の最後の日 $dim を計算しておく。
$wn1 と週の開始曜日 $start が一致するまでは変数 $flag がFALSEのままで、この時は日付を入れずに空のままにしておく。
$flag がTRUEになったら日付を入れてゆく。あわせて、二十四節気、旧暦の計算を行う。
土用の判定については、四季の土用の入りと丑の日のみを表示するようにしている。土用の明けは、立春、立夏、立秋、立冬の前日であるから、二十四節気の表示からわかるからだ。
$dim に達したらループを抜け出し、行末まで空白のセルを追加してゆく。

質疑応答

【質問】
「PHPで3ヶ月カレンダーを作る」のページのソースなのですが「サンプル・プログラムの解説:カレンダー作成」のソースの0171行目に「<tr>」の開始タグを入れたほうがいいと思います。曜日部分の行部分TRの開始タグが無いようです。
間違えであればすいません。
【回答】
ご指摘ありがとうございます。<tr> タグが抜けていました。追加しました。

活用例

みんなの知識 ちょっと便利帳」では、このサンプル・プログラムを活用し、「二十四節気・七十二候・干支・旧暦・六曜カレンダー」「一粒万倍日を調べる〈タイプ1〉」「一粒万倍日を調べる〈タイプ2〉」というサービスを提供している。また、「歴代天皇の誕生日が祝日に!?」というユニークなサービスも提供している。ありがとうございます。

参考サイト

一粒万倍日を調べる〈タイプ2〉:みんなの知識 ちょっと便利帳

参考書籍

表紙 知れば知るほど面白い暦の謎
著者 片山 真人
出版社 三笠書房
サイズ 文庫
発売日 2022年02月17日頃
価格 858円(税込)
ISBN 9784837987635
1週間はなぜ「7日」なのか?「曜日」はどのように生まれたか?国立天文台「暦の専門家」が教える!
 
(この項おわり)
header