1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

srtotime($time, $now)をDateTimeメソッドだけで実装する

Last updated at Posted at 2016-12-24

PHPのstrtotime()関数の第二引数を指定すると、基準となる現在時刻を変更することができます。言い換えると、第二引数を指定した場合、第一引数の日付文字列で相対的な書式を指定した場合の戻り値が変わります。

<?php
var_dump(date("c", strtotime("yesterday")));               // string(25) "2016-12-23T00:00:00+00:00"
var_dump(date("c", strtotime("yesterday", time()-86400))); // string(25) "2016-12-22T00:00:00+00:00"

ところで、strtotime()と完全互換の機能をDateTimeで実現したいと思ったことはありませんか?私が昔試みた時点では不可能だった気がするのですが、PHP 5.3.3以降であれば次のように実現できるのではないか?と考えました。

function is_relative_datetime_format($time)
{
    return (date_create("2001-02-03 $time") !== FALSE);
}
function my_strtotime($time, $now = NULL)
{
    if ($time === "") {
        return FALSE;
    }
    $dt = date_create($time);
    if ($dt === FALSE) {
        return FALSE;
    }
    if (is_relative_datetime_format($time) && $now !== NULL) {
        $dt = new \DateTime(date("Y-m-d H:i:s", $now), $dt->getTimezone());
        $dt->modify($time);
    }
    return $dt->getTimeStamp();
}

DateTimeのコンストラクタを最大3回呼んだ上で更にmodifyメソッドを呼んでおり一見すると冗長に見えるのですが、おそらく必要な処理だと考えています。もっとシンプルに書けるよ、という人がいたら教えてもらえると嬉しいです。

動機

上のコードはあくまで検証用のコードで、実用性はありません。

なんのためにこんな検証をしているかというと、現在strtotime()で行っている処理をDateTimeに寄せたかったからです。私の自作PHP拡張の処理がunixタイムスタンプに依存しているのですが、今のままだとマイクロ秒の扱いが面倒だという問題があります。DateTimeの方がマイクロ秒の扱いが楽であり、PHP 7.1からは勝手に現在時刻としてマイクロ秒まで入れ込んでくれるようになったので(参照:「PHP 7.1からDateTimeが現在時刻のマイクロ秒まで見るようになった」)、今後の実装ではこちらを利用すべきだと考えています。

追試のための情報

今回の検証は下記バージョンで行いました。

  • PHP 5.3.29
  • PHP 5.4.45
  • PHP 5.5.38
  • PHP 5.6.28
  • PHP 7.0.13
  • PHP 7.1.0

検証に使ったコードは次のようなものです。何も表示されなければstrtotime()my_srttotime()が同じ結果を返していることがわかります。

<?php
function is_relative_datetime_format($time)
{
    return (date_create("2001-02-03 $time") !== FALSE);
}
function my_strtotime($time, $now = NULL)
{
    if ($time === "") {
        return FALSE;
    }
    $dt = date_create($time);
    if ($dt === FALSE) {
        return FALSE;
    }
    if (is_relative_datetime_format($time) && $now !== NULL) {
        $dt = new \DateTime(date("Y-m-d H:i:s", $now), $dt->getTimezone());
        $dt->modify($time);
    }
    return $dt->getTimeStamp();
}

$time_strings = array(
    "",
    "now",
    "today",
    "+3days",
    "-1 min",
    "next Sunday",
    "first day of next month",
    "2012-03-31 12:34:56",
    "2012-03-31",
    "2012-03",
    //"March 31",
    "2012",
    //"March",
    "2015-W06-2",
    "2015-W06",
    "2015.034",
    "12:34:56",
    "12:34",
    "GMT",
    "12:00 America/Los_Angeles",
    "+03:00",
    "1970-01-01 19:00:00 EST",
    "@86400",
);

$now = time() + 86400*366 + 86400*31 + 86400 + 3600 + 60 + 1;
foreach ($time_strings as $time) {
    $t1 = strtotime($time, $now);
    $t2 = my_strtotime($time, $now);
    if ($t1 !== $t2) {
        printf("strtotime('%s', \$now) != my_strtotime('%s', \$now) (%s, %s)\n", $time, $time, date("c", $t1), date("c", $t2));
        var_dump($t1, $t2);
    }

    $t3 = strtotime($time);
    $t4 = my_strtotime($time);
    if ($t3 !== $t4) {
        printf("strtotime('%s') != my_strtotime('%s') (%s, %s)\n", $time, $time, date("c", $t3), date("c", $t4));
        var_dump($t3, $t4);
    }
}

試行錯誤の末、"March"以外は期待通りに動くようになりました。とはいえ、この方針で行くのが正解なのかどうかは微妙ですね…。

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?