PHPで宇宙カレンダーを作る

(1/1)
宇宙の誕生から今日までを1年間に見立てた「宇宙カレンダー」という考え方がある。1月1日午前0時にビッグバンが起き、9月1日に太陽系が形成、11月6日に真核生物が誕生した‥‥という形のものだ。
アメリカの天文学者で作家の故カール・セーガン氏が考案したとされ、パパぱふぅは学生時代、セーガン氏が進行担当したテレビ番組「コスモス」や、来日した際の講演などを聞き、当時は電卓で計算し、ノートに記入したものだ。
今回は、PHPを使い、あらかじめ用意した年表データを宇宙カレンダーに変換し、表示するプログラムを作る。なお、年表データを、たとえば日本史や家族史に置き換えれば、オリジナルの宇宙カレンダーを作ることができる。

(2026年1月3日)jQueryを使わない.PHP8.5対応:double→float

目次

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

宇宙カレンダー

サンプル・プログラム

圧縮ファイルの内容
cosmicCalendar.phpサンプル・プログラム本体。
cosmicCalendar.xmlイベント・ファイル
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
pahooCalendar.php暦計算クラス pahooCalendar。
暦計算クラスの使い方は「PHPで日出没・月出没・月齢・潮を計算」を参照。include_path が通ったディレクトリに配置すること。
cosmicCalendar.php 更新履歴
バージョン 更新日 内容
1.7.0 2025/12/06 jQueryを使わない.PHP8.5対応:double→float
1.6.0 2023/10/15 sigcalendar()のbug-fix, pahooInputData導入
1.5.0 2022/12/30 1月は時分まで表示するようにした
1.42 2022/07/21 bug-fix,イベント・ファイル更新
1.41 2021/05/12 bug-fix
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() 追加
pahooCalendar.php 更新履歴
バージョン 更新日 内容
4.6.1 2025/11/23 next_fullmoon: bug-fix
4.6.0 2025/10/08 getMidAutumnMoon() 追加
4.5.1 2025/05/31 deg2ddmm(), deg2hhmm() 不具合修正
4.5.0 2024/03/17 ヒジュラ暦メソッドを追加
4.4.1 2024/03/17 getCabinetOfficeHolidayTable() -- bug-fix

イベント・ファイル

イベント・ファイルの構造(xml) cosmicCalendar caption label キャプション description カレンダーの説明 start label 開始イベント before 現在から××年前:例 138E+8     description イベントの説明 event label イベント名 after 開始イベントからの年数:例 30E+4     description イベントの説明 event label イベント名 before 現在から××年前:例 135E+8     description イベントの説明 event label イベント名 dt 西暦表記:例 1945/8/15 00:00:00     description イベントの説明 event label イベント名 dt 紀元前表記:例 -1792/1/1 00:00:00     description イベントの説明
まず、上図のような構造のイベント・ファイル "cosmicCalendar.xml" を用意し、スクリプトと同じフォルダに配置する。
同梱したファイルには、宇宙の誕生から現在までの主要イベントを記入済みである。
このイベント・ファイルを編集すれば、日本史や家族史を使った宇宙カレンダー(365日カレンダーと呼んだ方がいいかもしれない)を表示することができる。

cosmicCalendar.xml

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <!-- 年代記 -->
   3: <cosmicCalendar>
   4: <!-- キャプション -->
   5: <caption>
   6:     <label>宇宙カレンダー</label>
   7:     <description>宇宙誕生から現在までを1年に圧縮</description>
   8: </caption>
   9: <!-- 開始 -->
  10: <start>
  11:     <label>宇宙誕生</label>
  12:     <before unit="year">138E+8</before>
  13:     <description></description>
  14: </start>
  15: <finish>
  16:     <label>現在</label>
  17:     <before unit="year">0</before>
  18:     <description></description>
  19: </finish>
  20: 
  21: <!-- 以下、イベント・データベース -->
  22: <event>
  23:     <label>宇宙の晴れ上がり</label>
  24: <!-- 開始イベントからの年数を記載するときは after -->
  25:     <after unit="year">30E+4</after>
  26:     <description></description>
  27: </event>
  28: <event>
  29:     <label>天の川銀河の誕生</label>
  30: <!-- 現在から××年前を記載するときは before -->
  31:     <before unit="year">110E+8</before>
  32:     <description></description>
  33: </event>
  34: <event>
  35:     <label>太平洋戦争終結</label>
  36: <!-- 西暦記載するときは dt  yyyy/mm/dd hh:mm:ss形式 -->
  37:     <dt>1945/8/15 00:00:00</dt>
  38:     <description></description>
  39: </event>
  40: <event>
  41:     <label>ハンムラビ王の即位</label>
  42: <!-- 紀元前はマイナス表記 -->
  43:     <dt>-1792/1/1 00:00:00</dt>
  44:     <description></description>
  45: </event>
  46: <event>
  47:     <label>最古のガンマ線バースト</label>
  48:     <after unit="year">7.3E+8</after>
  49:     <description>GRB 250314A -- 2025年3月14日に、フランスと中国の天文衛星「SVOM」がとらえた。</description>
  50: </event>

準備:外部クラスなど

cosmicCalendar.php

  57: // 表示幅(単位:ピクセル)
  58: define('WIDTH', 600);
  59: 
  60: // イベント・ファイル
  61: define('FILE_COSMIC_CALENDAR', 'cosmicCalendar.xml');
  62: 
  63: // 暦計算クラス
  64: require_once('pahooCalendar.php');

超長期のカレンダー計算が必要なことから、ユーザークラス "pahooCalendar" に用意したユリウス日計算メソッド Gregorian2JD を利用する。
そこで、クラスファイル "pahooCalendar.php" を  require_once  し、オブジェクトを生成する。

解説:イベント・ファイルを読み込み、ソートする

cosmicCalendar.php

 281: /**
 282:  * イベント・ファイルを読み込み、ソートする
 283:  * @param   string $fname イベント・ファイル名
 284:  * @param   array  $caldb イベントを格納する配列
 285:  * @param   array  $caption キャプションを格納する配列
 286:  * @return  bool TRUE/FALSE
 287: */
 288: function readDB($fname, &$caldb, &$caption) {
 289:     $pcl = new pahooCalendar();     // pahooCalendarクラス
 290:     $pcl->setLanguage('jp');
 291: 
 292:     $jd_end = $pcl->Gregorian2JD(date('Y'+ 1, 1, 1, 0, 0, 0); // 今年の最後
 293:     $dd = 365 + date('L');                                  // 1年の日数
 294:     $res = FALSE;
 295: 
 296:     // イベント・ファイル読み込み
 297:     if (file_exists($fname)) {
 298:         $xml = simplexml_load_file($fname);
 299:         // キャプション
 300:         if (isset($xml->caption->label)) {
 301:             $caption['label'] = isset($xml->caption->label? (string)$xml->caption->label : '宇宙カレンダー';
 302:             $caption['description'] = isset($xml->caption->description? (string)$xml->caption->description : '宇宙誕生から現在までを1年に圧縮';
 303:         }
 304:         // 開始
 305:         if (isset($xml->start->before)) {
 306:             $start = (float)$xml->start->before;
 307:             $unit = ($dd * 24 * 60 * 60) / $start;      // 1秒当たり年数
 308:             $cnt = 0;
 309:             $caldb[$cnt]['label'] = (string)$xml->start->label;
 310:             $ss = $start * $unit;
 311:             list($caldb[$cnt]['dt'], $caldb[$cnt]['fmt']) = sigcalendar($ss);
 312:             $cnt++;
 313:         }
 314:         // イベント読み込み
 315:         foreach ($xml->event as $event) {
 316:             $caldb[$cnt]['label'] = (string)$event->label;
 317:             if (isset($event->before)) {
 318:                 $ss = ($start - (float)$event->before* $unit;
 319:             } else if (isset($event->after)) {
 320:                 $ss = (float)$event->after * $unit;
 321:             } else if (isset($event->dt)) {
 322:                 sscanf((string)$event->dt, '%d/%d/%d %d:%d:%f', $year, $month, $day, $hour, $min, $sec);
 323:                 $jd = $pcl->Gregorian2JD($year, $month, $day, $hour, $min, $sec);
 324:                 $ss = ($start - ($jd_end - $jd) / $dd* $unit;
 325:             } else {
 326:                 $ss = 0;
 327:             }
 328:             // カレンダー計算
 329:             list($caldb[$cnt]['dt'], $caldb[$cnt]['fmt']) = sigcalendar($ss);
 330:             $cnt++;
 331:         }
 332:         $res = TRUE;
 333:     }
 334:     $pcl = NULL;
 335: 
 336:     // ソート
 337:     usort($caldb, function ($a, $b) {
 338:         if (! isset($a['dt']))  return NULL;
 339:         if (! isset($b['dt']))  return NULL;
 340:         return $a['dt'> $b['dt'? (+1: (-1);
 341:     });
 342: 
 343:     return $res;
 344: }

ユーザー関数 readDB では、イベント・ファイルを  simplexml_load_file  で読み込んだら、要素を処理しやすいように配列 $caldb に読み込み、最後に、 usort  を使って年月日の古い順に並び替える。

個々のイベント要素を読み込む際は、年数の表記が before, after, dt によって場合分けを行い、宇宙誕生(startに記述)を基準点として、そこから何秒の距離にあるかを変数 $ss に代入する。
イベントを、基準点と、その距離に置換することで、宇宙史だけでなく、日本史や家族史のような、スケールの異なるカレンダーも作ることができる。

解説:カレンダー計算

cosmicCalendar.php

 228: /**
 229:  * カレンダー計算
 230:  * @param   float $ss 秒数
 231:  * @return  array(カレンダー,フォーマット)
 232: */
 233: function sigcalendar($ss) {
 234:     $yyyy = date('Y');      // 今年の西暦年
 235:     $tt = (int)strtotime($yyyy .'/1/1 0:0:0'+ (int)$ss;
 236:     $dt = date('m/d H:i:s', $tt);
 237: 
 238:     // 01/01 00:00:01以前→ミリ秒まで
 239:     if (preg_match('/^01\/01 01:01:01/', $dt> 0) {
 240:         $fmt = 'm/d H:i:s';
 241:         $dt = date($fmt, $tt);
 242:         $dt .sprintf('.%03d', (($ss - floor($ss)) * 1000));
 243:     // 01/01 4時以前→秒まで
 244:     } else if (preg_match('/^12\/31 0[0-4]./', $dt> 0) {
 245:         $fmt = 'm/d H:i:s';
 246:         $dt = date($fmt, $tt);
 247:     // 01/01→分まで
 248:     } else if (preg_match('/^01\/01/', $dt> 0) {
 249:         $fmt = 'm/d H:i';
 250:         $dt = date($fmt, $tt);
 251:     // 1月→時まで
 252:     } else if (preg_match('/^01/', $dt> 0) {
 253:         $fmt = 'm/d H:00';
 254:         $dt = date($fmt, $tt);
 255:     // 12/31 23:59:59以降→ミリ秒まで
 256:     } else if (preg_match('/^12\/31 23:59:59/', $dt> 0) {
 257:         $fmt = 'm/d H:i:s';
 258:         $dt = date($fmt, $tt);
 259:         $dt .sprintf('.%03d', (($ss - floor($ss)) * 1000));
 260:     // 12/31 20時以降→秒まで
 261:     } else if (preg_match('/^12\/31 2/', $dt> 0) {
 262:         $fmt = 'm/d H:i:s';
 263:         $dt = date($fmt, $tt);
 264:     // 12/31→分まで
 265:     } else if (preg_match('/^12\/31/', $dt> 0) {
 266:         $fmt = 'm/d H:i';
 267:         $dt = date($fmt, $tt);
 268:     // 12月→時まで
 269:     } else if (preg_match('/^12/', $dt> 0) {
 270:         $fmt = 'm/d H:00';
 271:         $dt = date($fmt, $tt);
 272:     // それ以前→日まで
 273:     } else {
 274:         $fmt = 'm/d';
 275:         $dt = date($fmt, $tt);
 276:     }
 277: 
 278:     return array($dt, $fmt);
 279: }

宇宙カレンダーでは、数十億年前の出来事を扱うが、年代が古くなればなるほど誤差が大きいし、イベントの数も少ない。
そこで、一定の境界条件を設け、カレンダーの時分秒を表示しなかったり、逆にミリ秒まで表示するようにした。
これを行うのがユーザー関数 sigcalendar である。

解説:データベースに表示用フラグを立てる

cosmicCalendar.php

 346: /**
 347:  * データベースに表示用フラグを立てる:全部
 348:  * @param   array  $caldb データベース
 349:  * @param   int    $ti    マークしたい時刻(省略時は現在時刻)
 350:  * @return  なし
 351: */
 352: function checkDB_all(&$caldb, $ti=0) {
 353:     if ($ti == 0)   $ti = time();   // 省略時
 354: 
 355:     $key = 0;
 356:     $flag = (-1);
 357:     foreach ($caldb as $rec) {
 358:         if (isset($rec['dt'])) {
 359:             $t0 = date($rec['fmt'], $ti);
 360:             if ($rec['dt'< $t0) {
 361:                 $caldb[$key]['flag'] = $flag;
 362:             } else if ($flag == (-1)) {
 363:                 $flag++;
 364:                 $caldb[$key]['flag'] = $flag;
 365:                 $flag++;
 366:             } else {
 367:                 $caldb[$key]['flag'] = $flag;;
 368:             }
 369:             $key++;
 370:         }
 371:     }
 372: }

cosmicCalendar.php

 374: /**
 375:  * データベースに表示用フラグを立てる:同じ月
 376:  * @param   array  $caldb データベース
 377:  * @param   int    $ti    マークしたい時刻(省略時は現在時刻)
 378:  * @return  なし
 379: */
 380: function checkDB_month(&$caldb, $ti=0) {
 381:     if ($ti == 0)   $ti = time();   // 省略時
 382:     $t1 = date('m', $ti);
 383: 
 384:     $flag = (-2);
 385:     $key = 0;
 386:     foreach ($caldb as $rec) {
 387:         if (isset($rec['dt'])) {
 388:             $t0 = date($rec['fmt'], $ti);
 389:             if ($rec['dt'< $t0) {
 390:                 $caldb[$key]['flag'] = $flag;
 391:             } else if ($flag == (-2)) {
 392:                 $caldb[$key]['flag'] = 0;
 393:                 $flag = (+2);
 394:             } else {
 395:                 $caldb[$key]['flag'] = $flag;
 396:             }
 397:             if (preg_match('/(^\d{2})/iu', $rec['dt'], $arr> 0) {
 398:                 if (($arr[1] == $t1&& ($caldb[$key]['flag']) !0) {
 399:                     $caldb[$key]['flag'] = (-1);
 400:                 }
 401:             }
 402:             $key++;
 403:         }
 404:     }
 405: }

cosmicCalendar.php

 407: /**
 408:  * データベースに表示用フラグを立てる:「今ここ」と前後2件ずつ
 409:  * @param   array  $caldb データベース
 410:  * @param   int    $ti    マークしたい時刻(省略時は現在時刻)
 411:  * @return  なし
 412: */
 413: function checkDB_now(&$caldb, $ti=0) {
 414:     if ($ti == 0)   $ti = time();   // 省略時
 415: 
 416:     $flag = (-2);
 417:     $key = 0;
 418:     foreach ($caldb as $rec) {
 419:         if (isset($rec['dt'])) {
 420:             $t0 = date($rec['fmt'], $ti);
 421:             if ($rec['dt'< $t0) {
 422:                 $caldb[$key]['flag'] = $flag;
 423:                 $key++;
 424:             } else if ($flag == (-2)) {
 425:                 $flag++;
 426:                 for ($i = $key - 2$i < $key$i++) {
 427:                     if (isset($caldb[$i]['label'])) $caldb[$i]['flag'] = $flag;
 428:                 }
 429:                 $flag++;
 430:                 $i = $key;
 431:                 if (isset($caldb[$i]['label'])) $caldb[$i]['flag'] = $flag;
 432:                 $flag++;
 433:                 $key++;
 434:                 $caldb[$key]['flag'] = $flag;
 435:                 $key++;
 436:                 if (isset($caldb[$key]['label']))   $caldb[$key]['flag'] = $flag;
 437:                 $key++;
 438:                 $flag++;
 439:             } else {
 440:                 $caldb[$key]['flag'] = $flag;
 441:                 $key++;
 442:             }
 443:         }
 444:     }
 445: }

宇宙カレンダーを全て表示すると大きな表になってしまうので、指定した基準日(デフォルトでは現在日時)を含む月のイベントだけ表示するモード、基準日の前後2件ずつ、合計5件のイベントだけ表示するモードを加えた。
こららを処理するのが、ユーザー関数 checkDB_all, checkDB_month, checkDB_now である。

解説:宇宙カレンダーを作成

cosmicCalendar.php

 465: /**
 466:  * 宇宙カレンダーを作成:テーブル形式
 467:  * @param   array  $caldb データベース
 468:  * @param   array  $caption キャプション
 469:  * @return  string 表示用コンテンツ
 470: */
 471: function makeCosmicCalendar_table($caldb, $caption) {
 472:     $outstr =<<< EOT
 473: <table class="stripe">
 474: <caption>{$caption['label']}</caption>
 475: <tr><th>日時</th><th>イベント</th></tr>
 476: 
 477: EOT;
 478: 
 479:     foreach ($caldb as $rec) {
 480:         if ($rec['flag'>= (-1&& $rec['flag'<= (+1)) {
 481:             $mark = ($rec['flag'] == 0? '&nbsp;&#x23F1;' : '';
 482:             $outstr .=<<< EOT
 483: <tr>
 484: <td>{$rec['dt']}$mark</td>
 485: <td>{$rec['label']}</td>
 486: </tr>
 487: 
 488: EOT;
 489:         }
 490:     }
 491:     $outstr .=<<< EOT
 492: </table>
 493: 
 494: EOT;
 495:     return $outstr;
 496: }

cosmicCalendar.php

 447: /**
 448:  * 宇宙カレンダーを作成:テキスト形式
 449:  * @param   array  $caldb   データベース
 450:  * @param   array  $caption キャプション
 451:  * @return  string 表示用コンテンツ
 452: */
 453: function makeCosmicCalendar_text($caldb, $caption) {
 454:     $outstr = "<hr>■{$caption['label']}({$caption['description']})<br>\n";
 455:     foreach ($caldb as $rec) {
 456:         if ($rec['flag'>= (-1&& $rec['flag'<= (+1)) {
 457:             $outstr .$rec['dt'. ' - ' . $rec['label'];
 458:             if ($rec['flag'] == 0)  $outstr .'←いまココ';
 459:             $outstr ."<br>\n";
 460:         }
 461:     }
 462:     return $outstr;
 463: }

最後に、宇宙カレンダーを作成する書式だが、HTMLの表形式で出力するユーザー関数 makeCosmicCalendar_table と、SNSへの投稿を想定してテキスト形式で出力するユーザー関数 makeCosmicCalendar_text の2種類を用意した。

参考サイト

(この項おわり)
header