日付と時刻

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

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

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

サンプル・プログラム

日付を表す文字列

「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 表記
RFC3339 表記 日本語表記
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


とすればいい。

0009: from datetime import *
0010: import sysiso8601
0011: 
0012: sys.stdout.write("\n")
0013: 
0014: # 2018年6月29日の7日後
0015: str = '2018-06-29'
0016: dd  = 7
0017: if (dd > 0):
0018:     sg = ''
0019: else:
0020:     sg = ''
0021: dt = iso8601.parse_date(str)
0022: dt = dt + timedelta(days=1) * dd
0023: sys.stdout.write("{} ".format(str))
0024: sys.stdout.write(sg)
0025: sys.stdout.write(" {} days = ".format(abs(dd), '%d'))
0026: sys.stdout.write("{}\n".format(dt.strftime('%Y/%m/%d')))
0027: 
0028: # 2018年6月29日8:50の9時間前
0029: str = '2018-06-29T08:50'
0030: dd  = -9
0031: if (dd > 0):
0032:     sg = ''
0033: else:
0034:     sg = ''
0035: dt = iso8601.parse_date(str)
0036: dt = dt + timedelta(hours=1) * dd
0037: sys.stdout.write("{} ".format(str))
0038: sys.stdout.write(sg)
0039: sys.stdout.write(" {} hours = ".format(abs(dd), '%d'))
0040: sys.stdout.write("{}\n".format(dt.strftime('%Y-%m-%d %H:%M')))
0041: 
0042: sys.stdout.write("\n")

PythonでISO 8601日付・時刻の計算
実行結果は上図の通りである。
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を減算した結果を返す。
tzone()タイムゾーンを変更する。
parse_date()ISO 8601 日時フォーマットの解釈。
date()日時フォーマットで文字列に変換する。
isleap()閏年かどうか判定する。
getDaysInMonth()指定した月の日数を返す。

 

0011: //データ構造クラス
0012: require_once('pahooDataStructure.php');
0013: $dt = new pahooDateTime();                   //pahooDateTimeオブジェクト用意
0014: 
0015: printf("\n");
0016: 
0017: // 2018年6月29日の7日後
0018: $str = '2019-06-29';
0019: $dd  = 7;
0020: $sg = '';
0021: $dt->parse_date($str);
0022: printf("%s", $dt->date('Y-m-d'));
0023: printf(" %s %d days = ", $sg$dd);
0024: $dt->delta(($sg == '' ? (+1) : (-1)), 0, 0, $dd);
0025: printf("%s\n", $dt->date('Y-m-d'));
0026: 
0027: // 2018年6月29日8:50の9時間前
0028: $str = '2019-06-29T08:50+09:00';
0029: $dd  = 9;
0030: $sg = '';
0031: $dt->parse_date($str);
0032: printf("%s", $dt->date('Y-m-d g:i:s'));
0033: printf(" %s %d hours = ", $sg$dd);
0034: $dt->delta(($sg == '' ? (+1) : (-1)), 0, 0, 0, $dd);
0035: printf("%s\n", $dt->date('Y-m-d g:i:s'));

先ほどの Python プログラムを移植すると、こうなる。

カレンダー計算

0011: //データ構造クラス
0012: require_once('pahooDataStructure.php');
0013: 
0014: //pahooDateTimeオブジェクト用意
0015: $dt0 = new pahooDateTime();
0016: $dt1 = new pahooDateTime();
0017: 
0018: $birth = '1954-09-21';            //誕生日
0019: $dt0->parse_date($birth);
0020: $today = date('Y-m-d');      //現在日付
0021: $dt1->parse_date($today);
0022: $birth = '1954-09-21';            //誕生日
0023: $life = 80.98;                    //平均余命
0024: 
0025: //生存日数を計算
0026: list($dd$ss) = $dt1->sub($dt0);
0027: printf("\n%sから %sまで %d日\n", $birth$today$dd);
0028: 
0029: //死亡予定日を計算
0030: $dt0->delta(+1, $life);
0031: printf("%s 死亡見込み\n", $dt0->date('Y-m-d'));

2038 年問題の制約を受けないことから、誕生日から現在までの生存日数や、誕生日に平均余命を加算することを死亡予定日を計算することもできる。あの超人ロックの年数計算だってできますよ😀
pahooDateTime クラス は、年月日と時分秒を別々に計算することで2038 年問題回避している。また、年・月・日・時・分・秒・タイムゾーン(オフセット秒数)を別々のクラス内変数に保持するようにした。100 万年=1000000 年×365.25 日=3 億 6525 万日である。一方、32 ビットで表せる整数は±21 億 4748 万 3647である。
PHPで生存日数、死亡予定日を計算
実行例は左図の通り。

現地到着日時を計算

0011: //データ構造クラス
0012: require_once('pahooDataStructure.php');
0013: 
0014: //pahooDateTimeオブジェクト用意
0015: $dt = new pahooDateTime();
0016: 
0017: $start = '2018-07-01T21:45+09:00';            //成田空港出発日時
0018: $tz = '-10:00';                           //ハワイのタイムゾーン
0019: $hour = 7;   $min = 30;                       //飛行時間
0020: 
0021: $dt->parse_date($start);
0022: printf("\n出発:%s\n", $dt->date('c'));
0023: 
0024: $dt->delta(+1, 0, 0, 0, $hour$min);      //飛行時間を加算
0025: $dt->tzone($tz);                          //タイムゾーン変更
0026: printf("到着:%s\n", $dt->date('c'));

pahooDateTime クラス はタイムゾーンも保持しており、成田空港からハワイへ飛行機で飛び、到着時の現地時刻を計算するのも実質2行で実装できる。
PHPで現地到着日時を計算
実行例は左図の通り。

24時制と12時制

0423:     $x0 = floor($jd + 68570);
0424:     $x1 = floor($x0 / 36524.25);
0425:     $x2 = $x0 - floor(36524.25 * $x1 + 0.75);
0426:     $x3 = floor(($x2 + 1) / 365.2425);
0427:     $x4 = $x2 - floor(365.25 * $x3) + 31;
0428:     $x5 = floor(floor($x4) / 30.59);
0429:     $x6 = floor(floor($x5) / 11.0);
0430: 
0431:     $day   = $x4 - floor(30.59 * $x5);
0432:     $month = $x5 - 12 * $x6 + 2;
0433:     $year  = 100 * ($x1 - 49) + $x3 + $x6;

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

参考サイト

参考書籍

表紙 時計の科学 人と時間の5000年の歴史
著者 織田 一朗
出版社 講談社
サイズ 新書
発売日 2017年12月13日
価格 1,058円(税込)
rakuten
ISBN 9784065020418
人類が「時間」の存在に気づいたのは、いまから5000年以上も前のことです。太陽の動きを利用した「日時計」から始まり、周期を人工的につくりだす「機械式時計」の誕生、精度に革命を起こした「クオーツ時計」、そして、時間の概念を変えた「原子時計」まで。時代の最先端技術がつぎ込まれた時計の歴史を余すところなく解説します。
 
(この項おわり)
header