DateTime
日時を表す3番目の形式DateTimeを紹介しましょう。
現在の日時
まず、date_create()で現在の日時を取得します。
$now=date_create();
表示の際、date()で使えたフォーマットをここでも使えます。
echo $now->format(DATE_ISO8601); // 2007-10-16T07:50:06+0000
日時の設定
ISO 8601形式の文字列でDateTimeオブジェクトを生成することができます(ほかにもGNU Date Input Formatに準拠した形式ならば使えるようです)。エポックタイムスタンプがintの範囲外にある日時も扱えます。
$date=date_create('1901-12-13T20:45:51+0000'); echo $date->format(DATE_ISO8601); // 1901-12-13T20:45:51+0000 //(正しい)
DateTime::modify(string $modify)を使って日時の移動ができるはずなのですが、1582年10月4日の翌日は、かなりおかしなことになっています。
$date=date_create('1582-10-04T00:00:00+0000'); $date->modify('+1 day'); echo $date->format(DATE_ISO8601); // 1582-09-24T00:00:00+0000 (間違い)
DateTimeは比較的新しく実装されたクラスのせいか(PHP 5.1以上)、このようなバグを含んでいます。また、PEAR Dateに比べて機能も劣っています。
ユリウス日
結局、標準に用意されている方法で1582年10月4日の翌日を10月15日にするためには、JulianToJD()でユリウス暦の日付をユリウス日に変換し、そのユリウス日に1を追加、JDToGregorian()でグレゴリオ暦の日付に直すという作業が必要になります。
$jd=JulianToJD(10,4,1582); $jd++; echo JDToGregorian($jd); // 10/15/1582 (正しい)
ユリウス日とは、ユリウス暦の紀元前4713年1月1日からの積算日のことです(ユリウス日とユリウス暦はまったく別物です)。ユリウス日を使えば、筆者の祖母が生まれてから今日で何日になるかも計算できます(PEARのDate_Spanでは扱えなかった期間です)。
$birthdayJD=GregorianToJD(3,8,1923); $now=date_parse(date_create()->format(DATE_ISO8601)); $nowJD=GregorianToJD($now[month],$now[day],$now[year]); echo $nowJD-$birthdayJD; // 30903 (正しい)
このように、ユリウス日を基準に考えることで、日付の計算を正しく行えます。
しかしながら、PHPのユリウス日には、それが「整数」だという致命的な欠点があります。1未満の数を使って時間を表すことができないのです。タイムゾーンのことを考えれば、日付と時刻を分離して管理してはいけないことは容易に想像できるでしょう。
また、いちいちユリウス日に変換しなければならないのも面倒です(内部的に行って欲しいものです)。ちなみに、Javaの場合、日時を管理するクラスGregorianCalendarのデフォルトの動作が「1548年10月4日以前はユリウス暦、それより後はグレゴリオ暦」ということになっているので、一般的なユーザーが1582年10月15日の暦の切り替えを意識する必要はありません(拙著『初級プログラマのためのWebアプリケーション構築入門』を参照してください)。
まとめ
PHPで日付と時刻を扱う標準的な方法を紹介しました。大きく分けて4つの方法があり、そのすべてに問題があることが分かりました。
- エポックタイムスタンプ
- PEAR Date
- DateTime
- ユリウス日
日付や時刻の処理を実装するのはとても大変なので、大抵の場合はプログラミング言語にあらかじめ備わっているライブラリやクラスを利用します。ライブラリには適用可能な範囲があり、その範囲内で使う分には問題ないのですが、その範囲を超えると結果はまったく信用できないものになってしまいます。
悪いことに、PHPの場合は適用範囲外で利用しようとしても、分かりやすいエラーが返らないことがほとんどです。かつての2000年問題や、今起こりつつある2038年問題のような問題を再び生まないためにも、この記事で紹介したようなケースを心にとめて開発していただきたいと思います。
