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

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

(2022年10月15日)紀元前のHTMLパターン変更に対応

目次

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

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

サンプル・プログラム

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

0253: /**
0254:  * Wikipedia URL取得
0255:  * @param   int $month, $day 月,日
0256:  * @return  string URL
0257: */
0258: function getURL_Wikipedia($month$day) {
0259:     $url = 'http://ja.wikipedia.org/wiki/' . urlencode(sprintf('%d月%d日', $month$day));
0260: 
0261:     return $url;
0262: }

0264: /**
0265:  * 今日は何の日 取得
0266:  * @param   int    $month, $day 月,日
0267:  * @param   array  $items  情報格納配列
0268:  * @param   string $errmsg エラーメッセージ格納用
0269:  * @param   bool   $lfn    脚注残す(省略時=FALSE)
0270:  * @return  int 情報件数
0271: */
0272: function getDayToday($month$day, &$items, &$errmsg$lfn=FALSE) {
0273:     $pat1 = "/<span[\s\S]+id=\"できごと\">/iu";         //ver.1.42
0274:     $pat2 = "/<span[\s\S]+id=\"誕生日\">/iu";          //ver.1.42
0275:     $pat3 = "/<span[\s\S]+id=\"忌日\">/iu";               //ver.1.42
0276:     $pat5 = "/<li>[^\>]+([0-9]+(年|世紀).+)<\/li>/iu"; //年 - 内容 ver.2.11
0277:     $pat6 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+(.+)/iu";            //年 - イベント Ver.1.32
0278:     $pat7 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+([^、]+)、?([^(\(]+)*([^0-9]*)([0-9]*)/iu";    //年 - 名前、職業(生没年) Ver.1.43
0279:     $pat8 = "/<span[\s\S]+id=\"フィクションのできごと\">/iu";  //ver.1.5
0280: 
0281:     $url = getURL_Wikipedia($month$day);
0282: 
0283:     $infp = @fopen($url, 'r');
0284:     if ($infp == FALSE) {
0285:         $errmsg = 'Wikipediaにアクセスできません.';
0286:         return FALSE;
0287:     }
0288: 
0289:     $mode = '';
0290:     $cnt = 1;
0291:     while (! feof($infp)) {
0292:         $str = fgets($infp);
0293:         if (preg_match($pat1$str) == 1) {
0294:             $mode = 'event';
0295:         } else if (preg_match($pat8$str) == 1) {        //Ver.1.5
0296:             $mode = 'fiction';                          //Ver.1.5
0297:         } else if (preg_match($pat2$str) == 1) {
0298:             $mode = 'birth';
0299:         } else if (preg_match($pat3$str) == 1) {
0300:             $mode = 'death';
0301:         } else if ($mode != '' && preg_match($pat5$str$arr) > 0) {
0302:             $str = strip_tags(trim($str));
0303:             if ($mode == 'event' && preg_match($pat6$str$arr) == 1) {
0304:                 $items[$mode][$cnt]['year']  = ($arr[1] == '') ? (int)$arr[2] : 0 - $arr[2];
0305:                 $items[$mode][$cnt]['era']   = $arr[3];
0306:                 $items[$mode][$cnt]['event'] = $lfn ? $arr[4] : eliminate_footnote($arr[4]);
0307:                 $cnt++;
0308:             } else if (preg_match($pat7$str$arr) == 1) {
0309:                 $items[$mode][$cnt]['year']  = ($arr[1] == '') ? (int)$arr[2] : 0 - $arr[2];
0310:                 $items[$mode][$cnt]['era']     = $arr[3];
0311:                 $items[$mode][$cnt]['name']    = $lfn ? $arr[4] : eliminate_footnote($arr[4]);
0312:                 $items[$mode][$cnt]['profile'] = $lfn ? $arr[5] : eliminate_footnote($arr[5]);
0313:                 if (preg_match('/紀元前/u', $arr[6]) > 0) {            //Ver.2.11
0314:                     $items[$mode][$cnt]['plus']  = 0 - $arr[7];
0315:                 } else if (preg_match('/^\([\*\+]./', $arr[6]) > 0) {    //Ver.1.43
0316:                     $items[$mode][$cnt]['plus']  = $arr[7];
0317:                 } else {
0318:                     $items[$mode][$cnt]['plus']  = '';
0319:                 }
0320:                 $cnt++;
0321:             }
0322:         }
0323:     }
0324:     fclose($infp);
0325: 
0326:     if ($cnt == 0) {
0327:         $errmsg = '記念日が見つかりません.';
0328:     }
0329: 
0330:     return $cnt;
0331: }

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

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

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

0413: /**
0414:  * 今日は何の日 年号順にソート
0415:  * @param   array $sour1 ソート前配列(Wikipedia)
0416:  * @param   array $sour2 ソート前配列(日本記念日協会)
0417:  * @param   array $dest  ソード後配列
0418:  * @return  int 情報件数
0419: */
0420: function sortDayToday($sour1$sour2, &$dest) {
0421:     $cnt = 0;
0422: 
0423:     //できごと
0424:     foreach ($sour1['event'] as $item) {
0425:         $dest[$cnt]['year']  = $item['year'];
0426:         $dest[$cnt]['event'] = $item['event'];
0427:         $dest[$cnt]['era']   = $item['era'];
0428:         $cnt++;
0429:     }
0430:     //誕生日
0431:     foreach ($sour1['birth'] as $item) {
0432:         $event = "{$item['name']}({$item['profile']})誕生";
0433:         if ($item['plus'] != '')    $event .= "(~{$item['plus']}年)";
0434:         $dest[$cnt]['year']  = $item['year'];
0435:         $dest[$cnt]['event'] = $event;
0436:         $dest[$cnt]['era']   = $item['era'];
0437:         $cnt++;
0438:     }
0439:     //命日
0440:     foreach ($sour1['death'] as $item) {
0441:         $event = "{$item['name']}({$item['profile']})死去";
0442:         if ($item['plus'] != '')    $event .= "({$item['plus']}年~{$item['year']}年)";
0443:         $dest[$cnt]['year']  = $item['year'];
0444:         $dest[$cnt]['event'] = $event;
0445:         $dest[$cnt]['era']   = $item['era'];
0446:         $cnt++;
0447:     }
0448:     //記念日
0449:     foreach ($sour2 as $item) {
0450:         $dest[$cnt]['year']  = KINENBI_YEAR;
0451:         $dest[$cnt]['event'] = $item['title'];
0452:         $dest[$cnt]['era']   = '';
0453:         $dest[$cnt]['url']   = $item['url'];
0454:         $cnt++;
0455:     }
0456: 
0457:     //ソート
0458:     usort($destfunction ($a$b) {
0459:         return (int)($a['year'] - $b['year']);
0460:     });
0461: 
0462:     return $cnt;
0463: }

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

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

0639: $month = getParam('month', FALSE, 0);
0640: if ($month == 0)    $month = date('n');
0641: $day   = getParam('day', FALSE, 0);
0642: if ($day == 0)  $day = date('j');
0643: $year  = getParam('year', FALSE, 0);
0644: if ($year == 0) $year = date('Y');
0645: $diff  = getInteger('diff', -100, +100, 0);
0646: list($year$montjh$day) = diffymd($year$month$day$diff);
0647: $era  = getParam('era', FALSE, 1);
0648: $era = ($era == 1) ? FALSE : TRUE;
0649: $lfn  = getParam('lfn', FALSE, 1);          //Ver.1.43
0650: $lfn = ($lfn == 1) ? FALSE : TRUE;            //Ver.1.43

0177: /**
0178:  * 閏年かどうか判定する
0179:  * @param   int $year 西暦年
0180:  * @return  bool TRUE:閏年である/FALSE:平年である
0181: */
0182: function isleap($year) {
0183:     $ret = FALSE;
0184:     if ($year % 4 == 0) $ret = TRUE;
0185:     if ($year % 100 == 0)   $ret = FALSE;
0186:     if ($year % 400 == 0)   $ret = TRUE;
0187:     return $ret;
0188: }

0190: /**
0191:  * 指定した年月の日数を返す
0192:  * @param   int $year  年
0193:  * @param   int $month 月
0194:  * @return  int 日数/FALSE:引数の異常
0195: */
0196: function getDaysInMonth($year$month) {
0197:     static $days = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
0198:     if ($month < 1 || $month > 12)  return FALSE;
0199: 
0200:     $dm = (($month == 2) && isleap($year)) ? 29 : $days[$month];
0201: 
0202:     return $dm;
0203: }

0205: /**
0206:  * 指定年月日から$diff日だけ離れた年月日を取得する
0207:  * @param   int $year  西暦年
0208:  * @param   int $month 月
0209:  * @param   int $day   日
0210:  * @param   int $diff  差分日数
0211:  * @return  array(年,月,日)
0212: */
0213: function diffymd($year$month$day$diff) {
0214:     $dm = getDaysInMonth($year$month);
0215: 
0216:     $day += $diff;
0217:     //月の繰り下がり
0218:     while ($day < 1) {
0219:         $month--;
0220:         if ($month < 1) $year--;
0221:         $dm = getDaysInMonth($year$month);
0222:         $day += $dm;
0223:     }
0224:     //月の繰り上がり
0225:     while ($day > $dm) {
0226:         $dm = getDaysInMonth($year$month);
0227:         $day -= $dm;
0228:         $month++;
0229:         if ($month > 12)    $year++;
0230:     }
0231: 
0232:     return array($year$month$day);
0233: }

ユーザー関数 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