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

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

目次

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

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.3 2023/01/05 getDayToday()の取得パターンを変更した
2.12 2022/10/15 人物の紀元前パターンに対応
2.11 2022/10/15 紀元前のHTMLパターン変更に対応
2.1 2021/10/10 eliminate_footnote(): 脚注パターン追加
2.0 2021/01/26 annivオプションを追加(記念日を表示)

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

 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:     $pat5 = "/<li>[^\>]+([0-9]+(年|世紀).+)<\/li>/iu";  //年 - 内容 ver.2.11
 278:     $pat6 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+(.+)/iu";            //年 - イベント Ver.1.32
 279:     $pat7 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+([^、]+)、?([^(\(]+)*([^0-9]*)([0-9]*)/iu"//年 - 名前、職業(生没年) Ver.1.43
 280:     $pat8 = "/<span[\s\S]+id=\"フィクションのできごと\">/iu";   //ver.1.5
 281: 
 282:     $url = getURL_Wikipedia($month, $day);
 283: 
 284:     $infp = @fopen($url, 'r');
 285:     if ($infp == FALSE) {
 286:         $errmsg = 'Wikipediaにアクセスできません.';
 287:         return FALSE;
 288:     }
 289: 
 290:     $mode = '';
 291:     $cnt = 1;
 292:     while (! feof($infp)) {
 293:         $str = fgets($infp);
 294:         if (preg_match($pat1, $str) == 1) {
 295:             $mode = 'event';
 296:         } else if (preg_match($pat8, $str) == 1) {      //Ver.1.5
 297:             $mode = 'fiction';                          //Ver.1.5
 298:         } else if (preg_match($pat2, $str) == 1) {
 299:             $mode = 'birth';
 300:         } else if (preg_match($pat3, $str) == 1) {
 301:             $mode = 'death';
 302:         } else if ($mode !'' && preg_match($pat5, $str, $arr> 0) {
 303:             $str = strip_tags(trim($str));
 304:             if ($mode == 'event' && preg_match($pat6, $str, $arr) == 1) {
 305:                 $items[$mode][$cnt]['year']  = ($arr[1] == ''? (int)$arr[2: 0 - $arr[2];
 306:                 $items[$mode][$cnt]['era']   = $arr[3];
 307:                 $items[$mode][$cnt]['event'] = $lfn ? $arr[4: eliminate_footnote($arr[4]);
 308:                 $cnt++;
 309:             } else if (preg_match($pat7, $str, $arr) == 1) {
 310:                 $items[$mode][$cnt]['year']  = ($arr[1] == ''? (int)$arr[2: 0 - $arr[2];
 311:                 $items[$mode][$cnt]['era']     = $arr[3];
 312:                 $items[$mode][$cnt]['name']    = $lfn ? $arr[4: eliminate_footnote($arr[4]);
 313:                 $items[$mode][$cnt]['profile'] = $lfn ? $arr[5: eliminate_footnote($arr[5]);
 314:                 if (preg_match('/紀元前/u', $arr[6]) > 0) {         //Ver.2.11
 315:                     $items[$mode][$cnt]['plus']  = 0 - $arr[7];
 316:                 } else if (preg_match('/^\([\*\+]./', $arr[6]) > 0) {   //Ver.1.43
 317:                     $items[$mode][$cnt]['plus']  = $arr[7];
 318:                 } else {
 319:                     $items[$mode][$cnt]['plus']  = '';
 320:                 }
 321:                 $cnt++;
 322:             }
 323:         }
 324:     }
 325:     fclose($infp);
 326: 
 327:     if ($cnt == 0) {
 328:         $errmsg = '記念日が見つかりません.';
 329:     }
 330: 
 331:     return $cnt;
 332: }

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

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

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

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

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

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

 640: $month = getParam('month', FALSE, 0);
 641: if ($month == 0)    $month = date('n');
 642: $day   = getParam('day', FALSE, 0);
 643: if ($day == 0)  $day = date('j');
 644: $year  = getParam('year', FALSE, 0);
 645: if ($year == 0$year = date('Y');
 646: $diff  = getInteger('diff', -100, +100, 0);
 647: list($year, $montjh, $day) = diffymd($year, $month, $day, $diff);
 648: $era  = getParam('era', FALSE, 1);
 649: $era = ($era == 1? FALSE : TRUE;
 650: $lfn  = getParam('lfn', FALSE, 1);          //Ver.1.43
 651: $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