PHPで過去の元号を分析する

(1/1)
2019年(平成31年)4月1日、新しい元号が発表される。
巷間では新元号の予想が盛んだが、ちょうど「PHPでテキスト中の和暦・西暦年号を統一する(その2)」で紹介したクラス pahooNormalizeText の中に西暦⇔元号変換表があるので、これを利用し、PHPプログラムで過去の元号に関する分析を行ってみることにする。

南北朝時代については、南朝の元号を採用している。クラス pahooNormalizeText には北朝の元号がコメントアウトしてあるので、必要に応じて、南朝と切り替えてみてほしい。

(2021年1月29日)現在元号(令和)の完了日を当日までに変更
(2021年1月16日)PHP8対応

目次

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

PHPで過去の元号を分析する
URLパラメータなしで、上図のような結果を表示する。

ホームページに埋め込む場合に利用できるように、URLパラメータとして mode を指定することができる。gengo.php?mode=1 のようにして実行すると、概要表のみのHTML文を出力する。mode の値については、ソース・プログラム冒頭のコメントを参照されたい。
なお、TABLEタグに対してスタイルシートを指定しており、これはホームページ側で適宜設定してほしい。

サンプル・プログラム

圧縮ファイルの内容
gengo.phpサンプル・プログラム本体
pahooNormalizeText.phpテキスト正規化クラス pahooNormalizeText。
テキスト正規化クラスの使い方は「PHPで日本語テキストを正規化」を参照。include_path が通ったディレクトリに配置すること。
pahooCalendar.php暦計算クラス pahooCalendar。
暦計算クラスの使い方は「PHPで日出没・月出没・月齢・潮を計算」を参照。include_path が通ったディレクトリに配置すること。

解説:過去の元号を分析しやすいように配列に格納する

 196: /**
 197:  * 過去の元号を計算しやすいように配列に格納する
 198:  * @param   array $eras 元号を格納する配列
 199:  * @return  int 格納した元号の数
 200: */
 201: function getEras(&$eras) {
 202:     //オブジェクト生成
 203:     $pnt = new pahooNormalizeText();
 204:     $pc  = new pahooCalendar();
 205: 
 206:     $table = array($pnt->TABLE_AD_ERA2, $pnt->TABLE_AD_ERA1);
 207:     $cnt = 0;
 208:     foreach ($table as $tbl) {
 209:         foreach ($tbl as $key=>$val) {
 210:             $eras[$cnt]['name'] = $val;
 211:             if ($key == '99999999')     $key = date('Ymd');     //現在元号
 212:             //開始年月日
 213:             preg_match('/([0-9]{4})([0-9]{2})([0-9]{2})/', $key, $arr);
 214:             $eras[$cnt]['start_jd']    = $pc->AD2JD($arr[1], $arr[2], $arr[3], 0, 0, 0);    //ユリウス日
 215:             list($year, $month, $day, $hour, $min, $sec) = $pc->JD2Gregorian($eras[$cnt]['start_jd']);      //ユリウス日
 216:             $eras[$cnt]['start_year']  = (int)$year;
 217:             $eras[$cnt]['start_month'] = (int)$month;
 218:             $eras[$cnt]['start_day']   = (int)$day;
 219:             //終了年月日
 220:             if ($cnt > 0) {
 221:                 $eras[$cnt - 1]['finish_jd'] = $eras[$cnt]['start_jd'- 1;
 222:                 $eras[$cnt - 1]['interval'] = $eras[$cnt - 1]['finish_jd'- $eras[$cnt - 1]['start_jd'+ 1;
 223:                 list($year, $month, $day, $hour, $min, $sec) = $pc->JD2Gregorian($eras[$cnt - 1]['finish_jd']);     //ユリウス日
 224:                 $eras[$cnt - 1]['finish_year']  = (int)$year;
 225:                 $eras[$cnt - 1]['finish_month'] = (int)$month;
 226:                 $eras[$cnt - 1]['finish_day']   = (int)$day;
 227:             }
 228:             $cnt++;
 229:         }
 230:     }
 231:     //オブジェクト解放
 232:     $pnt = NULL;
 233:     $pc  = NULL;
 234: 
 235:     return $cnt;
 236: }

ユーザー関数 getEras は、クラス pahooNormalizeText の中に西暦⇔元号変換表を、分析しやすいような形に変換して配列に格納する。
配列の構造を以下に記す。2次元配列で、2次元目が連想配列になっている。
配列の内容
1次元 2次元 内  容
番号 name 元号
start_year 開始年(西暦)
start_month 開始月
start_day 開始日
start_jd 開始ユリウス日
finish_year 終了年(西暦)
finish_month 終了月
finish_day 終了日
finish_jd 終了ユリウス日
interval 期間(日)
ユリウス日は「PHPで祝日を求める」で紹介したが、紀元前4713年1月1日からの通日で、期間計算をするのに都合がいいことから加えた。

解説:有効な元号の数を数える

 238: /**
 239:  * 有効な元号の数を数える
 240:  * @param   array $eras  元号配列
 241:  * @return  int 有効な元号の数
 242: */
 243: function countEras($eras) {
 244:     $cnt = 0;
 245:     foreach ($eras as $era) {
 246:         if (mb_strlen($era['name']) > 0)    $cnt++;
 247:     }
 248:     return $cnt;
 249: }

飛鳥時代、元号が制定されなかった期間があり、西暦⇔元号変換表は空文字にしてある。また、新元号も空文字にしてある。
ユーザー関数 countEras は、これらを読み飛ばし、有効な元号の数を数える。

解説:元号に使われる漢字1文字を分析

 251: /**
 252:  * 元号に使われる漢字1文字を分析
 253:  * @param   array $eras  元号配列
 254:  * @param   array $items 結果を格納する配列
 255:  * @return  int 格納した漢字の数
 256: */
 257: function analyzeKanji($eras, &$items) {
 258:     $cnt = 0;
 259:     foreach ($eras as $era) {
 260:         if (mb_strlen($era['name']) == 0)   continue;
 261:         //マルチバイト文字を1文字1文字に分解し配列に格納する
 262:         $arr = preg_split('//u', $era['name'], -1, PREG_SPLIT_NO_EMPTY);
 263:         foreach ($arr as $kanji) {
 264:             if (isset($items[$kanji])) {
 265:                 $items[$kanji]['count']++;      //出現回数
 266:                 $items[$kanji]['interval'+$era['interval']; //使用期間
 267:             } else {
 268:                 $items[$kanji]['count'] = 1;
 269:                 $items[$kanji]['interval'] = $era['interval'];
 270:                 $cnt++;
 271:             }
 272:         }
 273:     }
 274:     return $cnt;
 275: }

過去の元号は、古典から文言を採用しているが、中には文言をバラバラにして漢字を取り出して組み合わせている例もある。そこで、ユーザー関数 getEras で取り出した元号を、漢字1文字1文字に分解し、それぞれの漢字の出現回数と、その漢字を使った元号の期間合計を算出するユーザー関数 analyzeKanji を用意した。

組み込み関数  preg_split  を使って、マルチバイト文字を1文字1文字に分解する処理が肝である。

解説:分析結果を表示テキストにする:概要

 277: /**
 278:  * 分析結果を表示テキストにする:概要
 279:  * @param   array $eras  元号配列
 280:  * @param   array $items 漢字の分析結果
 281:  * @return  string 表示テキスト(HTML)
 282: */
 283: function resultSummary($eras, $items) {
 284:     $n = countEras($eras);
 285:     $k = count($items);
 286: 
 287:     $html =<<< EOT
 288: <table class="plists">
 289: <tr>
 290: <td>元号の数</td>
 291: <td style="text-align:right">{$n}</td>
 292: </tr>
 293: <tr>
 294: <td>使われている漢字の数</td>
 295: <td style="text-align:right">{$k}</td>
 296: </tr>
 297: </table>
 298: 
 299: EOT;
 300:     return $html;
 301: }

解説:分析結果を表示テキストにする:期間の長い元号

 303: /**
 304:  * 分析結果を表示テキストにする:期間の長い元号
 305:  * @param   array $eras  元号配列
 306:  * @param   array $items 漢字の分析結果
 307:  * @return  string 表示テキスト(HTML)
 308: */
 309: function resultIntervalLong($eras, $items) {
 310:     $ranking = RANKING;
 311: 
 312:     //並べ替え
 313:     uasort($eras, function($a, $b) {
 314:         if (isset($a['interval']) && isset($b['interval'])) {
 315:             $res = (int)($a['interval'< $b['interval']);
 316:         } else {
 317:             $res = 0;
 318:         }
 319:         return $res;
 320:     });
 321:     $html =<<< EOT
 322: <table class="plists">
 323: <caption>期間の長い元号(トップ{$ranking})</caption>
 324: <tr class="index">
 325: <th>順位</th>
 326: <th>元号</th>
 327: <th colspan="2">期間</th>
 328: </tr>
 329: 
 330: EOT;
 331:     $i = 0;
 332:     $j = 1;
 333:     $order = $i;
 334:     $old = 0;
 335:     foreach ($eras as $era) {
 336:         if (mb_strlen($era['name']) == 0)   continue;
 337:         if ($old !$era['interval']) {
 338:             $i +$j;
 339:             $j = 1;
 340:             $order = $i;
 341:             if ($i > $ranking)  break;
 342:         } else {
 343:             $j++;
 344:             $order = '';
 345:         }
 346:         $str = sprintf('%d/%d/%d~%d/%d/%d', $era['start_year'], $era['start_month'], $era['start_day'], $era['finish_year'], $era['finish_month'], $era['finish_day']);
 347:         $y = sprintf('%.1f年', $era['interval'] / 365.25);
 348:         $html .=<<< EOT
 349: <tr>
 350: <td class="order">{$order}</td>
 351: <td class="kanji">{$era['name']}</td>
 352: <td class="period">{$str}</td>
 353: <td class="num">{$y}</td>
 354: </tr>
 355: 
 356: EOT;
 357:         $old = $era['interval'];
 358:     }
 359:     $html .=<<< EOT
 360: </table>
 361: 
 362: EOT;
 363:     return $html;
 364: }

ユーザー関数 resultIntervalLong は、期間の長い元号トップ10を表形式のHTML文として作成する。

まず、組み込み関数  uasort  を使って、配列を interval の大きい順に並び替える。
同数順位があり得ることから、順位に表示する値の計算を工夫した。

解説:分析結果を表示テキストにする:期間の短い元号

 366: /**
 367:  * 分析結果を表示テキストにする:期間の短い元号
 368:  * @param   array $eras  元号配列
 369:  * @param   array $items 漢字の分析結果
 370:  * @return  string 表示テキスト(HTML)
 371: */
 372: function resultIntervalShort($eras, $items) {
 373:     $ranking = RANKING;
 374: 
 375:     //並べ替え
 376:     uasort($eras, function($a, $b) {
 377:         if (isset($a['interval']) && isset($b['interval'])) {
 378:             $res = (int)($a['interval'> $b['interval']);
 379:         } else {
 380:             $res = 0;
 381:         }
 382:         return $res;
 383:     });
 384:     $html =<<< EOT
 385: <table class="plists">
 386: <caption>期間の短い元号(トップ{$ranking})</caption>
 387: <tr class="index">
 388: <th>順位</th>
 389: <th>元号</th>
 390: <th colspan="2">期間(日)</th>
 391: </tr>
 392: 
 393: EOT;
 394:     $i = 0;
 395:     $j = 1;
 396:     $order = $i;
 397:     $old = 0;
 398:     foreach ($eras as $era) {
 399:         if (mb_strlen($era['name']) == 0)   continue;
 400:         if ($old !$era['interval']) {
 401:             $i +$j;
 402:             $j = 1;
 403:             $order = $i;
 404:             if ($i > $ranking)  break;
 405:         } else {
 406:             $j++;
 407:             $order = '';
 408:         }
 409:         $str = sprintf('%d/%d/%d~%d/%d/%d', $era['start_year'], $era['start_month'], $era['start_day'], $era['finish_year'], $era['finish_month'], $era['finish_day']);
 410:         $y = sprintf('%d日', $era['interval']);
 411:         $html .=<<< EOT
 412: <tr>
 413: <td class="order">{$order}</td>
 414: <td class="kanji">{$era['name']}</td>
 415: <td class="period">{$str}</td>
 416: <td class="num">{$y}</td>
 417: </tr>
 418: 
 419: EOT;
 420:         $old = $era['interval'];
 421:     }
 422:     $html .=<<< EOT
 423: </table>
 424: 
 425: EOT;
 426:     return $html;
 427: }

ユーザー関数 resultIntervalShort は、期間の短い元号トップ10を表形式のHTML文として作成する。やっていることはユーザー関数 resultIntervalLong とほぼ同じで、冒頭の  uasort  の比較関数を逆転させた。

解説:分析結果を表示テキストにする:出現頻度の多い漢字

 429: /**
 430:  * 分析結果を表示テキストにする:出現頻度の多い漢字
 431:  * @param   array $eras  元号配列
 432:  * @param   array $items 漢字の分析結果
 433:  * @return  string 表示テキスト(HTML)
 434: */
 435: function resultKanjiFrequency($eras, $items) {
 436:     $ranking = RANKING;
 437: 
 438:     //並べ替え
 439:     uasort($items, function($a, $b) {
 440:         return (int)($a['count'< $b['count']);
 441:     });
 442:     $html =<<< EOT
 443: <table class="plists">
 444: <caption>出現頻度が多い漢字(トップ{$ranking})</caption>
 445: <tr class="index">
 446: <th>順位</th>
 447: <th>漢字</th>
 448: <th>出現回数</th>
 449: </tr>
 450: 
 451: EOT;
 452:     $i = 0;
 453:     $j = 1;
 454:     $order = $i;
 455:     $old = 0;
 456:     foreach ($items as $kanji=>$item) {
 457:         if ($old !$item['count']) {
 458:             $i +$j;
 459:             $j = 1;
 460:             $order = $i;
 461:             if ($i > $ranking)  break;
 462:         } else {
 463:             $j++;
 464:             $order = '';
 465:         }
 466:         $html .=<<< EOT
 467: <tr>
 468: <td class="order">{$order}</td>
 469: <td class="kanji">{$kanji}</td>
 470: <td class="num">{$item['count']}</td>
 471: </tr>
 472: 
 473: EOT;
 474:         $old = $item['count'];
 475:     }
 476:     $html .=<<< EOT
 477: </table>
 478: 
 479: EOT;
 480:     return $html;
 481: }

ユーザー関数 resultKanjiFrequency は、元号として出現頻度の高い漢字トップ10を表形式のHTML文として作成する。やっていることはユーザー関数 resultIntervalLong とほぼ同じで、冒頭の  uasort  の比較関数で比較する項目が異なる。

解説:分析結果を表示テキストにする:一度だけ使われた漢字

 483: /**
 484:  * 分析結果を表示テキストにする:一度だけ使われた漢字
 485:  * @param   array $eras  元号配列
 486:  * @param   array $items 漢字の分析結果
 487:  * @return  string 表示テキスト(HTML)
 488: */
 489: function resultKanjiOnce($eras, $items) {
 490:     $cols = 10;     //表示列数
 491: 
 492:     $html =<<< EOT
 493: <table class="plists">
 494: <caption>一度だけ使われた漢字</caption>
 495: 
 496: EOT;
 497:     $i = 0;
 498:     foreach ($items as $kanji=>$item) {
 499:         if ($item['count'] == 1) {
 500:             if ($i == 0)    $html ."<tr>\n";
 501:             $html ."<td class=\"kanji\">{$kanji}</td>\n";
 502:             $i++;
 503:             if ($i >$cols) {
 504:                 $html ."</tr>\n";
 505:                 $i = 0;
 506:             }
 507:         }
 508:     }
 509:     while ($i < $cols) {
 510:         $html ."<td class=\"kanji\">&nbsp;</td>\n";
 511:         $i++;
 512:         if ($i >$cols)    $html ."</tr>\n";
 513:     }
 514: 
 515:     $html .=<<< EOT
 516: </table>
 517: 
 518: EOT;
 519:     return $html;
 520: }

ユーザー関数 resultKanjiOnce は、元号として一度だけ使われた漢字を表形式のHTML文として作成する。連想配列の count が1であるものを抽出する。

解説:分析結果を表示テキストにする:使用期間の長い漢字

 522: /**
 523:  * 分析結果を表示テキストにする:使用期間の長い漢字
 524:  * @param   array $eras  元号配列
 525:  * @param   array $items 漢字の分析結果
 526:  * @return  string 表示テキスト(HTML)
 527: */
 528: function resultKanjiInterval($eras, $items) {
 529:     $ranking = RANKING;
 530: 
 531:     //並べ替え
 532:     uasort($items, function($a, $b) {
 533:         return (int)($a['interval'< $b['interval']);
 534:     });
 535:     $html =<<< EOT
 536: <table class="plists">
 537: <caption>使用期間が長い漢字(トップ{$ranking})</caption>
 538: <tr class="index">
 539: <th>順位</th>
 540: <th>漢字</th>
 541: <th>のべ使用期間</th>
 542: </tr>
 543: 
 544: EOT;
 545:     $i = 0;
 546:     $j = 1;
 547:     $order = $i;
 548:     $old = 0;
 549:     foreach ($items as $kanji=>$item) {
 550:         if ($old !$item['interval']) {
 551:             $i +$j;
 552:             $j = 1;
 553:             $order = $i;
 554:             if ($i > $ranking)  break;
 555:         } else {
 556:             $j++;
 557:             $order = '';
 558:         }
 559:         $y = sprintf('%.1f年', $item['interval'] / 365.25);
 560:         $html .=<<< EOT
 561: <tr>
 562: <td class="order">{$order}</td>
 563: <td class="kanji">{$kanji}</td>
 564: <td class="num">{$y}</td>
 565: </tr>
 566: 
 567: EOT;
 568:         $old = $item['interval'];
 569:     }
 570:     $html .=<<< EOT
 571: </table>
 572: 
 573: EOT;
 574:     return $html;
 575: }

ユーザー関数 resultKanjiInterval は、元号として使用期間の長い漢字トップ10を表形式のHTML文として作成する。やっていることはユーザー関数 resultIntervalLong とほぼ同じで、冒頭の  uasort  の比較関数で比較する項目が異なる。

解説:元号を検索

 577: /**
 578:  * 元号を検索
 579:  * @param   string $query 検索キー(正規表現可能)
 580:  * @param   array  $eras  元号配列
 581:  * @return  string 表示テキスト(HTML)
 582: */
 583: function searchKanji($query, $eras) {
 584:     //検索
 585:     $arr = array();
 586:     $cnt = 0;
 587:     foreach ($eras as $key=>$era) {
 588:         if (preg_match('/' . $query . '/u', $era['name']) > 0) {
 589:             $arr[$cnt] = $key;
 590:             $cnt++;
 591:         }
 592:     }
 593: 
 594:     //検索結果=ゼロ
 595:     if ($cnt == 0) {
 596:         $html = '<p>合致する元号はない.</p>';
 597: 
 598:     //検索結果
 599:     } else {
 600:         $html =<<< EOT
 601: <table class="plists">
 602: <caption>合致する元号</caption>
 603: <tr class="index">
 604: <th>番号</th>
 605: <th>元号</th>
 606: <th colspan="2">期間(日)</th>
 607: </tr>
 608: 
 609: EOT;
 610:         foreach ($arr as $key=>$val) {
 611:             $k = $key + 1;
 612:             $name = preg_replace('/(' . $query . ')/u', '<span class="match">$1</span>', $eras[$val]['name']);
 613:             $str = sprintf('%d/%d/%d~%d/%d/%d', $eras[$val]['start_year'], $eras[$val]['start_month'], $eras[$val]['start_day'], $eras[$val]['finish_year'], $eras[$val]['finish_month'], $eras[$val]['finish_day']);
 614:             $html .=<<< EOT
 615: <tr>
 616: <td class="order">{$k}</td>
 617: <td class="kanji">{$name}</td>
 618: <td class="period">{$str}</td>
 619: </tr>
 620: 
 621: EOT;
 622:         }
 623:         $html .=<<< EOT
 624: </table>
 625: 
 626: EOT;
 627:     }
 628:     return $html;
 629: }

ユーザー関数 searchKanji は、検索キーにヒットする元号を表形式のHTML文として作成する。
検索キーには正規表現が利用可能で、組み込み関数  preg_match  を使って検索する。

参考サイト

参考書籍

表紙 元号って何だ?
著者 藤井 青銅
出版社 小学館
サイズ 新書
発売日 2019年02月01日頃
価格 880円(税込)
ISBN 9784098253395
元号に関する素朴な疑問に答える入門書。そしていまいちばん楽しめる元号本。多くの日本人は平成⇒昭和⇒大正⇒明治まで遡ることができても明治の一つ前の元号を言うことができない。だから元号というと難しく感じる人もいるが、じつはとても人間くさくて面白いものなのだ。本書では全部で247ある元号をいろいろなランキングを使って解説。またさまざまな元号由来のネーミングや全国の改元ゆかりの地などを紹介する。まったく新しい元号読み物!
 
(この項おわり)
header