PHPで今日は何の日か調べる

(1/1)
Wikipediaには、指定した月日のできごとが記録されている。
これをPHPでスクレイピングすることで、今日は何の日か表示するプログラムを作成する。
さらに、「PHPで記念日を表示する」で作ったプログラムを取り込み、その日の記念日も表示できるようにする。

(2024年7月27日)Wikipediaのフォーマットが変わったことを受け、getDayToday()関数の取得パターンを変更した。

目次

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

PHPで今日は何の日か調べる
次のURLオプションが利用できる(URLにクエリパラメータを渡すことで機能を変更できる)。
month, day = 月,日の指定
(例)DayToday.php?month=8&day=2 (8月2日を指定する)
diff = 今日を基準とした差分日数指定(-100~+100)
(例)DayToday.php?diff=+2 (明後日を指定する)
era = 西暦年の下段に和暦などを表示
(例)DayToday.php?era
lfn = 脚注を残す
(例)DayToday.php?lfn
anniv = 記念日を表示する
(例)DayToday.php?anniv

サンプル・プログラム

圧縮ファイルの内容
DayToday.phpサンプル・プログラム本体。
DayToday.php 更新履歴
バージョン 更新日 内容
2.1.4 2024/07/27 getDayToday()の取得パターンを変更した
2.1.3 2023/01/05 getDayToday()の取得パターンを変更した
2.12 2022/10/15 人物の紀元前パターンに対応
2.11 2022/10/15 紀元前のHTMLパターン変更に対応
2.1 2021/10/10 eliminate_footnote(): 脚注パターン追加

解説:今日は何の日を取得

 254: /**
 255:  * Wikipedia URL取得
 256:  * @param   int $month, $day 月,日
 257:  * @return  string URL
 258: */
 259: function getURL_Wikipedia($month, $day) {
 260:     $url = 'http://ja.wikipedia.org/wiki/' . urlencode(sprintf('%d月%d日', $month, $day));
 261: 
 262:     return $url;
 263: }

 265: /**
 266:  * 今日は何の日 取得
 267:  * @param   int    $month, $day 月,日
 268:  * @param   array  $items  情報格納配列
 269:  * @param   string $errmsg エラーメッセージ格納用
 270:  * @param   bool   $lfn    脚注残す(省略時=FALSE)
 271:  * @return  int 情報件数
 272: */
 273: function getDayToday($month, $day, &$items, &$errmsg, $lfn=FALSE) {
 274: //  $pat1 = "/<span[\s\S]+id=\"できごと\"/iu";          //ver.2.1.3
 275: //  $pat2 = "/<span[\s\S]+id=\"誕生日\"/iu";            //ver.2.1.3
 276: //  $pat3 = "/<span[\s\S]+id=\"忌日\"/iu";              //ver.2.1.3
 277:     $pat1 = "/<h2\s+id\=\"できごと\"/iu";               //ver.2.1.4
 278:     $pat2 = "/<h2\s+id\=\"誕生日\"/iu";                 //ver.2.1.4
 279:     $pat3 = "/<h2\s+id\=\"忌日\"/iu";                   //ver.2.1.4
 280:     $pat5 = "/<li>[^\>]+([0-9]+(年|世紀).+)<\/li>/iu";  //年 - 内容 ver.2.11
 281:     $pat6 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+(.+)/iu";            //年 - イベント Ver.1.32
 282:     $pat7 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+([^、]+)、?([^(\(]+)*([^0-9]*)([0-9]*)/iu"//年 - 名前、職業(生没年) Ver.1.43
 283:     $pat8 = "/<span[\s\S]+id=\"フィクションのできごと\">/iu";   //ver.1.5
 284: 
 285:     $url = getURL_Wikipedia($month, $day);
 286: 
 287:     $infp = @fopen($url, 'r');
 288:     if ($infp == FALSE) {
 289:         $errmsg = 'Wikipediaにアクセスできません.';
 290:         return FALSE;
 291:     }
 292: 
 293:     $mode = '';
 294:     $cnt = 1;
 295:     while (! feof($infp)) {
 296:         $str = fgets($infp);
 297:         if (preg_match($pat1, $str) == 1) {
 298:             $mode = 'event';
 299:         } else if (preg_match($pat8, $str) == 1) {      //Ver.1.5
 300:             $mode = 'fiction';                          //Ver.1.5
 301:         } else if (preg_match($pat2, $str) == 1) {
 302:             $mode = 'birth';
 303:         } else if (preg_match($pat3, $str) == 1) {
 304:             $mode = 'death';
 305:         } else if ($mode !'' && preg_match($pat5, $str, $arr> 0) {
 306:             $str = strip_tags(trim($str));
 307:             if ($mode == 'event' && preg_match($pat6, $str, $arr) == 1) {
 308:                 $items[$mode][$cnt]['year']  = ($arr[1] == ''? (int)$arr[2: 0 - $arr[2];
 309:                 $items[$mode][$cnt]['era']   = $arr[3];
 310:                 $items[$mode][$cnt]['event'] = $lfn ? $arr[4: eliminate_footnote($arr[4]);
 311:                 $cnt++;
 312:             } else if (preg_match($pat7, $str, $arr) == 1) {
 313:                 $items[$mode][$cnt]['year']  = ($arr[1] == ''? (int)$arr[2: 0 - $arr[2];
 314:                 $items[$mode][$cnt]['era']     = $arr[3];
 315:                 $items[$mode][$cnt]['name']    = $lfn ? $arr[4: eliminate_footnote($arr[4]);
 316:                 $items[$mode][$cnt]['profile'] = $lfn ? $arr[5: eliminate_footnote($arr[5]);
 317:                 if (preg_match('/紀元前/u', $arr[6]) > 0) {         //Ver.2.11
 318:                     $items[$mode][$cnt]['plus']  = 0 - $arr[7];
 319:                 } else if (preg_match('/^\([\*\+]./', $arr[6]) > 0) {   //Ver.1.43
 320:                     $items[$mode][$cnt]['plus']  = $arr[7];
 321:                 } else {
 322:                     $items[$mode][$cnt]['plus']  = '';
 323:                 }
 324:                 $cnt++;
 325:             }
 326:         }
 327:     }
 328:     fclose($infp);
 329: 
 330:     if ($cnt == 0) {
 331:         $errmsg = '記念日が見つかりません.';
 332:     }
 333: 
 334:     return $cnt;
 335: }

Wikipediaには、「できごと」「誕生日」「忌日」に分かれて記録されている。
これらを正規表現 $pat1$pat7 を使ってスクレイピングする関数が getDayToday である。

Wikipediaのイベントや生没記述には揺れが大きく、これに対応するパターン $pat6 および $pat7 は、かなり複雑になっている。スクレイピングに成功すれば、配列要素として次のような値が入る。
要素内容
1先頭文字
2西暦年
3和暦など(西暦年の後に括弧書きがあれば)
4イベント、名前など
5プロファイル(生没の場合のみ)
6生年または没年の前の記号
7生年または没年(生没の場合のみ)

解説:今日は何の日をソート

 417: /**
 418:  * 今日は何の日 年号順にソート
 419:  * @param   array $sour1 ソート前配列(Wikipedia)
 420:  * @param   array $sour2 ソート前配列(日本記念日協会)
 421:  * @param   array $dest  ソード後配列
 422:  * @return  int 情報件数
 423: */
 424: function sortDayToday($sour1, $sour2, &$dest) {
 425:     $cnt = 0;
 426: 
 427:     //できごと
 428:     foreach ($sour1['event'as $item) {
 429:         $dest[$cnt]['year']  = $item['year'];
 430:         $dest[$cnt]['event'] = $item['event'];
 431:         $dest[$cnt]['era']   = $item['era'];
 432:         $cnt++;
 433:     }
 434:     //誕生日
 435:     foreach ($sour1['birth'as $item) {
 436:         $event = "{$item['name']}({$item['profile']})誕生";
 437:         if ($item['plus'!'')    $event ."(~{$item['plus']}年)";
 438:         $dest[$cnt]['year']  = $item['year'];
 439:         $dest[$cnt]['event'] = $event;
 440:         $dest[$cnt]['era']   = $item['era'];
 441:         $cnt++;
 442:     }
 443:     //命日
 444:     foreach ($sour1['death'as $item) {
 445:         $event = "{$item['name']}({$item['profile']})死去";
 446:         if ($item['plus'!'')    $event ."({$item['plus']}年~{$item['year']}年)";
 447:         $dest[$cnt]['year']  = $item['year'];
 448:         $dest[$cnt]['event'] = $event;
 449:         $dest[$cnt]['era']   = $item['era'];
 450:         $cnt++;
 451:     }
 452:     //記念日
 453:     foreach ($sour2 as $item) {
 454:         $dest[$cnt]['year']  = KINENBI_YEAR;
 455:         $dest[$cnt]['event'] = $item['title'];
 456:         $dest[$cnt]['era']   = '';
 457:         $dest[$cnt]['url']   = $item['url'];
 458:         $cnt++;
 459:     }
 460: 
 461:     //ソート
 462:     usort($dest, function ($a, $b) {
 463:         return (int)($a['year'- $b['year']);
 464:     });
 465: 
 466:     return $cnt;
 467: }

ユーザー関数 getDayToday および getAnniversary によって取得した「できごと」「誕生日」「忌日」「記念日」を年号順にソートする関数が sortDayToday である。
組み込み関数  usort  を利用し、年号順に配列をソートしている。

解説:指定年月日から何日か離れた年月日

 643: $month = getParam('month', FALSE, 0);
 644: if ($month == 0)    $month = date('n');
 645: $day   = getParam('day', FALSE, 0);
 646: if ($day == 0)  $day = date('j');
 647: $year  = getParam('year', FALSE, 0);
 648: if ($year == 0$year = date('Y');
 649: $diff  = getInteger('diff', -100, +100, 0);
 650: list($year, $montjh, $day) = diffymd($year, $month, $day, $diff);
 651: $era  = getParam('era', FALSE, 1);
 652: $era = ($era == 1? FALSE : TRUE;
 653: $lfn  = getParam('lfn', FALSE, 1);          //Ver.1.43
 654: $lfn = ($lfn == 1? FALSE : TRUE;          //Ver.1.43

 191: /**
 192:  * 指定した年月の日数を返す
 193:  * @param   int $year  年
 194:  * @param   int $month 月
 195:  * @return  int 日数/FALSE:引数の異常
 196: */
 197: function getDaysInMonth($year, $month) {
 198:     static $days = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
 199:     if ($month < 1 || $month > 12)   return FALSE;
 200: 
 201:     $dm = (($month == 2&& isLeapYear($year)) ? 29 : $days[$month];
 202: 
 203:     return $dm;
 204: }

 206: /**
 207:  * 指定年月日から$diff日だけ離れた年月日を取得する
 208:  * @param   int $year  西暦年
 209:  * @param   int $month 月
 210:  * @param   int $day   日
 211:  * @param   int $diff  差分日数
 212:  * @return  array(年,月,日)
 213: */
 214: function diffymd($year, $month, $day, $diff) {
 215:     $dm = getDaysInMonth($year, $month);
 216: 
 217:     $day +$diff;
 218:     //月の繰り下がり
 219:     while ($day < 1) {
 220:         $month--;
 221:         if ($month < 1)  $year--;
 222:         $dm = getDaysInMonth($year, $month);
 223:         $day +$dm;
 224:     }
 225:     //月の繰り上がり
 226:     while ($day > $dm) {
 227:         $dm = getDaysInMonth($year, $month);
 228:         $day -$dm;
 229:         $month++;
 230:         if ($month > 12)    $year++;
 231:     }
 232: 
 233:     return array($year, $month, $day);
 234: }

ユーザー関数 diffymd は、$year$month$day日から $diff日だけ離れた年月日を取得する。$diff に正数を指定すると未来の日付に、負数を指定するとかこの日付にさかのぼる。
月の大小が異なることから getDaysInMonth を呼び出し、うるう年の判定は isleap を呼び出すようにしてある。

組み込み関数  strtotime  を使えば簡便に書けるのだが、この関数は32ビット環境ではUNIXタイムスタンプで1901年2月13日から2038年1月19日までしか計算できないため、あえてユーザー関数を用意した。

なお、コマンドラインから "DayToday.php?diff=+2" のように指定することで $diff を直接指定できる。
また、メイン・プログラムで $diff = +1 のように書き換えれば、「明日は何の日」を求めるプログラムに変身する。

活用例

きょうは何の日?:みんなの知識 ちょっと便利帳」では、このサンプル・プログラムを活用し、情報を見やすくレイアウトしている。ありがとうございます。

参考サイト

(この項おわり)
header