PHPで祝日を求める

(1/1)
今回は、PHPを使って計算によって祝日を求めるプログラムを作ってみることにする。

あらかじめ断っておくが、計算によって祝日を求めることは難しい
秋分の日と敬老の日の微妙な関係」で述べたように、法律によって祝日が変わるなどのルールがあるため、ある年の祝日を計算で完全に求めることはできない。
過去の祝日であれば、実際のカレンダーから祝日を抜き出してテーブルに用意しておき、それを引いた方が確実である。また、未来の祝日についても、アプリケーションやインターネットにあるものをテーブルとして取り込み、それを引いた方が手っ取り早く確実である。
ここでは、アプリケーションやインターネットにないような未来の祝日を求めることを目的に、あえて計算で導出することを考えてみることにする。

(2024年2月25日)内閣府の祝日表を参照できるようにした.とくに春分の日・秋分の日.
(2023年10月14日)pahooInputDataクラス,Spinner導入.

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

PHPで祝日を求める

目次

サンプル・プログラムのダウンロード

圧縮ファイルの内容
getHoliday.phpサンプル・プログラム本体。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooCalendar.php暦計算クラス pahooCalendar。
暦計算クラスの使い方は「PHPで日出没・月出没・月齢・潮を計算」を参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
getHoliday.php 更新履歴
バージョン 更新日 内容
1.8.0 2024/02/25 内閣府の祝日表を参照できるようにした
1.7.0 2023/10/14 pahooInputDataクラス,Spinner導入
1.6 2021/04/24 PHP8対応,リファラ・チェック改良
1.5 2020/01/02 暦計算クラスpahooCalendar使用
1.4 2019/07/13 土日もカウントできるようにした
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() 追加
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 表記改訂:バクムーン→バックムーン,スタージャンムーン→スタージョンムーン,七十二候

祝日法を整理する

日本の祝日は、「国民の祝日に関する法律」(祝日法)という法律によって定められている。祝日法は、1948年(昭和23年)に定められた。そこで今回のプログラムは、1948年(昭和23年)以降の祝日について計算できるようにすることを目指す。
なお、新天皇即位に伴う「天皇の退位等に関する皇室典範特例法」により2019年(平成31年)の祝日が、東京オリンピック開催にともなう「国民の祝日に関する法律の一部を改正する法律」により2020年(令和2年)の祝日が変更になっていることに留意する。

2020年(令和2年)1月現在の祝日を一覧にすると下記の通りである。
「開始年」は祝日が有効になった年を、「終了年」は祝日が有効な最後の年を示す。
日本の祝日一覧(2020年1月現在)
月日 名称 開始年 終了年 備考
日本語 英語
固定祝日
1月1日 元日 New Year Day 1949 -  
1月15日 成人の日 Coming of Age Day 1949 1999 (※1)
2月11日 建国記念の日 National Foundation Day 1967 -
2月23日 天皇誕生日 The Emperor's Birthday 2020 - (※2)
4月29日 天皇誕生日 The Emperor's Birthday 1949 1989 (※2)
4月29日 みどりの日 Greenery Day 1990 2006 (※2)
4月29日 昭和の日 Showa Day 2007 - (※2)
5月3日 憲法記念日 Constitution Memorial Day 1949 -
5月4日 国民の休日 Holiday for a Nation 1988 2006 (※3)
5月4日 みどりの日 Greenery Day 2007 - (※3)
5月5日 こどもの日 Children's Day 1949 -  
7月20日 海の日 Marine Day 1996 2002  
7月22日 海の日 Marine Day 2021 2021 (※4)
7月23日 海の日 Marine Day 2020 2020 (※4)
7月23日 スポーツの日 Sports Day 2021 2021 (※4)
7月24日 スポーツの日 Sports Day 2020 2020 (※4)
8月8日 山の日 Mountain Day 2021 2021 (※4)
8月11日 山の日 Mountain Day 2016 2019  
8月10日 山の日 Mountain Day 2020 2020 (※4)
8月11日 山の日 Mountain Day 2022 -  
9月15日 敬老の日 Respect for the Aged Day 1966 2002 (※1)
10月10日 体育の日 Health and Sports Day 1966 1999 (※1)
11月3日 文化の日 National Culture Day 1948 -  
11月23日 勤労感謝の日 Labbor Thanksgiving Day 1948 -  
12月23日 天皇誕生日 The Emperor's Birthday 1989 2018  
4月10日 皇太子明仁親王
の結婚の儀
The Rite of Wedding of
HIH Crown Prince Akihito
1959 1959 (※4)
2月24日 昭和天皇
の大喪の礼
The Funeral Ceremony of Emperor Showa. 1989 1989 (※4)
11月12日 即位礼正殿の儀 The Ceremony of the Enthronement of
His Majesty the Emperor (at the Seiden)
1990 1990 (※4)
6月9日 皇太子徳仁親王
の結婚の儀
The Rite of Wedding of
HIH Crown Prince Naruhito
1993 1993 (※4)
5月1日 即位の日 Day of cadence 2019 2019 (※4)
10月22日 即位礼正殿の儀 The Ceremony of the Enthronement of
His Majesty the Emperor (at the Seiden)
2019 2019 (※4)
移動祝日1
3月21日頃 春分の日 Vernal Equinox Day 1949 -  
9月23日頃 秋分の日 Autumnal Equinox Day 1948 -  
移動祝日2(いわゆるハッピーマンデー)
1月
第2月曜日
成人の日 Coming of Age Day 2000 -  
7月
第3月曜日
海の日 Marine Day 2003 2019  
7月
第3月曜日
海の日 Marine Day 2022 -  
9月
第3月曜日
敬老の日 Respect for the Aged Day 2003 -  
10月
第2月曜日
体育の日 Health and Sports Day 2000 2019  
10月
第2月曜日
スポーツの日 Sports Day 2022 -  
振替休日(holiday in lieu)
祝日が日曜にあたるときは、その翌日を休日とする。1973年4月12日から適用。
【改正】祝日が日曜にあたるときは、その日後において、その日に最も近い「国民の祝日」でない日を休日。2007年1月1日から適用。
国民の休日(Citizen's Holiday)
その前日及び翌日が「国民の祝日」である日(日曜日にあたる日及び前項に規定する休日にあたる日を除く。)は、休日とする。1985年12月27日から適用。
【改正】その前日及び翌日が「国民の祝日」である日(「国民の祝日」でない日に限る)は、休日とする。 2005年5月20日から適用。

(※1)2000年または2002年に「移動祝日2」となる。
(※2)1990年より「みどりの日」、2007年より「昭和の日」となる。
(※3)2007年より「みどりの日」となる。
(※4)1年だけの祝日

祝日の性質に応じて、「固定祝日」「移動祝日1」「移動祝日2」「振替休日」「国民の休日」の5つにグルーピングしている。この名称はプログラムを作る便宜上付けたものであるので、他所では通用しない。
以下、それぞれのグループの計算方法を解説する。

解説:カレンダー計算

 171: /**
 172:  * 指定した年がうるう年かどうかを判定する.
 173:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php01/php05-01.shtm
 174:  * @param   int $year 西暦年
 175:  * @return  bool TRUE:閏年である/FALSE:平年である
 176: */
 177: function isleap($year) {
 178:     $ret = FALSE;
 179:     if ($year % 4 == 0)     $ret = TRUE;
 180:     if ($year % 100 == 0)   $ret = FALSE;
 181:     if ($year % 400 == 0)   $ret = TRUE;
 182:     return $ret;
 183: }

 185: /**
 186:  * 指定した月の日数(月の最後の日)を返す.
 187:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php01/php15-01.shtm
 188:  * @param   int $year  西暦年
 189:  * @param   int $month 月
 190:  * @return  int 日数/FALSE:引数の異常
 191: */
 192: function getDaysInMonth($year, $month) {
 193:     static $days = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
 194:     if ($month < 1 || $month > 12)   return FALSE;
 195:     $days[2] = $this->isleap($year? 29 : 28;      //閏年の判定
 196: 
 197:     return $days[$month];
 198: }

 345: /**
 346:  * グレゴリオ暦(ローカル時間)からユリウス日を求める.
 347:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm
 348:  * @param   int $year, $month, $day  グレゴリオ暦による年月日
 349:  * @param   float $hour, $min, $sec 時分秒(ローカル時間)(省略時は0)
 350:  * @param   float $tdiff UTCとの時差;NULLの時はTDIFF(省略時はNULL)
 351:  * @return  float ユリウス日
 352: */
 353: function Gregorian2JD($year, $month, $day, $hour=0, $min=0, $sec=0, $tdiff=NULL) {
 354:     //世界時との時差
 355:     if ($tdiff == NULL) {
 356:         $tdiff = $this->TDIFF;
 357:     }
 358:     $hour -$tdiff;
 359: 
 360:     if ($month <2) {
 361:         $month +12;
 362:         $year--;
 363:     }
 364:     $jd = floor(365.25 * $year- floor($year / 100.0+ floor($year / 400.0);
 365:     $jd +floor(30.59 * ($month - 2.0)) + $day + 1721088.5;
 366:     $jd +$hour / 24.0 + $min / (24.0 * 60.0+ $sec / (24.0 * 60.0 * 60.0);
 367: 
 368:     return $jd;
 369: }

 426: /**
 427:  * ユリウス日からグレゴリオ暦(ローカル時間)を求める.
 428:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm
 429:  * @param   float $jd ユリウス日
 430:  * @return  array($year, $month, $day, $hour, $min, $sec)  西暦年月日
 431: */
 432: function JD2Gregorian($jd, $tdiff=NULL) {
 433:     //世界時との時差
 434:     if ($tdiff == NULL) {
 435:         $tdiff = $this->TDIFF;
 436:     }
 437:     $jd += ($tdiff / 24.0);
 438: 
 439:     $x0 = floor($jd + 68570);
 440:     $x1 = floor($x0 / 36524.25);
 441:     $x2 = $x0 - floor(36524.25 * $x1 + 0.75);
 442:     $x3 = floor(($x2 + 1) / 365.2425);
 443:     $x4 = $x2 - floor(365.25 * $x3+ 31;
 444:     $x5 = floor(floor($x4) / 30.59);
 445:     $x6 = floor(floor($x5) / 11.0);
 446: 
 447:     $day   = $x4 - floor(30.59 * $x5);
 448:     $month = $x5 - 12 * $x6 + 2;
 449:     $year  = 100 * ($x1 - 49+ $x3 + $x6;
 450: 
 451:     if ($month == 2 && $day > 28) {
 452:         $day = $this->isleap($year? 29 : 28;
 453:     }
 454: 
 455:     $tm = 86400 * ($jd - floor($jd));
 456:     $hour = floor($tm / 3600.0);
 457:     $min  = floor(($tm - 3600 * $hour) / 60.0);
 458:     $sec  = floor($tm - 3600 * $hour - 60 * $min);
 459: 
 460:     return array($year, $month, $day, $hour, $min, $sec);
 461: }

 258: /**
 259:  * 指定した年月日の曜日番号を求める.
 260:  * ツェラーの公式を用いる.曜日番号は0(日曜日)から開始する.
 261:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm
 262:  * @param   int $year  西暦年
 263:  * @param   int $month 月
 264:  * @param   int $day   日
 265:  * @return  int 曜日番号(0:日曜日, 1:月曜日...6:土曜日)
 266: */
 267: function getWeekNumber($year, $month, $day) {
 268:     if ($month <2) {
 269:         $month +12;
 270:         $year--;
 271:     }
 272:     $c = floor($year / 100);
 273:     $y = $year % 100;
 274:     $t = -2 * $c + floor($c / 4);
 275:     $h = $day + floor(26 * ($month + 1) / 10+ $y + floor($y / 4+ $t - 1;
 276:     while ($h < 0)   $h +7;    //Ver.3.1 bug-fix
 277:     $h = $h % 7;
 278: 
 279:     return $h;
 280: }

PHPには曜日などの計算を行うための関数として  date  が用意されている。
しかし、date はUNIX 時間を基準にしているため、1970年(昭和45年)1月1日以降でないと使えない。今回は1948年(昭和23年)から対応しなければならないため、別のカレンダー関数を用意する必要がある。

PHPの環境によってはカレンダー関数が利用できる。
しかし、当サーバの環境では利用できないため、同様の関数を用意し、ユーザークラス "pahooCalendar" に分離している。まず、クラスファイル "pahooCalendar.php" を  require_once  し、オブジェクトを生成する。

指定した月の日数を返す関数として  cal_days_in_month  があるが、これも利用できないため、代わりにユーザー関数 getDaysInMonth を用意した。ある年が閏年 (うるうどし) かどうか判定する必要があるが、これについては「PHPで閏年かどうか判定する」で解説した通りである。

紀元前4713年1月1日からの通日をあらわす「ユリウス日」(Julian Day)というものがある。これであれば人類の有史時代全体を網羅できるので、天文学や歴史学で重宝されている。
ユーザー関数 Gregorian2JD は、西暦年月日からユリウス日を求める。JD2Gregorian は、ユリウス日から西暦年月日を求める。
getWeekNumber は、西暦年月日に対応する曜日番号を求める。曜日番号は、日曜日が0、月曜日が1‥‥土曜日が6である。

解説:固定祝日

2007: /**
2008:  * 指定した年月日が固定祝日であれば,その名称を求める.
2009:  * 祝日でなければFALSEを返す.
2010:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_getFixedHoliday
2011:  * @param   int $year  西暦年
2012:  * @param   int $month 月
2013:  * @param   int $day   日
2014:  * @return  string 固定祝日の名称/FALSE=祝日ではない
2015: */
2016: function getFixedHoliday($year, $month, $day) {
2017: //固定祝日
2018: static $fixed_holiday = array(
2019: //    月  日  開始年 終了年  名称
2020: array1,  1, 1949, 9999, '元日',         "New Year's Day"),
2021: array1, 15, 1949, 1999, '成人の日',     'Coming of Age Day'),
2022: array2, 11, 1967, 9999, '建国記念の日', 'National Foundation Day'),
2023: array2, 23, 2020, 9999, '天皇誕生日',   "The Emperor's Birthday"),
2024: array4, 29, 1949, 1989, '天皇誕生日',   "The Emperor's Birthday"),
2025: array4, 29, 1990, 2006, 'みどりの日',   'Greenery Day'),
2026: array4, 29, 2007, 9999, '昭和の日',     'Showa Day'),
2027: array5,  3, 1949, 9999, '憲法記念日',   'Constitution Memorial Day'),
2028: array5,  4, 1988, 2006, '国民の休日',   'Holiday for a Nation'),
2029: array5,  4, 2007, 9999, 'みどりの日',   'Greenery Day'),
2030: array5,  5, 1949, 9999, 'こどもの日',   "Children's Day"),
2031: array7, 20, 1996, 2002, '海の日',       'Marine Day'),
2032: array7, 22, 2021, 2021, '海の日',       'Marine Day'),
2033: array7, 23, 2020, 2020, '海の日',       'Marine Day'),
2034: array7, 23, 2021, 2021, 'スポーツの日', 'Health Sports Day'),
2035: array7, 24, 2020, 2020, 'スポーツの日', 'Health Sports Day'),
2036: array8,  8, 2021, 2021, '山の日',       'Mountain Day'),
2037: array8, 11, 2016, 2019, '山の日',       'Mountain Day'),
2038: array8, 10, 2020, 2020, '山の日',       'Mountain Day'),
2039: array8, 11, 2022, 9999, '山の日',       'Mountain Day'),
2040: array9, 15, 1966, 2002, '敬老の日',     'Respect for the Aged Day'),
2041: array(10, 10, 1966, 1999, '体育の日',     'Health and Sports Day'),
2042: array(11,  3, 1948, 9999, '文化の日',     'National Culture Day'),
2043: array(11, 23, 1948, 9999, '勤労感謝の日', 'Labbor Thanksgiving Day'),
2044: array(12, 23, 1989, 2018, '天皇誕生日',   "The Emperor's Birthday"),
2045: //以下,1年だけの祝日
2046: array4, 10, 1959, 1959, '皇太子明仁親王の結婚の儀', "The Rite of Wedding of HIH Crown Prince Akihito"),
2047: array2, 24, 1989, 1989, '昭和天皇の大喪の礼', "The Funeral Ceremony of Emperor Showa."),
2048: array(11, 12, 1990, 1990, '即位礼正殿の儀', "The Ceremony of the Enthronement
2049:       of His Majesty the Emperor (at the Seiden)"),
2050: array6,  9, 1993, 1993, '皇太子徳仁親王の結婚の儀 ', "The Rite of Wedding of HIH Crown Prince Naruhito"),
2051: array5,  1, 2019, 2019, '即位の日', 'Day of cadence'),
2052: array(10, 22, 2019, 2019, '即位礼正殿の儀', 'The Ceremony of the Enthronement of His Majesty the Emperor (at the Seiden)'),
2053: );
2054: 
2055:     $name = FALSE;
2056:     foreach ($fixed_holiday as $val) {
2057:         if ($month == $val[0&& $day == $val[1]) {
2058:             if ($year >$val[2&& $year <$val[3]) {
2059:                 $name = preg_match('/jp/i', $this->language) == 1 ? $val[4: $val[5];
2060:                 break;
2061:             }
2062:         }
2063:     }
2064:     return $name;
2065: }

毎年月日が決まっている祝日である。
1948年(昭和23年)に9つの祝日で始まった。ただし、7月20日に公布・施行だったため、7月19日以前の6つの祝日は翌1949年(昭和24年)から有効になった。
その後、東京オリンピックの翌年(1966年、昭和41年)に体育の日などが追加されていく。このときも6月25日に公布・施行だったため、建国記念の日(2月11日)は翌1967年(昭和42年)から有効になった。
その後、昭和天皇の崩御(1989年、昭和64年)に伴い、「天皇誕生日」の名称が目まぐるしく変わる。
また、いわゆるハッピーマンデー法の成立によって、2000年(平成12年)と2003年の二度にわたり、合計4つの祝日が移動した。

ユーザー関数 getFixedHoliday は、西暦年月日を与え、祝日であればその名称を返す。
プログラムは、まず、固定祝日の一覧を配列 $fixed_holiday に用意する。今後、固定祝日が変更になった場合は、この配列を変更するだけで対応できるようになっている。
そして、引数の月 $month と日 $day が合致するかどうかを調べる。
祝日の名称として、日本語と英語のいずれかを返すことができるようにしてある。

解説:移動祝日1(春分の日・秋分の日)

2067: /**
2068:  * 内閣府の祝日表を読み込んでプロパティに代入する.
2069:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_moving_holiday1
2070:  * @param   なし
2071:  * @return  bool TRUE:成功/失敗(ファイルが見つからないなど)
2072: */
2073: function getCabinetOfficeHolidayTable() {
2074:     if ($this->pcc == NULL)     return FALSE;
2075: 
2076:     //内閣府の祝日表をキャッシュに読み込む
2077:     $contents = $this->pcc->load(self::CABINETOFFICE_HOLIDAY_FILE);
2078:     if ($contents == FALSE)     return FALSE;
2079: 
2080:     //1行ずつ分解して解釈し、プロパティに代入する.
2081:     $tok = strtok($contents, "\n");
2082:     while ($tok !== FALSE) {
2083:         $ss = mb_convert_encoding($tok, INTERNAL_ENCODING, 'SJIS');
2084:         if (preg_match('/\s*([0-9]+)\s*\/\s*([0-9]+)\s*\/\s*([0-9]+)\s*\,(.+)/ui', $ss, $arr> 0) {
2085:             $year = (int)$arr[1];
2086:             $mmdd = (string)sprintf('%02d%02d', $arr[2], $arr[3]);
2087:             $holiday = (string)trim($arr[4]);
2088:             $this->CabinetOfficeHolidayTables[$year][$holiday] = $mmdd;
2089:         }
2090:         $tok = strtok("\n");
2091:     }
2092:     return TRUE;
2093: }

2095: /**
2096:  * 指定した年の春分の日を求める.
2097:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_moving_holiday1
2098:  * @param   int  $year 西暦年
2099:  * @param   bool $cb   TRUE:内閣の祝日表の日/FALSE:計算で求めた日
2100:  * @return  int 日(3月の)
2101: */
2102: function getVernalEquinox($year, &$cb=FALSE) {
2103:     //内閣府の祝日表を読み込む
2104:     if (count($this->CabinetOfficeHolidayTables) == 0)  $this->getCabinetOfficeHolidayTable();
2105:     //内閣府の祝日表にあれば
2106:     if (isset($this->CabinetOfficeHolidayTables[$year]['春分の日'])) {
2107:         $mmdd = $this->CabinetOfficeHolidayTables[$year]['春分の日'];
2108:         $day = (int)substr($mmdd, 2, 2);
2109:         $cb = TRUE;
2110: 
2111:     //計算で求める
2112:     } else {
2113:         $day = floor(20.8431 + 0.242194 * ($year - 1980- floor(($year - 1980) / 4));
2114:         $cb = FALSE;
2115:     }
2116: 
2117:     return $day;
2118: }

2120: /**
2121:  * 指定した年の秋分の日を求める.
2122:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_moving_holiday1
2123:  * @param   int $year 西暦年
2124:  * @param   bool $cb   TRUE:内閣の祝日表の日/FALSE:計算で求めた日
2125:  * @return  int 日(9月の)
2126: */
2127: function getAutumnalEquinox($year, &$cb=FALSE) {
2128:     //内閣府の祝日表を読み込む
2129:     if (count($this->CabinetOfficeHolidayTables) == 0)  $this->getCabinetOfficeHolidayTable();
2130:     //内閣府の祝日表にあれば
2131:     if (isset($this->CabinetOfficeHolidayTables[$year]['秋分の日'])) {
2132:         $mmdd = $this->CabinetOfficeHolidayTables[$year]['秋分の日'];
2133:         $day = (int)substr($mmdd, 2, 2);
2134:         $cb = TRUE;
2135: 
2136:     //計算で求める
2137:     } else {
2138:         $day = floor(23.2488 + 0.242194 * ($year - 1980- floor(($year - 1980) / 4));
2139:         $cb = FALSE;
2140:     }
2141: 
2142:     return $day;
2143: }

2145: /**
2146:  * 指定した年月日が移動祝日(春分/秋分の日)であれば,その名称を求める.
2147:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_moving_holiday1
2148:  * 移動祝日でなければFALSEを返す.
2149:  * @param   int $year  西暦年
2150:  * @param   int $month 月
2151:  * @param   int $day   日
2152:  * @return  string 移動祝日の名称/FALSE=祝日ではない
2153: */
2154: function getMovableHoliday1($year, $month, $day) {
2155:     $name = FALSE;
2156: 
2157:     //春分の日
2158:     $dd = $this->getVernalEquinox($year, $cb);
2159:     if ($year >=1949 && $day == $dd && $month == 3) {
2160:         $name = preg_match('/jp/i', $this->language) == 1 ? '春分の日' : 'Vernal Equinox Day';
2161:     }
2162:     //秋分の日
2163:     $dd = $this->getAutumnalEquinox($year);
2164:     if ($year >=1948 && $day == $dd && $month == 9) {
2165:         $name = preg_match('/jp/i', $this->language) == 1 ? '秋分の日' : 'Autumnal Equinox Day';
2166:     }
2167:     return $name;
2168: }

秋分の日と敬老の日の微妙な関係」で述べたように、「春分の日」「秋分の日」は前年度の閣議で決まる。これが一番厄介である。
秋分の日、秋分の日を天文学的な計算する手段はあるのだが、人為的に日付を変える可能性があるので、こればかりは計算で確定することができない。

一方、閣議決定された国民の祝日は、内閣府のページにCSVファイルとして公開されている。
そこで、このCSVファイルにあれば、その春分の日と秋分の日を返し、なければ計算によって求めることにする。

getVernalEquinox は、指定した年の春分の日を求めるメソッドである。
まず、プロパティ配列 $CabinetOfficeHolidayTables の要素が1つもなければ、getCabinetOfficeHolidayTable メソッドを使って、前述の内閣府のページにあるCSVファイルを読み込む。祝日表は頻繁に変わるものではないから、内閣府のページに余計な負荷を与えないように、いったん読み込んだCSVファイルはローカルにキャッシングするよう、キャッシュシステム pahooCache を利用する。
次に、プロパティ配列 $CabinetOfficeHolidayTables に指定した年の春分の日があれば、それを返し、無ければ、計算で求めた日を返す。計算で求める春分の日は、実際のカレンダーとは±1日程度ズレる可能性があることに留意されたい。
メソッドの第2引数 $cb は、祝日表から求めた日ならTRUEを、計算で求めたのならFALSEを代入する。呼び出し側で $cb を参照すれば、それが計算で求めたものかどうか判定できる。また、$cb は省略可能である。

getAutumnalEquinox は、指定した年の秋分の日を求めるメソッドである。流れは春分の日と同じである。

  24: /**
  25:  * コンストラクタ
  26:  * @param   string $language 表示言語;省略時 jp
  27:  * @param   float  $tdiff    世界時との時差(省略時 +9.0;日本標準時)
  28:  * @param   object $pcc      pahooCacheインスタンス;省略時 NULL
  29:  *              インターネット経由で内閣府の国民の祝日を参照するときに指定
  30:  * @return  bool オブジェクト/FALSE:$tdiffが不正
  31: */
  32: function __construct($language='jp', $tdiff=+9.0, $pcc=NULL) {
  33:     $this->error  = FALSE;
  34:     $this->errmsg = '';
  35:     $this->year  = date('Y');
  36:     $this->month = date('n');
  37:     $this->day   = date('j');
  38:     $this->resolve2033 = 0;
  39:     $this->pcc = $pcc;
  40:     $this->CabinetOfficeHolidayTables = array();
  41: 
  42:     $this->setLanguage($language);
  43:     if ($this->setTimeDifference($tdiff) == FALSE) {
  44:         $this->error = TRUE;
  45:         $this->errmsg = 'illegal tdiff';
  46:     }
  47: }

なお、祝日表を読み込むためには、pahooCalendar のコストラクタの第3引数として、呼び出し側で用意した pahooCache のインスタンスを指定する。これを指定しないと、祝日表の読み込みは行わない。

解説:移動祝日2(ハッピーマンデー)

2170: /**
2171:  * ある月の第N曜日の日付を求める.
2172:  * 計算オーバーフローが起きたらFALSEを返す.
2173:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_moving_holiday2
2174:  * @param   int $year  西暦年
2175:  * @param   int $month 月
2176:  * @param   int $week  曜日番号;0 (日曜)~ 6 (土曜)
2177:  * @param   int $n     第N曜日
2178:  * @return  int $day   日
2179: */
2180: function getWeeksOfMonth($year, $month, $week, $n) {
2181:     if ($n < 1)      return FALSE;
2182: 
2183:     $jd1 = $this->Gregorian2JD($year, $month, 1, 0, 0, 0, 0.0);
2184:     $wn1 = $this->getWeekNumber($year, $month, 1);
2185:     $dd  = $week - $wn1 < 0 ? 7 + $week - $wn1 : $week - $wn1;
2186:     $jd2 = $jd1 + $dd;
2187:     $jdn = $jd2 + 7 * ($n - 1);
2188:     list($yy, $mm, $dd) = $this->JD2Gregorian($jdn);
2189: 
2190:     if ($mm !$month)  return FALSE;   //月のオーバーフロー
2191: 
2192:     return $dd;
2193: }

2195: /**
2196:  * 指定した年月日が移動祝日(ハッピーマンデー)であれば,その名称を求める.
2197:  * 移動祝日でなければFALSEを返す.
2198:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_moving_holiday2
2199:  * @param   int $year  西暦年
2200:  * @param   int $month 月
2201:  * @param   int $day   日
2202:  * @return  string 移動祝日の名称/FALSE=祝日ではない
2203: */
2204: function getMovableHoliday2($year, $month, $day) {
2205: //移動祝日(ハッピーマンデー法)
2206: static $movable_holiday = array(
2207: //    月  曜日番号 第N曜日 開始年  終了年  名称
2208: array1, 1, 2, 2000, 9999, '成人の日', 'Coming of Age Day'),
2209: array7, 1, 3, 2003, 2019, '海の日',   'Marine Day'),
2210: array7, 1, 3, 2022, 9999, '海の日',   'Marine Day'),
2211: array9, 1, 3, 2003, 9999, '敬老の日', 'Respect for the Aged Day'),
2212: array(10, 1, 2, 2000, 2019, '体育の日', 'Health and Sports Day'),
2213: array(10, 1, 2, 2022, 9999, 'スポーツの日', 'Health Sports Day')
2214: );
2215: 
2216:     $name = FALSE;
2217:     foreach ($movable_holiday as $val) {
2218:         if ($month == $val[0&& $day == $this->getWeeksOfMonth($year, $month, $val[1], $val[2])) {
2219:             if ($year >$val[3&& $year <$val[4]) {
2220:                 $name = preg_match('/jp/i', $this->language) == 1 ? $val[5: $val[6];
2221:                 break;
2222:             }
2223:         }
2224:     }
2225:     return $name;
2226: }

いわゆるハッピーマンデー法で決まった祝日である。第2月曜日または第3月曜日が祝日に当たる。

ユーザー関数 getWeeksOfMonth は、ある月の第N曜日を求めることができる。
将来、ハッピーフライデー法(笑)ができるかもしれないので、月曜日でなく、任意の曜日について求めることができるようにしてある。

ユーザー関数 getMovableHoliday2 は、ハッピーマンデーであれば、その名称を返す。getFixedHoliday と同様、祝日一覧を配列 $movable_holiday に用意しておく。今後、ハッピーマンデー法が変更になった場合は、この配列を変更するだけで対応できるようになっている。

解説:振替休日

2243: /**
2244:  * 指定した年月日が振替休日かどうかを求める.
2245:  * 振替休日ならTRUEを返す.
2246:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_isTransferHoliday
2247:  * @param   int $year  西暦年
2248:  * @param   int $month 月
2249:  * @param   int $day   日
2250:  * @return  bool TRUE/FALSE
2251: */
2252: function isTransferHoliday($year, $month, $day) {
2253:     $jd = $this->Gregorian2JD($year, $month, $day, 0, 0, 0);
2254:     $j0 = $this->Gregorian2JD(1973, 4, 12, 0, 0, 0);
2255:     if ($jd < $j0)   return FALSE;       //有効なのは1973年4月12日以降
2256: 
2257:     //当日が祝日なら FALSE
2258:     if ($this->isFixedMovableHoliday($year, $month, $day))      return FALSE;
2259: 
2260:     $n = ($year <2006? 1 : 7;    //改正法なら最大7日間遡る
2261:     $jd--;                          //1日前
2262:     for ($i = 0$i < $n$i++) {        //無限ループに陥らないように
2263:         list($yy, $mm, $dd) = $this->JD2Gregorian($jd);
2264:         //祝日かつ日曜日なら振替休日
2265:         if ($this->isFixedMovableHoliday($yy, $mm, $dd)
2266:             && ($this->getWeekNumber($yy, $mm, $dd) == 0))      return TRUE;
2267:         //祝日でなければ打ち切り
2268:         if (! $this->isFixedMovableHoliday($yy, $mm, $dd))      break;
2269:         $jd--;  //1日前
2270:     }
2271:     return FALSE;
2272: }

日曜日と祝日が重なった場合、月曜日が祝日になる。これが振替休日である。1973年(昭和48年)4月12日からはじまった。
ところがハッピーマンデー法が制定されてから以降は、日曜日、月曜日が連続して祝日になるケースが発生する。そこで、2007年(平成19年)1月以降は、
祝日が日曜にあたるときは、その日後において、その日に最も近い「国民の祝日」でない日を休日
とすることになった。前述の場合は火曜日が祝日になる。
とくに2009年(平成21年)は、5月3日の日曜日が憲法記念日、月曜日のみどりの日、火曜日のこどもの日と、祝日が3日連続することになった。この場合は水曜日が振替休日となる。

ユーザー関数 isTransferHoliday は、与えられた西暦年月日が振替休日かどうか調べるものである。
前日が祝日である場合は、for ループを用い、日曜日まで祝日が続いているかどうか遡ってチェックするようにしている。連続していれば、その日は振替休日である。
ここで while ではなくforループを用いたのは、引数や他の祝日判定の異常で無限ループに陥ることがないようにするための配慮である。

解説:国民の休日

2274: /**
2275:  * 指定した年月日が国民の休日かどうかを求める.
2276:  * 国民の休日ならTRUEを返す.
2277:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_isNationalHoliday
2278:  * @param   int $year  西暦年
2279:  * @param   int $month 月
2280:  * @param   int $day   日
2281:  * @return  bool TRUE/FALSE
2282: */
2283: function isNationalHoliday($year, $month, $day) {
2284:     if ($year < 2003)    return FALSE;   //有効なのは2003年以降
2285:     $j0 = $this->Gregorian2JD($year, $month, $day, 0, 0, 0- 1;    //前日
2286:     list($yy0, $mm0, $dd0) = $this->JD2Gregorian($j0);
2287:     $j1 = $this->Gregorian2JD($year, $month, $day, 0, 0, 0+ 1;    //翌日
2288:     list($yy1, $mm1, $dd1) = $this->JD2Gregorian($j1);
2289: 
2290:     //前日と翌日が固定祝日または移動祝日なら国民の休日
2291:     if ($this->isFixedMovableHoliday($yy0, $mm0, $dd0)
2292:         && $this->isFixedMovableHoliday($yy1, $mm1, $dd1))      return TRUE;
2293:     return FALSE;
2294: }

固定祝日、移動祝日1、移動祝日2のいずれかで挟まれた1日は「国民の休日」となる。2003年(平成15年)以降に有効になる。

ユーザー関数 isTransferHoliday は、与えられた西暦年月日が国民の休日かどうかを調べる。
その翌日と前日が、固定祝日、移動祝日1、移動祝日2のいずれかでないかどうかを調べているだけである。

解説:祝日を求める

2296: /**
2297:  * 指定した年月日が祝日であれば,その名称を求める.
2298:  * 祝日でなければFALSEを返す.
2299:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_getHoliday
2300:  * @param   int $year  西暦年
2301:  * @param   int $month 月
2302:  * @param   int $day   日
2303:  * @return  string 祝日の名称/FALSE=祝日ではない
2304: */
2305: function getHoliday($year, $month, $day) {
2306:     //固定祝日
2307:     $name = $this->getFixedHoliday($year, $month, $day, 'jp');
2308:     if ($name !FALSE)     return $name;
2309:     //移動祝日(春分/秋分の日)
2310:     $name = $this->getMovableHoliday1($year, $month, $day, 'jp');
2311:     if ($name !FALSE)     return $name;
2312:     //移動祝日(ハッピーマンデー)
2313:     $name = $this->getMovableHoliday2($year, $month, $day, 'jp');
2314:     if ($name !FALSE)     return $name;
2315:     //振替休日
2316:     if ($this->isTransferHoliday($year, $month, $day)) {
2317:         return preg_match('/jp/i', $this->language) == 1 ? '振替休日' : 'holiday in lieu';
2318:     }
2319:     //国民の祝日
2320:     if ($this->isNationalHoliday($year, $month, $day)) {
2321:         return preg_match('/jp/i', $this->language) == 1 ? '国民の休日' : "Citizen's Holiday";
2322:     }
2323:     //祝日ではない
2324:     return FALSE;
2325: }

2327: /**
2328:  * 指定した年月日が祝日かどうかを求める.
2329:  * 祝日ならTRUEを返す.
2330:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php02/php02-27-01.shtm#php_getHoliday
2331:  * @param   int $year  西暦年
2332:  * @param   int $month 月
2333:  * @param   int $day   日
2334:  * @return  bool TRUE/FALSE
2335: */
2336: function isHoliday($year, $month, $day) {
2337:     return getHoliday($year, $month, $day, 'jp') == FALSE ? FALSE : TRUE;
2338: }

ユーザー関数 getHoliday は、与えられた西暦年月日が祝日であれば、その名称を返す。
固定祝日、移動祝日1、移動祝日2、振替休日、国民の休日を順々に調べている。

ユーザー関数 isHoliday は、与えられた西暦年月日が祝日かどうかを調べるものである。

以上の関数を利用し、入力された西暦年から以降3年分の祝日一覧を表示するのがサンプル・プログラムである。

祝日と祭日

Wikipediaによれば、「祭日」とは「宗教儀礼上重要な祭祀を行う日のこと」とされている。
戦後、GHQの占領下において、神道に基づく儀礼は一切廃止されたため、「祭日」は無くなった。そして、国民が祝うべき「祝日」に切り替わったのである。
ところが実際は、新憲法が制定された1947年(昭和22年)を過ぎても戦前の祝日法が有効であり、ようやく1948年(昭和23年)になって新しい「国民の祝日に関する法律」(祝日法)が定められた。

『国民の祝日』の由来がわかる小事典」(所功/PHP研究所/2003年(平成15年)08月)は、祝日を
  1. 祭日に基づく祝日
  2. 国家にちなむ祝日
  3. 人生に伴う祝日
の3つに分類しているが、「春分の日」「秋分の日」「勤労感謝の日」は「祭日に基づく祝日」に分類している。これらは、農耕民族である日本人古来の祭礼に基づく祝日だからである。表向き、祭日が無くなったとはいえ、実はしっかりとカレンダーに根付いているのである。

参考書籍

表紙 知れば知るほど面白い暦の謎
著者 片山 真人
出版社 三笠書房
サイズ 文庫
発売日 2022年02月17日頃
価格 858円(税込)
ISBN 9784837987635
1週間はなぜ「7日」なのか?「曜日」はどのように生まれたか?国立天文台「暦の専門家」が教える!
 
表紙 理科年表Q&A
著者 理科年表Q&A編集委員会
出版社 丸善出版
サイズ 単行本
発売日 2003年11月
価格 1,980円(税込)
ISBN 9784621073360
本書では、これまで理科年表編集部が受けたたくさんの質問の中から、「これはなるほど」、「これは知って便利」というテーマを選び出し、理科年表の部門「暦(こよみ)」、「天文」、「気象」、「物理/化学」、「地学」、「生物」に沿ってしぼり込み、できるだけわかりやすく解説しながら答えを書くよう努めました。
 
表紙 暦の雑学事典
著者 吉岡安之
出版社 日本実業出版社
サイズ 単行本
発売日 1999年12月
価格 1,430円(税込)
ISBN 9784534030214
知っているとちょっと楽しい知識を満載!ミレニアムの意外な秘密がわかる。暦の歴史をたどり、ルーツを探る。いまに生きる旧暦の数々がわかる。さまざまな時計の歴史と科学を紹介。身近な暦の話題から歳時記まで暦の蘊蓄が盛りだくさん。
 
表紙 「国民の祝日」の由来がわかる小事典
著者 所功
出版社 PHP研究所
サイズ 新書
発売日 2003年08月
価格 858円(税込)
ISBN 9784569630861
「建国記念日の日」「こどもの日」「勤労感謝の日」…。今や年間十五日にのぼる「国民の祝日」はいつ頃、どのように成立したのか。古来の民俗的な年中行事や人生儀礼に伴なう祝祭日。明治以降の国家的な祝日。さらに平成に入ってから付け加えられた「みどりの日」や「海の日」など、「国民の祝日」は古さと新しさをあわせもつ。そこには、わが国で永年育まれてきた自然と、先祖に感謝する心、共同体の人間関係を尊ぶ豊かな精神が盛り込まれている。歴史学の観点から日本人の英知を解き明かす。
 
表紙 プログラミングのセオリー
著者 矢沢久雄
出版社 技術評論社
サイズ 単行本
発売日 2008年11月
価格 2,618円(税込)
ISBN 9784774136288
 

参考サイト

(この項おわり)
header