日時の設定
現在時刻を表示できるだけでは何にもならないので、特定の日時を表現する方法を紹介しましょう。関数mktime(時,分,秒,月,日,年)の形式で日時を指定を使います。設定されるのは現行のタイムゾーン(ここではGMT)での日時です。
$birthday=mktime(0,0,0,1,5,1976); echo date('r',$birthday); // Mon, 05 Jan 1976 00:00:00 +0000
mktime()の戻り値は整数です。
echo mktime(0,0,0,1,5,1976); // 189648000
これは、UNIXエポック(1970-01-01T00:00:00+0000)からの経過時間(秒)で、エポックタイムスタンプと呼ばれます。正の値がUNIXエポック以降を、負の値が以前を表します。現在の値はtime()で得られます。
エポックタイムスタンプは4バイトの整数(いわゆるint型)で処理されるため、その範囲を超えた日時を扱えないという大問題があります。2038年問題です。
$n=-2147483649; echo date(DATE_ISO8601,$n); // 2038-01-19T03:14:07+0000 //(間違い) $n=-2147483648; echo date(DATE_ISO8601,$n); // 1901-12-13T20:45:52+0000 //(正しい) $n=-2147483647; echo date(DATE_ISO8601,$n); // 1901-12-13T20:45:53+0000 //(正しい) $n=0; echo date(DATE_ISO8601,$n); // 1970-01-01T00:00:00+0000 //(Unixエポック) $n=2147483647; echo date(DATE_ISO8601,$n); // 2038-01-19T03:14:07+0000 //(正しい) $n=2147483648; echo date(DATE_ISO8601,$n); // 1901-12-13T20:45:52+0000 //(間違い)
関数var_dump()によって、整数をint型で扱えているかどうかを調べられます。下のように、-2147483648以下あるいは2147483648以上の整数はint型では扱えないのです。エポックタイムスタンプがint型の範囲を超えると、上で見たように日時の処理が狂います。ちなみに、一般的には(C言語等)では-2147483648(つまり-2の32乗)はintの範囲内です。いずれにしても、スクリプト言語でこのレベルの話はあまりしたくないものです。
$n=-2147483649; echo var_dump($n); // float(-2147483649) $n=-2147483648; echo var_dump($n); // float(-2147483648) $n=-2147483647; echo var_dump($n); // int(-2147483647) $n=2147483647; echo var_dump($n); // int(2147483647) $n=2147483648; echo var_dump($n); // float(2147483648)
PEAR Date
エポックタイムスタンプがintで表せる範囲の日時しか扱えないという問題は、DateというPEARのパッケージを導入することで解決できます。
現在の日時
まずは現在の日時から試しましょう。
require_once 'Date.php'; $now=new Date(); // 引数なしで生成すると現在の日時になる echo $now->getDate(); // 2007-10-16 07:50:06 (整形して表示) echo $now->getTime(); // 1192521006 (エポックタイムスタンプ)
日時の設定
Dateのコンストラクタでは、さまざまな形式で日時を指定することができます。ソースを読んでもらうのが一番ですが、ここでは3つの方法を紹介しましょう。
$date=new Date(-2147483649); // エポックタイムスタンプで指定 echo $date->getDate(); // 2038-01-19 03:14:07 (間違い) echo $date->getTime(); // 2147483647 (元の値と違う) $date=new Date('19011213204551'); // 数字の羅列で指定 echo $date->getDate(); // 1901-12-13 20:45:51 (正しい) echo $date->getTime(); // 取得できない(falseが返る) $date=new Date('1901-12-14T05:45:51+09:00'); // ISO 8601形式の文字列で指定 echo $date->getDate(); // 1901-12-13 20:45:51 (UTCだが正しい) echo $date->getTime(); // 取得できない(falseが返る)
先に紹介した方法では扱えなかった日時(エポックタイムスタンプが4バイトの整数では表せない)も、タイムスタンプ形式やISO 8601形式の文字列で指定すれば扱えることが分かります。ただし、作成した日時のエポックタイムスタンプを取得することができません。
日時の表示
Dateオブジェクトは、あるタイムゾーンでの年や月、日、時、分、秒を別々に保持しています。これらの要素はメソッドgetYear()等で取得し、setYear()等で設定できます(ロケールを日本語・日本に設定しておけば、getDayName()は日本語の曜日を返します)。メソッドの詳細はAPIリファレンスを参照してください。
$date=new Date('1901-12-13T20:45:51+00:00'); echo $date->getYear(); // 1901 $date->setYear(2001); echo $date->getYear(); // 2001
getterメソッドを組み合わせれば、いろいろな形式を作り出すことができますが、単に整形したいだけなら、format()を使います。
$now=new Date(); echo $now->format('今日は%Y年%m月%e日です'); // 今日は2007年10月16日です
format()のフォーマット文字はstrftime()のそれと大体同じです(詳細はAPIリファレンスのformat()の項を参照してください)。
getDate()に実装されている次のようなフォーマットを使ってもいいでしょう。
echo $now->getDate(); // 2007-10-16 07:50:06 echo $now->getDate(DATE_FORMAT_ISO); // 2007-10-16 07:50:06 echo $now->getDate(DATE_FORMAT_ISO_BASIC); // 20071016T075006 echo $now->getDate(DATE_FORMAT_ISO_EXTENDED); // 2007-10-16T07:50:06 echo $now->getDate(DATE_FORMAT_ISO_EXTENDED_MICROTIME); // 2007-10-16T07:50:06.000000 echo $now->getDate(DATE_FORMAT_TIMESTAMP); // 20071016075006 echo $now->getDate(DATE_FORMAT_UNIXTIME); // 1192521006
タイムゾーン
先に述べたように、Dateオブジェクトはタイムゾーンと年月日時分秒等で日時を保持しています。利用可能なタイムゾーンは次のように調べられます(実行結果は省略します)。
foreach(Date_TimeZone::getAvailableIDs() as $id) echo '□'.$id;
タイムゾーンには2種類の変更方法があります。
date_default_timezone_set('GMT'); // タイムゾーンをGMTにしておく $date=new Date('19810128000000'); // 時刻を設定 $date->convertTZbyID('Asia/Tokyo');// 日本時間ではどうなるかを知りたい //(保持する日時は変わらない) echo $date->format('%Y-%m-%eT%H:%M:%S%o'); // 1981-01-28T09:00:00+09:00 $date=new Date('19810128000000'); // 時刻を設定 $date->setTZbyID('Asia/Tokyo'); // タイムゾーンだけを変更 // (保持する日時が変わる) echo $date->format('%Y-%m-%eT%H:%M:%S%o'); // 1981-01-28T00:00:00+09:00
日時の計算
Dateには、次の日を計算するgetNextDay()や、前の日を計算するgetPrevDay()が備わっています。
$now=new Date(); echo $now->getDate(); // 2007-10-16 07:50:06 $tommorow=$now->getNextDay(); echo $tommorow->getDate(); // 2007-10-17 07:50:06
「10日後」のように、一般的な方法で指定したい場合は、期間を表すDate_Spanオブジェクトを作成し、DateのメソッドaddSpan()に与えます(「前」を知りたい場合はsubtractSpan()を使います。負の期間はありません)。Date_Spanのコンストラクタにはさまざまな呼び出し方がありますが、ここでは文字列とフォーマットを与える方法を紹介します。フォーマットの指定方法はAPIリファレンスのsetFromString()の項(コンストラクタの内部で呼ばれるメソッド)を参照してください。
$span=new Date_Span('10','%D'); $after=new Date($now); $after->addSpan($span); echo $after->getDate(); // 2007-10-26 07:50:06 $before=new Date($now); $before->subtractSpan($span); echo $before->getDate(); // 2007-10-06 07:50:06
Date_Spanは2つの日時の間隔を調べるのにも使えます。例えば、1900年1月1日から1901年1月1日までの日数は、次のように調べられます(setFromDateDiff()の引数の順番は結果に関係しません。Date_Spanに「負」はないのです)。
$date1=new Date('1900-01-01T00:00:00+00:00'); $date2=new Date('1901-01-01T00:00:00+00:00'); $span->setFromDateDiff($date1,$date2); echo $span->toDays(); // 365
日数が365日なので、1900年は閏年ではありません(1900は100の倍数でかつ400の倍数ではないので閏年ではないのです)。もっとも、閏年かどうかを調べたいだけなら、DateのメソッドisLeapYear()を使う方が簡単です。Date_Calc::isLeapYear($year)をあえてDateから呼び出せるようにする設計には若干の違和感を感じてしまいますが…。
echo ($date1->isLeapYear() ? '閏年' : '閏年ではない'); // 閏年ではない




