SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

PEARライブラリ活用

PHPにおける日付と時刻の混乱

PEARライブラリ活用 (1)


日時の設定

 現在時刻を表示できるだけでは何にもならないので、特定の日時を表現する方法を紹介しましょう。関数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() ? '閏年' : '閏年ではない'); // 閏年ではない

次のページ
PEAR Dateの問題点

この記事は参考になりましたか?

PEARライブラリ活用連載記事一覧

もっと読む

この記事の著者

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるLaravel実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

WINGSプロジェクト 矢吹 太朗(ヤブキ タロウ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。 2026年時点での登録メンバは約50名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS X: @WingsPro_info(公式)、@WingsPro_info/wings(メンバーリスト) Facebook

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/1816 2007/12/13 16:10

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー