日付と時刻

2038年問題、タイムゾーン、サマータイム
ウーラニアー世界時計
ウーラニアー世界時計
データ属性としての日付・時刻は単純な数値ではなく、2038年問題という制約や、タイムゾーン、サマータイムを意識する必要がある。

西暦年月日時分秒、時差、サマータイムの全てを表す標準書式として、ISO 8601がある。PythonやPHPには、ISO 8601を扱う仕組みが用意されているが、2038年問題の制約を受けることから、±100万年を扱えるPHP用のクラスを作成する。

また、PHPによる西暦と元号の相互変換クラスは「PHPでテキスト中の和暦・西暦年号を統一する(その2)」を、旧暦計算クラスは「PHPで3ヶ月カレンダーを作る」を参考にしてほしい。

サンプル・プログラムのダウンロード

圧縮ファイルの内容
pahooDataStructure.phpデータ構造に関わるPHPのクラス。include_pathが通ったディレクトリに配置すること。
dt02-21-01.pyPython:ISO 8601日付・時刻の計算
dt02-21-02.phpPHP:ISO 8601日付・時刻の計算
dt02-21-03.phpPHP:生存日数、死亡予定日を計算
dt02-21-04.phpPHP:現地到着日時を計算
dt02-21-05.phpPHP:現地到着日時を計算(サマータイムを含む)

日付を表す文字列

「2018年6月29日」「2018/06/29」「平成30年6月29日」は、すべて同じ日付を表す。日付の表記方式は数値のように統一されていないので、identity としての日付を格納したいだけであれば、データ属性を文字列としておけばいいだろう。

しかし、日付には「2018年6月29日から10日は何月何日?」というように「quantitation として扱う場合がある。この場合は、計算可能なデータ属性にしておかなければならない。時刻のデータ属性を含めて後述する。

時刻を表す文字列

「午後10時35分」「24時25分」「24:25」が、すべて同じ時刻を表す。
時刻も日付と同様、表記方式が統一されておらず、identity としての時刻を格納したいだけであれば、データ属性を文字列としておけばいいだろう。
しかし、日付にも「24時25分から5時間後は何時何分」というように「quantitation として扱う場合がある。この場合は、計算可能なデータ属性にしておかなければならない。

さらに時刻の計算を行う場合、わが国にいる限りは意識しなくて済むが、時差」やサマータイムも意識しなくてはいけない。

quantitation としての日付・時刻

日付や時刻を計算可能なデータ属性にするには、まず、表記方式を統一する必要がある。
日付・時刻を表記する国際規格として ISO 8601 がある。これを使った表記例を下表に示す。最近は、インターネットでよく見かける表記方式で、ここでも ISO 8601 を利用することにする。

時刻の後ろに Z を添えることで UTC(協定世界時)を、また +/- でUTCからの時差を表現することで各地のローカル時刻を表現することができる。
コンピュータ内部では文字列として保持すれば十分だが、データ属性が日付時刻であることを意識する必要がある。
ISO 8601 表記
ISO 8601 表記 日本語表記
2018 西暦2018年
2018-07 西暦2018年7月
2018-07-12 西暦2006年7月12日
2018-07-12T23:20Z 西暦2006年7月12日23時20分UTC
2018-07-21T23:20:52Z 西暦2006年7月21日23時20分52秒UTC
2018-07-21T23:20:50.32Z 西暦2006年7月21日23時20分50.32秒UTC
2018-07-19T16:10:40.22-08:00 西暦2006年7月19日16時10分40.22秒
(米太平洋標準時間;UTCから-8時間の時差)
2018-07-19T16:10:40.22+09:00 西暦2006年7月19日16時10分40.22秒
(日本標準時間;UTCから+9時間の時差)
2006-01-01T08:59:60+09:00 西暦2006年1月1日8時59分60秒
(日本標準時間;閏秒を示す)

Pythonによる日付・時刻計算

PythonにはISO8601パッケージが用意されている。
導入は、コマンドラインから
pip install iso8601
とすればいい。

   9: from datetime import *
  10: import sys, iso8601
  11: 
  12: sys.stdout.write("\n")
  13: 
  14: # 2018年6月29日の7日後
  15: str = '2018-06-29'
  16: dd  = 7
  17: if (dd > 0):
  18:     sg = '+'
  19: else:
  20:     sg = '-'
  21: dt = iso8601.parse_date(str)
  22: dt = dt + timedelta(days=1* dd
  23: sys.stdout.write("{} ".format(str))
  24: sys.stdout.write(sg)
  25: sys.stdout.write(" {} days = ".format(abs(dd), '%d'))
  26: sys.stdout.write("{}\n".format(dt.strftime('%Y/%m/%d')))
  27: 
  28: # 2018年6月29日8:50の9時間前
  29: str = '2018-06-29T08:50'
  30: dd  = -9
  31: if (dd > 0):
  32:     sg = '+'
  33: else:
  34:     sg = '-'
  35: dt = iso8601.parse_date(str)
  36: dt = dt + timedelta(hours=1* dd
  37: sys.stdout.write("{} ".format(str))
  38: sys.stdout.write(sg)
  39: sys.stdout.write(" {} hours = ".format(abs(dd), '%d'))
  40: sys.stdout.write("{}\n".format(dt.strftime('%Y-%m-%d %H:%M')))
  41: 
  42: sys.stdout.write("\n")

PythonでISO 8601日付・時刻の計算
サンプル・プログラム "dt02-21-01.py" の実行結果は上図の通りである。
ISO8601パッケージに用意されているメソッド iso8601.parse_date により、ISO 8601形式の日付時刻文字列を datetimeオブジェクトに変換する。

日付計算の制約

Pythonの datetimeオブジェクトが通用するのは、1970年から2038年のまでの範囲である。他の多くの処理系でも、日付計算できる範囲は同様の制約を受ける。
これは、日付を内部的に秒数で保持しており、32ビット整数で表現できる年数が±68年という制約による。
また、UNIX処理系では1970年1月1日0時0分0秒からの累積秒数(UNIX epoch)を保持しており、今日でも多くの処理系がこれに倣っている。
これらの制約を2038年問題と呼ぶ

PHPによる日時クラスの作成

PHPの組み込み関数  strtotime  は ISO 8601 を解釈し、UNIX epoch に変換できる。しかし、Phthonと同様、1970年から2038年のまでの範囲の制約を受ける。

そこで、少なくとも±100万年の日時計算ができるユーザー・クラス pahooDateTime を作成した。次のメソッドを備えている。
メソッド機能
datetime()年月日時分秒をセットする。
delta()日時の差分を計算する。
sub()$dtを減算した結果を返す。
getTimezone()タイムゾーン識別子によりタイムゾーンのオフセット値を取得する。
tzone()タイムゾーンを変更する。
parse_date()ISO 8601 日時フォーマットの解釈。
date()日時フォーマットで文字列に変換する。
isleap()閏年かどうか判定する。
getDaysInMonth()指定した月の日数を返す。
verify()年月日時分秒の各値が正しいかどうか検査する。

 

先ほどのPythonプログラムをPHPに移植すると、"dt02-21-02.php" のようになる。

  11: //データ構造クラス
  12: require_once('pahooDataStructure.php');
  13: $dt = new pahooDateTime();                  //pahooDateTimeオブジェクト用意
  14: 
  15: printf("\n");
  16: 
  17: // 2018年6月29日の7日後
  18: $str = '2019-06-29';
  19: $dd  = 7;
  20: $sg = '+';
  21: $dt->parse_date($str);
  22: printf("%s", $dt->date('Y-m-d'));
  23: printf(" %s %d days = ", $sg, $dd);
  24: $dt->delta(($sg == '+' ? (+1: (-1)), 0, 0, $dd);
  25: printf("%s\n", $dt->date('Y-m-d'));
  26: 
  27: // 2018年6月29日8:50の9時間前
  28: $str = '2019-06-29T08:50+09:00';
  29: $dd  = 9;
  30: $sg = '-';
  31: $dt->parse_date($str);
  32: printf("%s", $dt->date('Y-m-d g:i:s'));
  33: printf(" %s %d hours = ", $sg, $dd);
  34: $dt->delta(($sg == '+' ? (+1: (-1)), 0, 0, 0, $dd);
  35: printf("%s\n", $dt->date('Y-m-d g:i:s'));

カレンダー計算

  11: //データ構造クラス
  12: require_once('pahooDataStructure.php');
  13: 
  14: //pahooDateTimeオブジェクト用意
  15: $dt0 = new pahooDateTime();
  16: $dt1 = new pahooDateTime();
  17: 
  18: $birth = '1954-09-21';          //誕生日
  19: $dt0->parse_date($birth);
  20: $today = date('Y-m-d');     //現在日付
  21: $dt1->parse_date($today);
  22: $birth = '1954-09-21';          //誕生日
  23: $life = 80.98;                  //平均余命
  24: 
  25: //生存日数を計算
  26: list($dd, $ss) = $dt1->sub($dt0);
  27: printf("\n%sから %sまで %d日\n", $birth, $today, $dd);
  28: 
  29: //死亡予定日を計算
  30: $dt0->delta(+1, $life);
  31: printf("%s死亡見込み\n", $dt0->date('Y-m-d'));

PHPで生存日数、死亡予定日を計算
サンプル・プログラム "dt02-21-03.php" の実行結果は左図の通りである。
2038年問題の制約を受けないことから、誕生日から現在までの生存日数や、誕生日に平均余命を加算することを死亡予定日を計算することもできる。あの超人ロックの年数計算だってできますよ😀

pahooDateTimeクラス は、年月日と時分秒を別々に計算することで2038年問題回避している。また、年・月・日・時・分・秒・タイムゾーン(オフセット秒数)を別々のクラス内変数に保持するようにした。100万年=1000000年×365.25日=3億6525万日である。一方、32ビットで表せる整数は±21億4748万3647である。

現地到着日時を計算

  11: //データ構造クラス
  12: require_once('pahooDataStructure.php');
  13: 
  14: //pahooDateTimeオブジェクト用意
  15: $dt = new pahooDateTime();
  16: 
  17: $start = '2018-07-01T21:45+09:00';          //成田空港出発日時
  18: $tz = '-10:00';                         //ハワイのタイムゾーン
  19: $hour = 7;  $min = 30;                      //飛行時間
  20: 
  21: $dt->parse_date($start);
  22: printf("\n出発:%s\n", $dt->date('c'));
  23: 
  24: $dt->delta(+1, 0, 0, 0, $hour, $min);       //飛行時間を加算
  25: $dt->tzone($tz);                            //タイムゾーン変更
  26: printf("到着:%s\n", $dt->date('c'));

PHPで現地到着日時を計算
サンプル・プログラム "dt02-21-04.php" の実行結果は左図の通りである。
pahooDateTimeクラス はタイムゾーンも保持しており、成田空港からハワイへ飛行機で飛び、到着時の現地時刻を計算するのも実質2行で実装できる。

24時制と12時制

 698:         $sec -60;
 699:         $min++;
 700:     }
 701:     while ($sec < 0) {
 702:         $sec +60;
 703:         $min--;
 704:     }
 705:     while ($min >60) {
 706:         $min -60;
 707:         $hour++;
 708:     }

コンピュータでは、12時制の場合、1~12の自然数しか使わない。したがって、深夜00:00はAM12:00、正午12:00はPM12:00、13:00はPM1:00となる。
なぜゼロを使わないかというと、アナログ時計を見れば分かる。ヨーロッパで12時制の時計が作られ始めていた当時、インドからゼロの概念が入っていなかったためだ。

サマータイム

daylight saving time = サマータイム
2020年開催の東京オリンピック・パラリンピックを目指し、わが国でもサマータイム(夏時間;summer time)の導入が検討されている。アメリカでは DST(daylight saving time)と呼ばれる。

現在、サマータイムを導入している国は、ヨーロッパを中心に約70カ国ある。
たとえばイギリスでは、毎年3月の最終日曜日1時(UTC)から10月の最終日曜日1時(UTC)までの期間、UTCより1時間だけ早めるイギリス夏時間(BST;British Summer Time)を実施している。
たとえば2018年3月25日(日)の1時になった瞬間、時計は2時を指す。そして、10月28日(日)の2時になった瞬間、時計は1時に戻る。
一方、アラスカでは、毎年4月の第一日曜日2時から10月の最終日曜日2時までをアラスカ夏時間(AKDT;Alaska Daylight Time)としている。
このように、国や地域によってサマータイムの期間が異なる

さらに、イギリスでは、第二次大戦中の1940年2月15日(木)2時(UTC)から1945年10月7日(日)2時(UTC)までの間、2時間のサマータイムを行っていた。
このように、サマータイムは1時間とは限らないこと、そして年代によっては期間や時間が異なる。ある時期にサマータイムを廃止した国もあるし、新規に導入した国もある。

わが国も、GHQの指導により、1948年4月28日に夏時刻法を公布し、サマータイムを導入した時期がある。実施期間は、1948年5月2日(日)0時~9月12日(日)1時、1949年4月3日(日)0時~1949年9月11日(日)1時、1950年5月7日(日)0時~1950年9月10日(日)1時、1951年5月6日(日)0時~1951年9月8日(土)1時という変則的なものだった。

このようにローカルな条件が多いため、ISO 8601 ではサマータイムを定義しておらず、時差で表現することを推奨している。
サマータイム表記
ISO 8601 表記 日本語表記
2018-03-25T01:30Z 西暦2018年3月25日01時30分UTC
2018-03-25T02:30+01:00 西暦2018年3月25日02時30分
(イギリス夏時間BST;UTCから+1時間の時差)
コンピュータ・システムへのサマータイムの実装は、千差万別である。
Windows 7以降のWindowsやmacOS、Linuxでは、時刻の内部管理にUTCを用いており、OS側でサマータイムを自動的に補正している。古いOSや、FATのような古いファイル管理システムでは、タイムスタンプをローカル時刻で記録しているため、サマータイムとのズレが発生してしまう。

サマータイムを実施すると、期間初日は23時間しかなく、最終日は25時間あることになる。ローカルタイムで日時計算を行うと、とくに最終日は1時間ないし2時間時刻が重なることになり、計算結果がおかしなことになる。そこで、かならずUTCに換算して計算する必要がある。
よって、日時のデータ構造は、UTCで行うか、ISO 8601 のようにUTCとの時差を明記した形で行うべきである。

現地到着日時を計算(サマータイム編)

サンプル・プログラム "dt02-21-04.php" では時差を含めた現地到着日時を計算したが、メソッド tzone に渡すタイムゾーンを変更することで、サマータイムにおける現地到着日時を計算することができる。
ただ、前述の通り、サマータイムの開始・終了日や時差は地域によって異なることから、これをいちいち指定するのは面倒だ。そこで、'Asia/Tokyo' などシステムでよく使われるタイムゾーン識別子を与えることで、時差とサマータイムの有無を織り込んで現地到着日時を計算するサンプル・プログラム "dt02-21-05.php" を作成する。

 562: /**
 563:  * タイムゾーンを取得する
 564:  * @param   string $timezoneタイムゾーン識別子('Asia/Tokyo' など)
 565:  * @return  stringタイムゾーン(+|-hh:mm形式)/FALSE定義域外
 566: */
 567: function getTimezone($timezone) {
 568:     $dtz = new DateTimeZone($timezone);
 569:     if ($dtz == FALSE) {
 570:         $this->error = TRUE;
 571:         $this->errmsg = 'TZONE: illegal';
 572:         return FALSE;
 573:     }
 574:     $dt  = new DateTime();
 575:     $dt->setTimezone($dtz);
 576:     if ($dt->setDate($this->year, $this->month, $this->day) == FALSE) {
 577:         $this->error = TRUE;
 578:         $this->errmsg = 'TZONE: illegal';
 579:         return FALSE;
 580:     }
 581:     if ($dt->setTime($this->hour, $this->minuite, $this->second) == FALSE) {
 582:         $this->error = TRUE;
 583:         $this->errmsg = 'TZONE: illegal';
 584:         return FALSE;
 585:     }
 586: 
 587:     $res = $dt->format('P');
 588:     if ($dt->format('Z'> 0)   $res = '+' . $res;
 589: 
 590:     $dtz = NULL;
 591:     $dt  = NULL;
 592: 
 593:     return $res;
 594: }

メソッド getTimezone は、先ほど使ったメソッド tzone の引数を求めるものだが、pahooDateTime インスタンスに記録されている年月日を見て、サマータイムかどうかを自動的に判断する。
タイムゾーン識別子から時差を求める処理は、PHPの DateTimeZone クラスと DateTime クラスを呼び出して実行している。

  14: //pahooDateTimeオブジェクト用意
  15: $dt = new pahooDateTime();
  16: 
  17: //タイムゾーン識別子
  18: $tokyo  = 'Asia/Tokyo';
  19: $london = 'Europe/London';
  20: 
  21: //飛行時間:羽田→ヒースロー
  22: $hour = 12$min = 30;
  23: 
  24: //通常時間
  25: $dt->datetime(2018, 1, 6, 11, 50, 0, $tokyo);   //羽田空港出発日時
  26: printf("\n出発:%s\n", $dt->date('c'));
  27: $dt->delta(+1, 0, 0, 0, $hour, $min);           //飛行時間を加算
  28: $tz = $dt->getTimezone($london);                //イギリス標準時
  29: $dt->tzone($tz);                                //タイムゾーン変更
  30: printf("到着:%s\n", $dt->date('c'));
  31: 
  32: //夏時間
  33: $dt->datetime(2018, 7, 1, 11, 50, 0, $tokyo);   //羽田空港出発日時
  34: printf("\n出発:%s\n", $dt->date('c'));
  35: $dt->delta(+1, 0, 0, 0, $hour, $min);           //飛行時間を加算
  36: $tz = $dt->getTimezone($london);                //イギリス標準時
  37: $dt->tzone($tz);                                //タイムゾーン変更
  38: printf("到着:%s\n", $dt->date('c'));

PHPで現地到着日時を計算(サマータイム編)
サマータイムがあるイギリス・ロンドンへの到着時刻を求めるサンプル・プログラム "dt02-21-05.php" の実行例は左図の通りだ。1月6日出発と7月1日出発とでは、日本にはサマータイムがないが、イギリスにはあるため、現地到着時刻が1時間変化する。

曜日の話

曜日の並びは、古代メソポタミア、古代エジプト、古代ギリシアの天文学(占星術)が合わさって、中世ヨーロッパで定まった。

古代メソポタミアでは、太陽、月、火星、水星、木星、金星、土星の7つの惑星(太陽・月を含む)を神として重視しており、1週間を7つに分割した。
古代エジプトでは、1日を24時間に分割していた。
古代ギリシアでは、惑星の並びは、地球から遠い順に、土星、木星、火星、太陽、金星、水星、月の順に定められた。

そこで、キリストが誕生した西暦1年1月1日0時を土星に、1時を木星に、2時を火星に‥‥と順番に並べてゆくと、下表のようになる。
毎日の0時の惑星を並べると、土星(土曜日)、太陽(日曜日)、月(月曜日)、火星(火曜日)、水星(水曜日)、木星(木曜日)、金星(金曜日)となる。

このように、かつて、1週間は土曜日から始まり、金曜日で終わっていた。その後、太陽信仰が加わり、1週間の始まりが日曜日にシフトした。

ちなみに、火曜日が火星に対応する古代ギリシア神話の神アレス、古代ローマ神話の神マルスと似ても似つかないテューズデイになっているのは、北欧神話の神テュールに由来するため。同様に、木曜日は神オーディンの武器トール、金曜日は女神フリッグに由来する。
このように曜日は、古代メソポタミア、古代エジプト、古代ギリシアだけでなく、北欧神話まで巻き込んで作られていった。

曜日の概念は、9世紀初め、空海が真言密教とともに「宿曜経 (すくようきょう) 」として日本へ紹介した。平安時代は陰陽師とともに重用されたが、鎌倉時代には衰退していった。
再び曜日が導入されているのは、明治維新後のことである。
時/日 西暦1年1月
1日 2日 3日 4日 5日 6日 7日 8日
0時 土星 太陽 火星 水星 木星 金星 土星
1時 木星 金星 土星 太陽 火星 水星 木星
2時 火星 水星 木星 金星 土星 太陽 火星
3時 太陽 火星 水星 木星 金星 土星 太陽
4時 金星 土星 太陽 火星 水星 木星 金星
5時 水星 木星 金星 土星 太陽 火星 水星
6時 火星 水星 木星 金星 土星 太陽
7時 土星 太陽 火星 水星 木星 金星 土星
8時 木星 金星 土星 太陽 火星 水星 木星
9時 火星 水星 木星 金星 土星 太陽 火星
10時 太陽 火星 水星 木星 金星 土星 太陽
11時 金星 土星 太陽 火星 水星 木星 金星
12時 水星 木星 金星 土星 太陽 火星 水星
13時 火星 水星 木星 金星 土星 太陽
14時 土星 太陽 火星 水星 木星 金星 土星
15時 木星 金星 土星 太陽 火星 水星 木星
16時 火星 水星 木星 金星 土星 太陽 火星
17時 太陽 火星 水星 木星 金星 土星 太陽
18時 金星 土星 太陽 火星 水星 木星 金星
19時 水星 木星 金星 土星 太陽 火星 水星
20時 火星 水星 木星 金星 土星 太陽
21時 土星 太陽 火星 水星 木星 金星 土星
22時 木星 金星 土星 太陽 火星 水星 木星
23時 火星 水星 木星 金星 土星 太陽 火星

参考サイト

参考書籍

表紙 時計の科学 人と時間の5000年の歴史
著者 織田 一朗
出版社 講談社
サイズ 新書
発売日 2017年12月14日頃
価格 1,078円(税込)
ISBN 9784065020418
人類が「時間」の存在に気づいたのは、いまから5000年以上も前のことです。太陽の動きを利用した「日時計」から始まり、周期を人工的につくりだす「機械式時計」の誕生、精度に革命を起こした「クオーツ時計」、そして、時間の概念を変えた「原子時計」まで。時代の最先端技術がつぎ込まれた時計の歴史を余すところなく解説します。
 
表紙 天文の世界史
著者 廣瀬 匠
出版社 集英社インターナショナル
サイズ 新書
発売日 2017年12月07日頃
価格 836円(税込)
ISBN 9784797680171
西洋だけでなく、インド、中国、マヤなどの天文学にも迫った画期的な天文学通史。神話から最新の宇宙物理までを、時間・空間ともに壮大なスケールで描き出す。人類は古来、天からのメッセージを何とか解読しようと、天文現象を観察。天文学は、地域や文化の壁を越えて発達し、政治や宗教とも深く関わってきた。天体を横軸に、歴史を縦軸に構成。学者たちの情熱、宇宙に関する驚きの事実や楽しい逸話も織り込んでいる。
 
(この項おわり)
header