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

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

(2026年4月10日)getDayToday() 「年」取得パターンを変更
(2025年9月11日)getDayToday() 「できごと」取得パターンを変更
(2025年9月6日)Wikipediaの仕様変更への対応=pahooCacheクラスを導入した.pahooCacheクラスにHTTPヘッダ送信機能を追加した.

目次

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

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サンプル・プログラム本体。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooCache.phpキャッシュ処理に関わるクラス pahooCache。
キャッシュ処理に関わるクラスの使い方は「PHPで天気予報を求める」を参照。include_path が通ったディレクトリに配置すること。
DayToday.php 更新履歴
バージョン 更新日 内容
2.2.2 2026/04/10 getDayToday() 「年」取得パターンを変更
2.2.1 2025/09/11 getDayToday() 「できごと」取得パターンを変更
2.2.0 2025/09/06 pahooCacheクラス導入
2.1.4 2024/07/27 getDayToday()の取得パターンを変更した
2.1.3 2023/01/05 getDayToday()の取得パターンを変更した
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加
pahooCache.php 更新履歴
バージョン 更新日 内容
1.3.0 2025/12/06 PHP8.5対応:curl_closeを使わない
1.2.0 2025/09/06 cLoad() HTTPヘッダを送信できるようにした
1.1.3 2025/08/10 var→public
1.1.2 2023/07/22 bug-fix
1.1.1 2023/02/11 コメント追記

準備:PHP の https対応

クラウド連携や相手先サイトのデータを読み込むのに https通信を使うため、PHPに OpenSSLモジュールが組み込まれている必要がある。関数  phpinfo  を使って、下図のように表示されればOKだ。
OpenSSL - PHP
そうでない場合は、次の手順に従ってOpenSSLを有効化し、PHPを再起動させる必要がある。

Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dll
Linuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

準備:pahooCache クラス

pahooCache.php

  13: class pahooCache {
  14:     const LIFE_CACHE = (2 * 60);        // キャッシュ保持時間(デフォルト;分)
  15:     const DEF_DIRCACHE = './pcache/';   // キャッシュ・ディレクトリ(デフォルト)
  16: 
  17:     public $lifeCache;      // キャッシュ保持時間(分)(0:キャッシュしない)
  18:     public $dirCache;       // キャッシュ用ディレクトリ
  19:     public $httpHeader;     // HTTPヘッダ(空文字の時は何も送らない)
  20:     public $error;          // エラーフラグ
  21:     public $errmsg;         // エラーメッセージ
  22:     public $debug;          // デバッグ用ファイル名
  23: 
  24: /**
  25:  * コンストラクタ
  26:  * 参考サイト https://www.pahoo.org/e-soul/webtech/php06/php06-72-01.shtm
  27:  * @param   int    $life キャッシュ保持時間(分)(省略可能)
  28:  * @param   string $dir キャッシュ・ディレクトリ(省略可能)
  29:  * @param   array  $httpHeader httpヘッダに渡す配列(省略可能)
  30:  *          USER AGENT偽装に用いることを想定
  31:  *          (例)
  32:  *           array(
  33:  *              'User-Agent: Mozilla/5.0(Windows NT 10.0; Win64; x64) pahooAppy/pahoo.org AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
  34:  *              'Accept-Language: ja-JP'
  35:  *          );
  36:  * @return  なし
  37: */
  38: function __construct($life=self::LIFE_CACHE, $dir=self::DEF_DIRCACHE, $httpHeader=NULL) {
  39:     if ($life < 0) {
  40:         $life = 0;
  41:     }
  42:     if (preg_match('/\/$/ui', $dir) == 0) {
  43:         $dir = $dir . '/';
  44:     }
  45:     $this->error      = FALSE;
  46:     $this->errmsg     = '';
  47:     $this->debug      = '';
  48:     $this->lifeCache  = $life;
  49:     $this->dirCache   = $dir;
  50:     $this->httpHeader = $httpHeader;
  51: 
  52:     // PHP5以上であることを調べる.
  53:     if (! $this->isphp5over()) {
  54:         $this->error   = TRUE;
  55:         $this->errmsg  = '動作にはPHP5以上が必要です';
  56:         return;
  57:     }
  58: 
  59:     // キャッシュ・ディレクトリが無ければ作成する.
  60:     if (! is_dir($this->dirCache)) {
  61:         $res = mkdir($this->dirCache, 0744);
  62:         if ($res == FALSE) {
  63:             $this->error   = TRUE;
  64:             $this->errmsg  = 'キャッシュ・ディレクトリ "' . $this->$dirCache . '" の作成に失敗しました';
  65:             return;
  66:         }
  67:     }
  68: }

クラウド連携や相手先サイトの公開データを利用する際、こちらのアプリを起動する都度、APIを呼び出したりデータをダウンロードするのでは、相手サーバに負荷をかけてしまう。
そこで、頻繁に変更がないデータについては、一度取り込んだら、こちら側のサーバのローカルストレージにキャッシュしておく仕組みを用意した。それが pahooCacheクラス である。同梱のクラス・ファイル "pahooCache.php" は include_path が通ったディレクトリに配置してほしい。他のプログラムでも pahooCacheクラス を利用するが、常に最新のクラス・ファイルを1つ配置すればよい。

pahooCacheクラス の注意ポイントは、キャッシュ時間(単位:分)とキャッシュを保存するディレクトリをコンストラクタで指定している点だ。これらはプログラムによって変わるものである。インスタンス化するときの値は、pahooCacheクラス を利用するメイン・プログラムの方で解説する。

サイトによっては、User-Agent などを必要とすることがあるだろう。そこで、第3引数に HTTPヘッダ として送信するデータを配列で渡すことができるようにした。配列の構造はコメントを参照していただきたい。

PHPのクラスについては「PHPでクラスを使ってテキストの読みやすさを調べる」を参照されたい。

準備:各種定数など

DayToday.php

  64: // 各種定数(START) ===========================================================
  65: 
  66: // 表示幅(ピクセル)
  67: define('WIDTH', 600);
  68: 
  69: // リクエストURL(日本記念日協会)(※変更不可)
  70: define('KINENBI_URL', 'https://www.kinenbi.gr.jp/');
  71: 
  72: // 記念日識別値(※変更不可)
  73: define('KINENBI_YEAR', (-99999));
  74: 
  75: // キャッシュ保持時間(分) 0:キャッシュしない
  76: // Wikipediaへのアクセス負荷軽減のため,720分以上のキャッシュ保持をお勧めする.
  77: define('LIFE_CACHE', 1440);
  78: 
  79: // キャッシュ・ディレクトリ
  80: // 書き込み可能で,外部からアクセスされないディレクトリを指定すること.
  81: // 最大約200Mバイトを消費します.
  82: define('DIR_CACHE', './pcache/');
  83: 
  84: // Wikipediaアクセス時に送信するHTTPヘッダ
  85: $HttpHeader = array(
  86:     'User-Agent: Mozilla/5.0(Windows NT 10.0; Win64; x64) pahooAppy/pahoo.org AppleWebKit/537.36 (KHTML, like Gecko) Chrome/139.0.0.0 Safari/537.36',
  87:     'Accept-Language: ja-JP'
  88: );
  89: 
  90: // 各種定数(END) ===============================================================

各種パラメータは定数を define している。とくに変更不可の記載のないものは、自由に変更してかまわない。

後述するように pahooCache クラスを使用する。定数 LIFE_CACHE にキャッシュ保持時間を、定数 DIR_CACHE にキャッシュ・ディレクトリを設定する。それぞれの設定値についてはコメントを参照のこと。
定数 DIR_CACHE の容量だが、Wikipediaの月日の記事が1つあたり0.5Mバイトとして 0.5×365=182.5Mバイトから 200Mバイト程度は必要である。
制約事項であるが、定数 LIFE_CACHE で指定した期間中は、Wikipediaサイトからデータを取ってこない。したがって、この期間中に Wikipediaサイトが編集されたとしても、その内容は反映されない。

2025年9月現在、Wikipediaにアクセスするのに User-Agent が必要になった。すべてのコンテンツについて必要かどうかは確認していないが、念のため、User-AgentAccep--Language を送信するようにした。その値を設定しているのがグローバル配列 $HttpHeader である。
User-Agent の内容は自由に変更して構わない。また、これ以外にも HTTPヘッダとして送るデータが出てきたら、配列 $HttpHeader に追加してほしい。

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

DayToday.php

 262: /**
 263:  * Wikipedia URL取得
 264:  * @param   int $month, $day 月,日
 265:  * @return  string URL
 266: */
 267: function getURL_Wikipedia($month, $day) {
 268:     $url = 'http://ja.wikipedia.org/wiki/' . urlencode(sprintf('%d月%d日', $month, $day));
 269: 
 270:     return $url;
 271: }

DayToday.php

 273: /**
 274:  * 今日は何の日 取得
 275:  * @param   int    $month, $day 月,日
 276:  * @param   array  $items  情報格納配列
 277:  * @param   string $errmsg エラーメッセージ格納用
 278:  * @param   bool   $lfn    脚注残す(省略時=FALSE)
 279:  * @return  int 情報件数
 280: */
 281: function getDayToday($month, $day, &$items, &$errmsg, $lfn=FALSE) {
 282:     // スクレイピング・パターン
 283:     $pat1 = "/<h2\s+id\=\"(できごと|出来事)\"/iu";      // ver.2.2.1
 284:     $pat2 = "/<h2\s+id\=\"誕生日\"/iu";                 // ver.2.1.4
 285:     $pat3 = "/<h2\s+id\=\"忌日\"/iu";                   // ver.2.1.4
 286:     $pat5 = "/<li[^\>]*>[^\>]+([0-9]+(年|世紀).+)<\/li>/iu";    //  - 内容 ver.2.2.2
 287:     $pat6 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+(.+)/iu";            //  - イベント Ver.1.32
 288:     $pat7 = "/([^0-9]*)([0-9]+)年(\s*[\((].[^\))]+[)\)].)?[ \-]+([^、]+)、?([^(\(]+)*([^0-9]*)([0-9]*)/iu"; //  - 名前、職業(生没年) Ver.1.43
 289:     $pat8 = "/<span[\s\S]+id=\"フィクションのできごと\">/iu";   // ver.1.5
 290: 
 291:     // インスタンス生成
 292:     global $HttpHeader;
 293:     $pcc = new pahooCache(LIFE_CACHE, DIR_CACHE, $HttpHeader);
 294: 
 295:     // Wikipediaの指定月日の記事URL
 296:     $url = getURL_Wikipedia($month, $day);
 297: 
 298:     // Wikipediaの記事を読み込む
 299:     $contents = $pcc->load($url);
 300:     if ($contents == FALSE) {
 301:         $errmsg = 'Wikipediaにアクセスできません.';
 302:         return FALSE;
 303:     }
 304: 
 305:     // インスタンス解放
 306:     $pcc = NULL;
 307: 
 308:     // $contents をファイルのようにして扱う (PHP 5.1以上)
 309:     $infp = fopen('php://memory', 'r+');
 310:     fwrite($infp, $contents);
 311:     rewind($infp);
 312: 
 313:     $mode = '';
 314:     $cnt = 1;
 315:     while (! feof($infp)) {
 316:         $str = fgets($infp);
 317:         if (preg_match($pat1, $str) == 1) {
 318:             $mode = 'event';
 319:         } else if (preg_match($pat8, $str) == 1) {      // Ver.1.5
 320:             $mode = 'fiction';                          // Ver.1.5
 321:         } else if (preg_match($pat2, $str) == 1) {
 322:             $mode = 'birth';
 323:         } else if (preg_match($pat3, $str) == 1) {
 324:             $mode = 'death';
 325:         } else if ($mode !'' && preg_match($pat5, $str, $arr> 0) {
 326:             $str = strip_tags(trim($str));
 327:             if ($mode == 'event' && preg_match($pat6, $str, $arr) == 1) {
 328:                 $items[$mode][$cnt]['year']  = ($arr[1] == ''? (int)$arr[2: 0 - $arr[2];
 329:                 $items[$mode][$cnt]['era']   = $arr[3];
 330:                 $items[$mode][$cnt]['event'] = $lfn ? $arr[4: eliminate_footnote($arr[4]);
 331:                 $cnt++;
 332:             } else if (preg_match($pat7, $str, $arr) == 1) {
 333:                 $items[$mode][$cnt]['year']  = ($arr[1] == ''? (int)$arr[2: 0 - $arr[2];
 334:                 $items[$mode][$cnt]['era']     = $arr[3];
 335:                 $items[$mode][$cnt]['name']    = $lfn ? $arr[4: eliminate_footnote($arr[4]);
 336:                 $items[$mode][$cnt]['profile'] = $lfn ? $arr[5: eliminate_footnote($arr[5]);
 337:                 if (preg_match('/紀元前/u', $arr[6]) > 0) {         // Ver.2.11
 338:                     $items[$mode][$cnt]['plus']  = 0 - $arr[7];
 339:                 } else if (preg_match('/^\([\*\+]./', $arr[6]) > 0) {   // Ver.1.43
 340:                     $items[$mode][$cnt]['plus']  = $arr[7];
 341:                 } else {
 342:                     $items[$mode][$cnt]['plus']  = '';
 343:                 }
 344:                 $cnt++;
 345:             }
 346:         }
 347:     }
 348:     fclose($infp);
 349: 
 350:     if ($cnt == 0) {
 351:         $errmsg = '記念日が見つかりません.';
 352:     }
 353: 
 354:     return $cnt;
 355: }

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

まず、pahooCacheクラス を使って、定数 LIFE_CACHE で指定した期間中はローカルにキャッシュされたデータを読み込み、その期間を過ぎたらサイトからキャッシュへ Wikipedia の当該コンテンツを読み込む。
キャッシュからの読み込みは、load メソッドを使ってコンテンツを丸ごと変数 $contents に読み込む。変数 $contents から1行ずつ取りだしてスクレイピングをしていくのだが、あたかもファイルから1行ずつ取りだしているかのように処理するために、fopen('php://memory', 'r+'):blue を利用した。このファイルポインタの使い方は、PHP 5.1 以上で実装されている。

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

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

DayToday.php

 437: /**
 438:  * 今日は何の日 年号順にソート
 439:  * @param   array $sour1 ソート前配列(Wikipedia)
 440:  * @param   array $sour2 ソート前配列(日本記念日協会)
 441:  * @param   array $dest  ソード後配列
 442:  * @return  int 情報件数
 443: */
 444: function sortDayToday($sour1, $sour2, &$dest) {
 445:     $cnt = 0;
 446: 
 447:     // できごと
 448:     if (isset($sour1['event'])) {
 449:         foreach ($sour1['event'as $item) {
 450:             $dest[$cnt]['year']  = $item['year'];
 451:             $dest[$cnt]['event'] = $item['event'];
 452:             $dest[$cnt]['era']   = $item['era'];
 453:             $cnt++;
 454:         }
 455:     }
 456:     // 誕生日
 457:     if (isset($sour1['birth'])) {
 458:         foreach ($sour1['birth'as $item) {
 459:             $event = "{$item['name']}({$item['profile']})誕生";
 460:             if ($item['plus'!'')    $event ."(~{$item['plus']}年)";
 461:             $dest[$cnt]['year']  = $item['year'];
 462:             $dest[$cnt]['event'] = $event;
 463:             $dest[$cnt]['era']   = $item['era'];
 464:             $cnt++;
 465:         }
 466:     }
 467:     // 命日
 468:     if (isset($sour1['death'])) {
 469:         foreach ($sour1['death'as $item) {
 470:             $event = "{$item['name']}({$item['profile']})死去";
 471:             if ($item['plus'!'')    $event ."({$item['plus']}年~{$item['year']}年)";
 472:             $dest[$cnt]['year']  = $item['year'];
 473:             $dest[$cnt]['event'] = $event;
 474:             $dest[$cnt]['era']   = $item['era'];
 475:             $cnt++;
 476:         }
 477:     }
 478:     // 記念日
 479:     foreach ($sour2 as $item) {
 480:         $dest[$cnt]['year']  = KINENBI_YEAR;
 481:         $dest[$cnt]['event'] = $item['title'];
 482:         $dest[$cnt]['era']   = '';
 483:         $dest[$cnt]['url']   = $item['url'];
 484:         $cnt++;
 485:     }
 486: 
 487:     // ソート
 488:     usort($dest, function ($a, $b) {
 489:         return (int)($a['year'- $b['year']);
 490:     });
 491: 
 492:     return $cnt;
 493: }

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

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

DayToday.php

 668: $month = getParam('month', FALSE, 0);
 669: if ($month == 0)    $month = date('n');
 670: $day   = getParam('day', FALSE, 0);
 671: if ($day == 0)  $day = date('j');
 672: $year  = getParam('year', FALSE, 0);
 673: if ($year == 0$year = date('Y');
 674: $diff  = getInteger('diff', -100, +100, 0);
 675: list($year, $montjh, $day) = diffymd($year, $month, $day, $diff);
 676: $era  = getParam('era', FALSE, 1);
 677: $era = ($era == 1? FALSE : TRUE;
 678: $lfn  = getParam('lfn', FALSE, 1);          // Ver.1.43
 679: $lfn = ($lfn == 1? FALSE : TRUE;          // Ver.1.43

DayToday.php

 186: /**
 187:  * 指定した年がうるう年かどうか判定する
 188:  * @param   int  $year  西暦年
 189:  * @return  bool TRUE:うるう年/FALSE:平年
 190: */
 191: function isLeapYear($year) {
 192:     $result = FALSE;
 193:     if ($year % 4 == 0)     $result = TRUE;
 194:     if ($year % 100 == 0)   $result = FALSE;
 195:     if ($year % 400 == 0)   $result = TRUE;
 196:     return $result;
 197: }

DayToday.php

 199: /**
 200:  * 指定した年月の日数を返す
 201:  * @param   int $year  年
 202:  * @param   int $month 月
 203:  * @return  int 日数/FALSE:引数の異常
 204: */
 205: function getDaysInMonth($year, $month) {
 206:     static $days = array(0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
 207:     if ($month < 1 || $month > 12)   return FALSE;
 208: 
 209:     $dm = (($month == 2&& isLeapYear($year)) ? 29 : $days[$month];
 210: 
 211:     return $dm;
 212: }

DayToday.php

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

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