サンプル・プログラムのダウンロード
pahooDataStructure.php | データ構造に関わるPHPのクラス。include_pathが通ったディレクトリに配置すること。 |
dt02-21-01.py | Python:ISO 8601日付・時刻の計算 |
dt02-21-02.php | PHP:ISO 8601日付・時刻の計算 |
dt02-21-03.php | PHP:生存日数、死亡予定日を計算 |
dt02-21-04.php | PHP:現地到着日時を計算 |
dt02-21-05.php | PHP:現地到着日時を計算(サマータイムを含む) |
日付を表す文字列
しかし、日付には「2018年6月29日から10日は何月何日?」というように「quantitation として扱う場合がある。この場合は、計算可能なデータ属性にしておかなければならない。時刻のデータ属性を含めて後述する。
時刻を表す文字列
時刻も日付と同様、表記方式が統一されておらず、identity としての時刻を格納したいだけであれば、データ属性を文字列としておけばいいだろう。
しかし、日付にも「24時25分から5時間後は何時何分」というように「quantitation として扱う場合がある。この場合は、計算可能なデータ属性にしておかなければならない。
さらに時刻の計算を行う場合、わが国にいる限りは意識しなくて済むが、時差」やサマータイムも意識しなくてはいけない。
quantitation としての日付・時刻
日付・時刻を表記する国際規格として ISO 8601 がある。これを使った表記例を下表に示す。最近は、インターネットでよく見かける表記方式で、ここでも ISO 8601 を利用することにする。
時刻の後ろに Z を添えることで UTC(協定世界時)を、また +/- でUTCからの時差を表現することで各地のローカル時刻を表現することができる。
コンピュータ内部では文字列として保持すれば十分だが、データ属性が日付時刻であることを意識する必要がある。
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による日付・時刻計算
導入は、コマンドラインから
pip install iso8601とすればいい。
from datetime import *
import sys, iso8601
sys.stdout.write("\n")
# 2018年6月29日の7日後
str = '2018-06-29'
dd = 7
if (dd > 0):
sg = '+'
else:
sg = '-'
dt = iso8601.parse_date(str)
dt = dt + timedelta(days=1) * dd
sys.stdout.write("{} ".format(str))
sys.stdout.write(sg)
sys.stdout.write(" {} days = ".format(abs(dd), '%d'))
sys.stdout.write("{}\n".format(dt.strftime('%Y/%m/%d')))
# 2018年6月29日8:50の9時間前
str = '2018-06-29T08:50'
dd = -9
if (dd > 0):
sg = '+'
else:
sg = '-'
dt = iso8601.parse_date(str)
dt = dt + timedelta(hours=1) * dd
sys.stdout.write("{} ".format(str))
sys.stdout.write(sg)
sys.stdout.write(" {} hours = ".format(abs(dd), '%d'))
sys.stdout.write("{}\n".format(dt.strftime('%Y-%m-%d %H:%M')))
sys.stdout.write("\n")
ISO8601パッケージに用意されているメソッド iso8601.parse_date により、ISO 8601形式の日付時刻文字列を datetimeオブジェクトに変換する。
日付計算の制約
これは、日付を内部的に秒数で保持しており、32ビット整数で表現できる年数が±68年という制約による。
また、UNIX処理系では1970年1月1日0時0分0秒からの累積秒数(UNIX epoch)を保持しており、今日でも多くの処理系がこれに倣っている。
これらの制約を2038年問題と呼ぶ
PHPによる日時クラスの作成
そこで、少なくとも±100万年の日時計算ができるユーザー・クラス pahooDateTime を作成した。次のメソッドを備えている。
メソッド | 機能 |
---|---|
datetime() | 年月日時分秒をセットする。 |
delta() | 日時の差分を計算する。 |
sub() | $dtを減算した結果を返す。 |
getTimezone() | タイムゾーン識別子によりタイムゾーンのオフセット値を取得する。 |
tzone() | タイムゾーンを変更する。 |
parse_date() | ISO 8601 日時フォーマットの解釈。 |
date() | 日時フォーマットで文字列に変換する。 |
isleap() | 閏年かどうか判定する。 |
getDaysInMonth() | 指定した月の日数を返す。 |
verify() | 年月日時分秒の各値が正しいかどうか検査する。 |
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'));
カレンダー計算
dt02-21-03.php
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'));
pahooDateTimeクラス は、年月日と時分秒を別々に計算することで2038年問題回避している。また、年・月・日・時・分・秒・タイムゾーン(オフセット秒数)を別々のクラス内変数に保持するようにした。100万年=1000000年×365.25日=3億6525万日である。一方、32ビットで表せる整数は±21億4748万3647である。
現地到着日時を計算
dt02-21-04.php
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'));
24時制と12時制
pahooDataStructure.php
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時制の時計が作られ始めていた当時、インドからゼロの概念が入っていなかったためだ。
サマータイム
一方、アラスカでは、毎年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との時差を明記した形で行うべきである。
現地到着日時を計算(サマータイム編)
ただ、前述の通り、サマータイムの開始・終了日や時差は地域によって異なることから、これをいちいち指定するのは面倒だ。そこで、'Asia/Tokyo' などシステムでよく使われるタイムゾーン識別子を与えることで、時差とサマータイムの有無を織り込んで現地到着日時を計算するサンプル・プログラム "dt02-21-05.php" を作成する。
pahooDataStructure.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: }
タイムゾーン識別子から時差を求める処理は、PHPの DateTimeZone クラスと DateTime クラスを呼び出して実行している。
dt02-21-05.php
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'));
曜日の話
古代メソポタミアでは、太陽、月、火星、水星、木星、金星、土星の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 | ||
西洋だけでなく、インド、中国、マヤなどの天文学にも迫った画期的な天文学通史。神話から最新の宇宙物理までを、時間・空間ともに壮大なスケールで描き出す。人類は古来、天からのメッセージを何とか解読しようと、天文現象を観察。天文学は、地域や文化の壁を越えて発達し、政治や宗教とも深く関わってきた。天体を横軸に、歴史を縦軸に構成。学者たちの情熱、宇宙に関する驚きの事実や楽しい逸話も織り込んでいる。 | |||
参考サイト
- 時刻の話:ぱふぅ家のホームページ
- 午後12時10分? 午後0時10分?:ぱふぅ家のホームページ
- PHPでテキスト中の和暦・西暦年号を統一する(その2):ぱふぅ家のホームページ
- PHPで3ヶ月カレンダーを作る:ぱふぅ家のホームページ
- 世界の国のサマータイムの導入状況についてTime-j.net 世界時計
西暦年月日時分秒、時差、サマータイムの全てを表す標準書式として、ISO 8601がある。PythonやPHPには、ISO 8601を扱う仕組みが用意されているが、2038年問題の制約を受けることから、±100万年を扱えるPHP用のクラスを作成する。
また、PHPによる西暦と元号の相互変換クラスは「PHPでテキスト中の和暦・西暦年号を統一する(その2)」を、旧暦計算クラスは「PHPで3ヶ月カレンダーを作る」を参考にしてほしい。