PHPで月齢を計算

(1/1)
月齢 (げつれい) とは、直前の朔(新月)の瞬間を "0" として、そこからの経過日数を表す数字だ。
月齢は、太陽と月の位置関係から計算できる。今回は、PHPで月の視黄経を算出するプログラムを作り、指定した月から3ヶ月分の月齢一覧を求めることを目標にする。

(2023年1月14日)計算精度を向上した。calcMoonAge()で時刻指定可能にした。数値入力にSpinner、pahooInputDataを導入した。

目次

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

PHPで月齢を計算

サンプル・プログラム

圧縮ファイルの内容
getMoonAge.phpサンプル・プログラム本体。
pahooCalendar.php暦・潮位計算クラス pahooCalendar。
暦・潮位計算クラスの使い方は「PHPで二十四節気・七十二候一覧を作成」「PHPで月齢を計算」「PHPで日出没・月出没・月齢・潮を計算」「PHPで潮位を計算する」などを参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
getMoonAge.php 更新履歴
バージョン 更新日 内容
1.6.0 2023/01/14 数値入力にSpinner、pahooInputData導入
1.5.0 2023/01/11 計算精度向上,calcMoonAge()で時刻指定可能に
1.4 2021/05/08 PHP8対応,リファラ・チェック改良
1.3 2020/01/02 リファラチェック追加
1.2 2018/01/03 pahooCalendarクラス利用
pahooCalendar.php 更新履歴
バージョン 更新日 内容
4.5.0 2024/03/17 ヒジュラ暦メソッドを追加
4.4.1 2024/03/17 getCabinetOfficeHolidayTable() -- bug-fix
4.4.0 2024/02/25 内閣府の祝日表を参照できるようにした
4.3.2 2023/02/11 getSolarTerm72() 表記改訂:水澤腹堅→水沢腹堅
4.3.1 2023/02/03 表記改訂:バクムーン→バックムーン,スタージャンムーン→スタージョンムーン,七十二候
pahooInputData.php 更新履歴
バージョン 更新日 内容
1.5.0 2024/01/28 exitIfExceedVersion() 追加
1.4.2 2024/01/28 exitIfLessVersion() メッセージ修正
1.4.1 2023/09/30 コメントの訂正
1.4.0 2023/09/09 $_GET, $_POST参照をfilter_input()関数に置換
1.3.0 2023/07/11 roundFloat() 追加

準備:初期値など

  36: //指定できる西暦年の範囲
  37: define('MIN_YEAR', 1901);
  38: define('MAX_YEAR', 2099);
  39: 
  40: //世界時からの時差(日本標準時)
  41: define('UTCDIFF', +9.0);
  42: 
  43: //計算期間(月)
  44: define('CALC_TERM', 3);
  45: 
  46: //月齢を計算する時刻
  47: define('CALC_HOUR', 21);
  48: 
  49: //Spinner - jQuery UI を使用するかどうか
  50: define('USESPINNER', TRUE);
  51: 
  52: //数値増減クリックで即判定するかどうか(TRUE:即判定,FALE:判定ボタンを用意)
  53: define('ONCHANGE', FALSE);
  54: 
  55: //表示幅(単位:ピクセル)
  56: define('WIDTH', 550);
  57: 
  58: //require_once()で呼ぶファイルはinclude_pathが通っているフォルダに配置すること.
  59: //暦計算クラス
  60: require_once('pahooCalendar.php');
  61: 
  62: //データ入力に関わる関数群
  63: require_once('pahooInputData.php');

あらかじめ、計算期間(月)と世界時からの時差を定数として定義しておく。

月齢、月の視半径、各種カレンダー計算は、ユーザークラス "pahooCalendar" に用意したメソッド群を利用する。
オブジェクト生成時に、表示言語として "ja"(日本語)を、世界時との時差としてユーザー定義の定数 UTCDIFF を渡しておく。こうすることで、pahooCalendar クラスが出力する文字列は日本語に、日時計算は時差 UTCDIFF がある前提で計算を行う。

数値入力に jQuery UISpinner を使用している。設定でOFFにすることもできる。Spinner の使い方については、「Spinner - jQuery UI - PHPで素数かどうか判定」をご覧いただきたい。

入力した数値の取得とバリデーションチェックに、ユーザー定義クラス群 "pahooInputData.php" を利用している。

月齢とは

月の位置と月齢
PHPで二十四節気・七十二候一覧を作成」で黄道座標系(黄経黄緯)を説明した。
月の位置を黄経黄緯で示すとき、太陽の黄経が0度であれば朔(新月)となる。朔の瞬間を月齢0.0とし、そこから1日後(24時間後)は1.0、2日後は2.0‥‥とカウントしていく。
そこで、今回は月の視黄経を求める必要がある。

解説:月の視黄経の算出

1458: /**
1459:  * J2000.0からの経過年数における月の視黄経(度)を求める。
1460:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-44-01.shtm
1461:  * @param   float $jy 2000.0からの経過年数
1462:  * @return  float 月の視黄経(度)
1463: */
1464: function __longitude_moon($jy) {
1465:     $am  = 0.0006 * sin(deg2rad($this->__angle54.0 + 19.3  * (float)$jy)));
1466:     $am +0.0006 * sin(deg2rad($this->__angle71.0 +  0.2  * (float)$jy)));
1467:     $am +0.0020 * sin(deg2rad($this->__angle55.0 + 19.34 * (float)$jy)));
1468:     $am +0.0040 * sin(deg2rad($this->__angle(119.5 +  1.33 * (float)$jy)));
1469:     $rm_moon  = 0.0003 * sin(deg2rad($this->__angle(280.0   + 23221.3    * $jy)));
1470:     $rm_moon +0.0003 * sin(deg2rad($this->__angle(161.0   +    40.7    * $jy)));
1471:     $rm_moon +0.0003 * sin(deg2rad($this->__angle(311.0   +  5492.0    * $jy)));
1472:     $rm_moon +0.0003 * sin(deg2rad($this->__angle(147.0   + 18089.3    * $jy)));
1473:     $rm_moon +0.0003 * sin(deg2rad($this->__angle66.0   +  3494.7    * $jy)));
1474:     $rm_moon +0.0003 * sin(deg2rad($this->__angle83.0   +  3814.0    * $jy)));
1475:     $rm_moon +0.0004 * sin(deg2rad($this->__angle20.0   +   720.0    * $jy)));
1476:     $rm_moon +0.0004 * sin(deg2rad($this->__angle71.0   +  9584.7    * $jy)));
1477:     $rm_moon +0.0004 * sin(deg2rad($this->__angle(278.0   +   120.1    * $jy)));
1478:     $rm_moon +0.0004 * sin(deg2rad($this->__angle(313.0   +   398.7    * $jy)));
1479:     $rm_moon +0.0005 * sin(deg2rad($this->__angle(332.0   +  5091.3    * $jy)));
1480:     $rm_moon +0.0005 * sin(deg2rad($this->__angle(114.0   + 17450.7    * $jy)));
1481:     $rm_moon +0.0005 * sin(deg2rad($this->__angle(181.0   + 19088.0    * $jy)));
1482:     $rm_moon +0.0005 * sin(deg2rad($this->__angle(247.0   + 22582.7    * $jy)));
1483:     $rm_moon +0.0006 * sin(deg2rad($this->__angle(128.0   +  1118.7    * $jy)));
1484:     $rm_moon +0.0007 * sin(deg2rad($this->__angle(216.0   +   278.6    * $jy)));
1485:     $rm_moon +0.0007 * sin(deg2rad($this->__angle(275.0   +  4853.3    * $jy)));
1486:     $rm_moon +0.0007 * sin(deg2rad($this->__angle(140.0   +  4052.0    * $jy)));
1487:     $rm_moon +0.0008 * sin(deg2rad($this->__angle(204.0   +  7906.7    * $jy)));
1488:     $rm_moon +0.0008 * sin(deg2rad($this->__angle(188.0   + 14037.3    * $jy)));
1489:     $rm_moon +0.0009 * sin(deg2rad($this->__angle(218.0   +  8586.0    * $jy)));
1490:     $rm_moon +0.0011 * sin(deg2rad($this->__angle(276.5   + 19208.02   * $jy)));
1491:     $rm_moon +0.0012 * sin(deg2rad($this->__angle(339.0   + 12678.71   * $jy)));
1492:     $rm_moon +0.0016 * sin(deg2rad($this->__angle(242.2   + 18569.38   * $jy)));
1493:     $rm_moon +0.0018 * sin(deg2rad($this->__angle(  4.1   +  4013.29   * $jy)));
1494:     $rm_moon +0.0020 * sin(deg2rad($this->__angle55.0   +    19.34   * $jy)));
1495:     $rm_moon +0.0021 * sin(deg2rad($this->__angle(105.6   +  3413.37   * $jy)));
1496:     $rm_moon +0.0021 * sin(deg2rad($this->__angle(175.1   +   719.98   * $jy)));
1497:     $rm_moon +0.0021 * sin(deg2rad($this->__angle87.5   +  9903.97   * $jy)));
1498:     $rm_moon +0.0022 * sin(deg2rad($this->__angle(240.6   +  8185.36   * $jy)));
1499:     $rm_moon +0.0024 * sin(deg2rad($this->__angle(252.8   +  9224.66   * $jy)));
1500:     $rm_moon +0.0024 * sin(deg2rad($this->__angle(211.9   +   988.63   * $jy)));
1501:     $rm_moon +0.0026 * sin(deg2rad($this->__angle(107.2   + 13797.39   * $jy)));
1502:     $rm_moon +0.0027 * sin(deg2rad($this->__angle(272.5   +  9183.99   * $jy)));
1503:     $rm_moon +0.0037 * sin(deg2rad($this->__angle(349.1   +  5410.62   * $jy)));
1504:     $rm_moon +0.0039 * sin(deg2rad($this->__angle(111.3   + 17810.68   * $jy)));
1505:     $rm_moon +0.0040 * sin(deg2rad($this->__angle(119.5   +     1.33   * $jy)));
1506:     $rm_moon +0.0040 * sin(deg2rad($this->__angle(145.6   + 18449.32   * $jy)));
1507:     $rm_moon +0.0040 * sin(deg2rad($this->__angle13.2   + 13317.34   * $jy)));
1508:     $rm_moon +0.0048 * sin(deg2rad($this->__angle(235.0   +    19.34   * $jy)));
1509:     $rm_moon +0.0050 * sin(deg2rad($this->__angle(295.4   +  4812.66   * $jy)));
1510:     $rm_moon +0.0052 * sin(deg2rad($this->__angle(197.2   +   319.32   * $jy)));
1511:     $rm_moon +0.0068 * sin(deg2rad($this->__angle53.2   +  9265.33   * $jy)));
1512:     $rm_moon +0.0079 * sin(deg2rad($this->__angle(278.2   +  4493.34   * $jy)));
1513:     $rm_moon +0.0085 * sin(deg2rad($this->__angle(201.5   +  8266.71   * $jy)));
1514:     $rm_moon +0.0100 * sin(deg2rad($this->__angle44.89  + 14315.966  * $jy)));
1515:     $rm_moon +0.0107 * sin(deg2rad($this->__angle(336.44  + 13038.696  * $jy)));
1516:     $rm_moon +0.0110 * sin(deg2rad($this->__angle(231.59  +  4892.052  * $jy)));
1517:     $rm_moon +0.0125 * sin(deg2rad($this->__angle(141.51  + 14436.029  * $jy)));
1518:     $rm_moon +0.0153 * sin(deg2rad($this->__angle(130.84  +   758.698  * $jy)));
1519:     $rm_moon +0.0305 * sin(deg2rad($this->__angle(312.49  +  5131.979  * $jy)));
1520:     $rm_moon +0.0348 * sin(deg2rad($this->__angle(117.84  +  4452.671  * $jy)));
1521:     $rm_moon +0.0410 * sin(deg2rad($this->__angle(137.43  +  4411.998  * $jy)));
1522:     $rm_moon +0.0459 * sin(deg2rad($this->__angle(238.18  +  8545.352  * $jy)));
1523:     $rm_moon +0.0533 * sin(deg2rad($this->__angle10.66  + 13677.331  * $jy)));
1524:     $rm_moon +0.0572 * sin(deg2rad($this->__angle(103.21  +  3773.363  * $jy)));
1525:     $rm_moon +0.0588 * sin(deg2rad($this->__angle(214.22  +   638.635  * $jy)));
1526:     $rm_moon +0.1143 * sin(deg2rad($this->__angle(  6.546 +  9664.0404 * $jy)));
1527:     $rm_moon +0.1856 * sin(deg2rad($this->__angle(177.525 +   359.9905 * $jy)));
1528:     $rm_moon +0.2136 * sin(deg2rad($this->__angle(269.926 +  9543.9773 * $jy)));
1529:     $rm_moon +0.6583 * sin(deg2rad($this->__angle(235.700 +  8905.3422 * $jy)));
1530:     $rm_moon +1.2740 * sin(deg2rad($this->__angle(100.738 +  4133.3536 * $jy)));
1531:     $rm_moon +6.2887 * sin(deg2rad($this->__angle(134.961 +  4771.9886 * $jy + $am)));
1532: 
1533:     return (float)$this->__angle($rm_moon + 218.3161 + 4812.67881 * $jy);
1534: }

1536: /**
1537:  * 指定した日時における月の視黄経(度)を求める(日時はローカル時間).
1538:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-44-01.shtm
1539:  * @param   int $year, $month, $day  年月日
1540:  * @param   float $hour, $min, $sec 時分秒(日本時)
1541:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF
1542:  * @return  float 月の視黄経(度)
1543: */
1544: function longitude_moon($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1545:     $jy = (float)$this->Gregorian2JY($year, $month, $day, $hour, $min, $sec, $tdiff);
1546: 
1547:     return $this->__longitude_moon($jy);
1548: }

計算式は『日の出・日の入りの計算』(長沢工=著)を参考にした。

解説:次の朔の日時を求める

1795: /**
1796:  * 指定した日時の次の新月の日時を求める(日時はローカル時間).
1797:  * 太陽と月の黄緯差0度になる日時を近似計算で求める.
1798:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-43-01.shtm
1799:  * @param   int $year, $month, $day  年月日(グレゴリオ暦)
1800:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1801:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF
1802:  * @param   int $method 引き込み方式(1または2)
1803:  * @return  array($year, $month, $day, $hour, $min, $sec)  日時(ローカル時間)
1804: */
1805: function next_newmoon($year, $month, $day, $hour=0.0, $min=0.0, $sec=0.0, $tdiff=NULL) {
1806:     //世界時との時差
1807:     if ($tdiff == NULL) {
1808:         $tdiff = $this->TDIFF;
1809:     }
1810: 
1811:     $dif = 29.55;       //一朔望月(近似計算の指標)
1812:     $jd  = $this->Gregorian2JD($year, $month, $day, $hour, $min, $sec, $tdiff);
1813:     $jdc = (float)$jd;
1814: 
1815:     //ニュートン法による近似計算
1816:     for ($i = 1$i < 20$i++) {
1817:         list($year, $month, $day, $hour, $min, $sec) = $this->JD2Gregorian($jdc, $tdiff);
1818:         $ym = $this->longitude_moon($year, $month, $day, $hour, $min, $sec, $tdiff);
1819:         $ys = $this->longitude_sun($year, $month, $day, $hour, $min, $sec, $tdiff);
1820: 
1821:         //太陽と月の黄緯差0度を目指す.
1822:         $dy = $ym - $ys;
1823: 
1824:         //引き込み範囲に入ったら補正する.
1825:         if ($dy <-360.0) {
1826:             $dy +360.0;
1827:         } else if ($dy >0.0) {
1828:             $dy -360.0;
1829:         }
1830: 
1831:         //誤差内に入ったらループを脱出する.
1832:         if ($dy >0.0 && $dy <0.00001)    break;
1833:         if ($dy <0.0 && $dy >-0.00001)   break;
1834: 
1835:         $dd = $dy / $dif;
1836:         $jdc -$dd;
1837:     }
1838: 
1839:     return array($year, $month, $day, $hour, $min, $sec);
1840: }

月齢を求める基準が朔であることから、指定した日時の次の朔の日時を求めるメソッド next_newmoon を用意した。

月の視黄経から朔の日時を求める逆関数は存在しないため、ニュートンの近似法を使って求めることにする。無限ループに陥ることを避けるため、ループ回数は20回に制限している。

解説:月齢の算出

1842: /**
1843:  * 指定した日時における月齢を求める(日時はローカル時間).
1844:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-43-01.shtm
1845:  * @param   int $year, $month, $day  年月日(グレゴリオ暦)
1846:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)
1847:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF
1848:  * @return  float 月齢
1849: */
1850: function moon_age($year, $month, $day, $hour, $min, $sec, $tdiff=NULL) {
1851:     //世界時との時差
1852:     if ($tdiff == NULL) {
1853:         $tdiff = $this->TDIFF;
1854:     }
1855:     //一朔望月を遡って,次の朔の日時を求める.
1856:     $jd0  = $this->Gregorian2JD($year, $month, $day, $hour, $min, $sec, $tdiff);
1857:     $jd1  = $jd0 - 29.55;
1858:     list($year, $month, $day, $hour, $min, $sec) = $this->JD2Gregorian($jd1, $tdiff);
1859:     list($year, $month, $day, $hour, $min, $sec) = $this->next_newmoon($year, $month, $day, $hour, $min, $sec, $tdiff);
1860:     $jd2  = $this->Gregorian2JD($year, $month, $day, $hour, $min, $sec, $tdiff);
1861: 
1862:     //次の朔望月になっていた場合,基準日時を変えて再計算する.
1863:     if ($jd2 >$jd0) {
1864:         $jd2 -29.55 * 2;
1865:         list($year, $month, $day, $hour, $min, $sec) = $this->JD2Gregorian($jd2, $tdiff);
1866:         list($year, $month, $day, $hour, $min, $sec) = $this->next_newmoon($year, $month, $day, $hour, $min, $sec, $tdiff);
1867:         $jd2 = $this->Gregorian2JD($year, $month, $day, $hour, $min, $sec, $tdiff);
1868:     }
1869: 
1870:     //月齢を算出する.
1871:     return $jd0 - $jd2;
1872: }

太陽と月の視黄経が一致した瞬間が「(新月)」、月が太陽より90度東にきた瞬間を「上弦」、月と太陽が180度離れた瞬間を「(満月)」、月が太陽より90度西にきた瞬間を「下弦」である。
ここから、月齢を求めるためには、「PHPで二十四節気一覧を作成」で紹介した太陽の視黄経と、今回作成した 月の視黄経の差分を計算することにする。

指定した日時より29.55日前(一朔望月=新月から次の新月までの日数)を減じて、その時点から次の朔を next_newmoon を使って求める。
この朔の日時と指定日時の差を求めれば月齢になるはずだが、内部演算誤差で指定日時より一朔望月未来の朔の比を算出する恐れがある。そこで、差がマイナスになった場合には、基準日時を変えて、再度、月齢を計算する。

活用例

月齢=今日の月・現在の月=」(みんなの知識 ちょっと便利帳)では、このサンプル・プログラムを活用し、月の写真を使って月齢を表示するようにしている。ありがとうございます。

参考書籍

表紙 日の出・日の入りの計算
著者 長沢工
出版社 地人書館
サイズ 単行本
発売日 1999年12月
価格 1,650円(税込)
ISBN 9784805206348
 

参考サイト

(この項おわり)
header