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

(1/1)
Wikipedia には、指定した月日のできごとが記録されている。
これを PHP でスクレイピングすることで、今日は何の日か表示するプログラムを作成する。

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

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

サンプル・プログラム

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

0191: /**
0192:  * Wikipedia URL取得
0193:  * @param int $month, $day 月,日
0194:  * @return string URL
0195: */
0196: function getURL_Wikipedia($month$day) {
0197:     $url = 'http://ja.wikipedia.org/wiki/' . urlencode(sprintf('%d月%d日', $month$day));
0198: 
0199:     return $url;
0200: }
0201: 
0202: /**
0203:  * 今日は何の日 取得
0204:  * @param int $month, $day 月,日
0205:  * @param array $items 情報格納配列
0206:  * @param bool $lfn 脚注残す(省略時=FALSE)
0207:  * @return int 情報件数
0208: */
0209: function getDayToday($month$day, &$items$lfn=FALSE) {
0210:     $pat1 = "/<span[\s\S]+id=\"できごと\">/iu";            //ver.1.42
0211:     $pat2 = "/<span[\s\S]+id=\"誕生日\">/iu";                //ver.1.42
0212:     $pat3 = "/<span[\s\S]+id=\"忌日\">/iu";                //ver.1.42
0213:     $pat5 = "/<li>[^\>]+([0-9]+年.+)<\/li>/iu";         //年 - 内容
0214:     $pat6 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+(.+)/iu";         //年 - イベント Ver.1.32
0215:     $pat7 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+([^、]+)、?([^(\(]+)*([^0-9]*)([0-9]*)/iu"; //年 - 名前、職業(生没年) Ver.1.43
0216: 
0217:     $url = getURL_Wikipedia($month$day);
0218: 
0219:     $infp = @fopen($url, 'r');
0220:     if ($infp == FALSE)     return FALSE;
0221: 
0222:     $mode = '';
0223:     $cnt = 1;
0224:     while (! feof($infp)) {
0225:         $str = fgets($infp);
0226:         if (preg_match($pat1$str) == 1) {
0227:             $mode = 'event';
0228:         } else if (preg_match($pat2$str) == 1) {
0229:             $mode = 'birth';
0230:         } else if (preg_match($pat3$str) == 1) {
0231:             $mode = 'death';
0232:         } else if ($mode != '' && preg_match($pat5$str$arr) > 0) {
0233:             $str = strip_tags(trim($str));
0234:             if ($mode == 'event' && preg_match($pat6$str$arr) == 1) {
0235:                 $items[$mode][$cnt]['year']  = ($arr[1] == '') ? (int)$arr[2] : 0 - $arr[2];
0236:                 $items[$mode][$cnt]['era']   = $arr[3];
0237:                 $items[$mode][$cnt]['event'] = $lfn ? $arr[4] : eliminate_footnote($arr[4]);   //Ver.1.43
0238:                 $cnt++;
0239:             } else if (preg_match($pat7$str$arr) == 1) {
0240:                 $items[$mode][$cnt]['year']  = ($arr[1] == '') ? (int)$arr[2] : 0 - $arr[2];
0241:                 $items[$mode][$cnt]['era']     = $arr[3];
0242:                 $items[$mode][$cnt]['name']    = $arr[4];
0243:                 $items[$mode][$cnt]['profile'] = $lfn ? $arr[5] : eliminate_footnote($arr[5]);   //Ver.1.43
0244:                 if (preg_match('/^\([\*\+]./', $arr[6]) > 0) {  //Ver.1.43
0245:                     $items[$mode][$cnt]['plus']  = $arr[7];
0246:                 } else {
0247:                     $items[$mode][$cnt]['plus']  = '';
0248:                 }
0249:                 $cnt++;
0250:             }
0251:         }
0252:     }
0253:     fclose($infp);
0254: 
0255:     return $cnt;
0256: }

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

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

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

0258: /**
0259:  * 今日は何の日 年号順にソート
0260:  * @param array $sour ソート前配列
0261:  * @param array $dest  ソード後配列
0262:  * @return int 情報件数
0263: */
0264: function sortDayToday($sour, &$dest) {
0265:     $cnt = 0;
0266: 
0267:     //できごと
0268:     foreach ($sour['event'] as $item) {
0269:         $dest[$cnt]['year']  = $item['year'];
0270:         $dest[$cnt]['event'] = $item['event'];
0271:         $dest[$cnt]['era']   = $item['era'];
0272:         $cnt++;
0273:     }
0274:     //誕生日
0275:     foreach ($sour['birth'] as $item) {
0276:         $event = "{$item['name']}({$item['profile']})誕生";
0277:         if ($item['plus'] != '')    $event .= "(~{$item['plus']}年)";
0278:         $dest[$cnt]['year']  = $item['year'];
0279:         $dest[$cnt]['event'] = $event;
0280:         $dest[$cnt]['era']   = $item['era'];
0281:         $cnt++;
0282:     }
0283:     //命日
0284:     foreach ($sour['death'] as $item) {
0285:         $event = "{$item['name']}({$item['profile']})死去";
0286:         if ($item['plus'] != '')    $event .= "({$item['plus']}年~{$item['year']}年)";
0287:         $dest[$cnt]['year']  = $item['year'];
0288:         $dest[$cnt]['event'] = $event;
0289:         $dest[$cnt]['era']   = $item['era'];
0290:         $cnt++;
0291:     }
0292: 
0293:     //ソート
0294:     usort($destfunction ($a$b) {
0295:         return $a['year'] - $b['year'];
0296:     });
0297: 
0298:     return $cnt;
0299: }

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

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

0447: $diff  = getInteger('diff', -100, +100, 0);
0448: list($year$montjh$day) = diffymd($year$month$day$diff);

0122: /**
0123:  * 閏年かどうか判定する
0124:  * @param int $year 西暦年
0125:  * @return bool TRUE:閏年である/FALSE:平年である
0126: */
0127: function isleap($year) {
0128:     $ret = FALSE;
0129:     if ($year % 4 == 0) $ret = TRUE;
0130:     if ($year % 100 == 0)   $ret = FALSE;
0131:     if ($year % 400 == 0)   $ret = TRUE;
0132:     return $ret;
0133: }
0134: 
0135: /**
0136:  * 指定した年月の日数を返す
0137:  * @param int $year  年
0138:  * @param int $month 月
0139:  * @return int 日数/FALSE:引数の異常
0140: */
0141: function getDaysInMonth($year$month) {
0142:     static $days = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
0143:     if ($month < 1 || $month > 12)  return FALSE;
0144: 
0145:     $dm = (($month == 2) && isleap($year)) ? 29 : $days[$month];
0146: 
0147:     return $dm;
0148: }
0149: 
0150: /**
0151:  * 指定年月日から$diff日だけ離れた年月日を取得する
0152:  * @param int $year  西暦年
0153:  * @param int $month 月
0154:  * @param int $day   日
0155:  * @param int $diff  差分日数
0156:  * @return array(年,月,日)
0157: */
0158: function diffymd($year$month$day$diff) {
0159:     $dm = getDaysInMonth($year$month);
0160: 
0161:     $day += $diff;
0162:     //月の繰り下がり
0163:     while ($day < 1) {
0164:         $month--;
0165:         if ($month < 1) $year--;
0166:         $dm = getDaysInMonth($year$month);
0167:         $day += $dm;
0168:     }
0169:     //月の繰り上がり
0170:     while ($day > $dm) {
0171:         $dm = getDaysInMonth($year$month);
0172:         $day -= $dm;
0173:         $month++;
0174:         if ($month > 12)    $year++;
0175:     }
0176: 
0177:     return array($year$month$day);
0178: }

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