目次
サンプル・プログラムの実行例
- 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 が通ったディレクトリに配置すること。 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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()の取得パターンを変更した |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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() 追加 |
| バージョン | 更新日 | 内容 |
|---|---|---|
| 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対応
Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dllLinuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順
これで準備は完了だ。
準備:pahooInputData 関数群
また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .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: }
そこで、頻繁に変更がないデータについては、一度取り込んだら、こちら側のサーバのローカルストレージにキャッシュしておく仕組みを用意した。それが 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) ===============================================================
後述するように 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-Agent と Accep--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: }
これらを正規表現 $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: }
組み込み関数 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: }
月の大小が異なることから getDaysInMonth を呼び出し、うるう年の判定は isleap を呼び出すようにしてある。
組み込み関数 strtotime を使えば簡便に書けるのだが、この関数は32ビット環境ではUNIXタイムスタンプで1901年2月13日から2038年1月19日までしか計算できないため、あえてユーザー関数を用意した。
なお、コマンドラインから "DayToday.php?diff=+2" のように指定することで $diff を直接指定できる。
また、メイン・プログラムで $diff = +1 のように書き換えれば、「明日は何の日」を求めるプログラムに変身する。
活用例
参考サイト
- PHPで記念日を表示する:ぱふぅ家のホームページ
- C++ で今日は何の日か調べる:ぱふぅ家のホームページ
- 一般社団法人 日本記念日協会

これをPHPでスクレイピングすることで、今日は何の日か表示するプログラムを作成する。
さらに、「PHPで記念日を表示する」で作ったプログラムを取り込み、その日の記念日も表示できるようにする。
(2026年4月10日)getDayToday() 「年」取得パターンを変更
(2025年9月11日)getDayToday() 「できごと」取得パターンを変更
(2025年9月6日)Wikipediaの仕様変更への対応=pahooCacheクラスを導入した.pahooCacheクラスにHTTPヘッダ送信機能を追加した.