PHPにおける翌週、先週、翌月、先月の取得方法


正直、オブジェクト指向で記述するのは結構大変。
当面の間は手続き型のみで記述した方が安全そう。

日の差分を出すだけならオブジェクト指向の方が楽なんだけどね。

## PHPでの無難な日付操作一覧

|用途 |コード |結果 |
|----|----|----|
|基準日の設定<br>実際の使用シーンでは<br>\$ts = time();<br>や<br>\$ts = \$_SERVER['REQUEST_TIME'];<br>が使用されるはず |\$base_datetime = '2016-08-11 13:00:00'; |2016-08-11 13:00:00 |
|基準となるタイムスタンプの作成 |\$ts = strtotime(\$base_datetime); |1470888000 |
|当日開始時 |date('Y-m-d 00:00:00', \$ts); |2016-08-11 00:00:00 |
|当日終了時 |date('Y-m-d 23:59:59', \$ts); |2016-08-11 23:59:59 |
|月初 |date('Y-m-01 00:00:00', \$ts); |2016-08-01 00:00:00 |
|月末 |date('Y-m-t 23:59:59', \$ts); |2016-08-31 23:59:59 |
|先月初 |date('Y-m-01 00:00:00', strtotime(date('Y-m-01 00:00:00', \$ts)) - 1); |2016-07-01 00:00:00 |
|先月末 |date('Y-m-t 23:59:59', strtotime(date('Y-m-01 00:00:00', \$ts)) - 1); |2016-07-31 23:59:59 |
|翌月初 |date('Y-m-01 00:00:00', strtotime(date('Y-m-t 23:59:59', \$ts)) + 1); |2016-09-01 00:00:00 |
|翌月末 |date('Y-m-t 23:59:59', strtotime(date('Y-m-t 23:59:59', \$ts)) + 1); |2016-09-30 23:59:59 |
|週頭（日） |date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', \$ts)), \$ts)); |2016-08-07 00:00:00 |
|週末（土） |date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', \$ts)), \$ts)); |2016-08-13 23:59:59 |
|先週頭（日） |date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', \$ts) + 7), \$ts)); |2016-07-31 00:00:00 |
|先週末（土） |date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', \$ts) - 7), \$ts)); |2016-08-06 23:59:59 |
|翌週頭（日） |date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', \$ts) - 7), \$ts)); |2016-08-14 00:00:00 |
|翌週末（土） |date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', \$ts) + 7), \$ts)); |2016-08-20 23:59:59 |

### 表構築機

```php
<?php

$message = [
    [
        '基準日の設定（実際の使用シーンでは$ts = time();や$ts = $_SERVER[\'REQUEST_TIME\'];が使用されるはず）',
        '$base_datetime = \'2016-08-11 13:00:00\';',
        $base_datetime = '2016-08-11 13:00:00',
    ],
    [
        '基準となるタイムスタンプの作成',
        '$ts = strtotime($base_datetime);',
        $ts = strtotime($base_datetime),
    ],
    [
        '当日開始時',
        'date(\'Y-m-d 00:00:00\', $ts);',
        date('Y-m-d 00:00:00', $ts),
    ],
    [
        '当日終了時',
        'date(\'Y-m-d 23:59:59\', $ts);',
        date('Y-m-d 23:59:59', $ts),
    ],
    [
        '月初',
        'date(\'Y-m-01 00:00:00\', $ts);',
        date('Y-m-01 00:00:00', $ts),
    ],
    [
        '月末',
        'date(\'Y-m-t 23:59:59\', $ts);',
        date('Y-m-t 23:59:59', $ts),
    ],
    [
        '先月初',
        'date(\'Y-m-01 00:00:00\', strtotime(date(\'Y-m-01 00:00:00\', $ts)) - 1);',
        date('Y-m-01 00:00:00', strtotime(date('Y-m-01 00:00:00', $ts)) - 1),
    ],
    [
        '先月末',
        'date(\'Y-m-t 23:59:59\', strtotime(date(\'Y-m-01 00:00:00\', $ts)) - 1);',
        date('Y-m-t 23:59:59', strtotime(date('Y-m-01 00:00:00', $ts)) - 1),
    ],
    [
        '翌月初',
        'date(\'Y-m-01 00:00:00\', strtotime(date(\'Y-m-t 23:59:59\', $ts)) + 1);',
        date('Y-m-01 00:00:00', strtotime(date('Y-m-t 23:59:59', $ts)) + 1),
    ],
    [
        '翌月末',
        'date(\'Y-m-t 23:59:59\', strtotime(date(\'Y-m-t 23:59:59\', $ts)) + 1);',
        date('Y-m-t 23:59:59', strtotime(date('Y-m-t 23:59:59', $ts)) + 1),
    ],
    [
        '週頭（日）',
        'date(\'Y-m-d 00:00:00\', strtotime(sprintf(\'-%s day\', date(\'w\', $ts)), $ts));',
        date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', $ts)), $ts)),
    ],
    [
        '週末（土）',
        'date(\'Y-m-d 23:59:59\', strtotime(sprintf(\'+%s day\', 6 - date(\'w\', $ts)), $ts));',
        date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', $ts)), $ts)),
    ],
    [
        '先週頭（日）',
        'date(\'Y-m-d 00:00:00\', strtotime(sprintf(\'-%s day\', date(\'w\', $ts) + 7), $ts));',
        date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', $ts) + 7), $ts)),
    ],
    [
        '先週末（土）',
        'date(\'Y-m-d 23:59:59\', strtotime(sprintf(\'+%s day\', 6 - date(\'w\', $ts) - 7), $ts));',
        date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', $ts) - 7), $ts)),
    ],
    [
        '翌週頭（日）',
        'date(\'Y-m-d 00:00:00\', strtotime(sprintf(\'-%s day\', date(\'w\', $ts) - 7), $ts));',
        date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', $ts) - 7), $ts)),
    ],
    [
        '翌週末（土）',
        'date(\'Y-m-d 23:59:59\', strtotime(sprintf(\'+%s day\', 6 - date(\'w\', $ts) + 7), $ts));',
        date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', $ts) + 7), $ts)),
    ],
];

printf('|用途 |コード |結果 |%s', \PHP_EOL);
printf('|----|----|----|%s', \PHP_EOL);
foreach ($message as $row) {
    printf('|%s |%s |%s |%s', $row[0], $row[1], $row[2], \PHP_EOL);
}
```

## オブジェクト指向で実現する場合

```php
<?php

// 基準となるDateTimeオブジェクトの作成
$dateTime = new DateTime($base_datetime);

// 復帰用タイムスタンプ
$ts = $dateTime->getTimestamp();

// 当日開始時
echo $dateTime->format('Y-m-d 00:00:00'), \PHP_EOL;

// 当日終了時
echo $dateTime->format('Y-m-d 23:59:59'), \PHP_EOL;

// 月初
echo $dateTime->format('Y-m-01 00:00:00'), \PHP_EOL;

// 月末
echo $dateTime->format('Y-m-t 00:00:00'), \PHP_EOL;

// 週頭（日）
$dateTime->setTimestamp($ts);
echo $dateTime->sub(new DateInterval(sprintf('P%sD', $dateTime->format('w'))))->format('Y-m-d 00:00:00'), \PHP_EOL;

// 週末（土）
$dateTime->setTimestamp($ts);
echo $dateTime->add(new DateInterval(sprintf('P%sD', 6 - $dateTime->format('w'))))->format('Y-m-d 23:59:59'), \PHP_EOL;

// 週頭（月：月曜スタートの場合）
$dateTime->setTimestamp($ts);
echo $dateTime->sub(new DateInterval(sprintf('P%sD', $dateTime->format('w') - 1)))->format('Y-m-d 00:00:00'), \PHP_EOL;

// 週末（日：月曜スタートの場合）
$dateTime->setTimestamp($ts);
echo $dateTime->add(new DateInterval(sprintf('P%sD', 7 - $dateTime->format('w'))))->format('Y-m-d 23:59:59'), \PHP_EOL;
```

## 手続き型とオブジェクト指向型のパフォーマンス差異

### 単純ループ（オブジェクト指向側不利）

|手続き型 |オブジェクト指向型 |
|----|----|
|0.20067310333252 |0.26106715202332 |

```php
<?php

// 基準日の設定（実際の使用シーンでは$ts = time();や$ts = $_SERVER[\'REQUEST_TIME\'];が使用されるはず）',
$base_datetime = '2016-08-11 13:00:00';

// 1万回回す
$range = 10000;

$mts = microtime(true);
for ($i = 0; $i < $range; $i++) {
    // 基準となるタイムスタンプの作成
    $ts = strtotime($base_datetime);

    // 当日開始時
    date('Y-m-d 00:00:00', $ts);

    // 当日終了時
    date('Y-m-d 23:59:59', $ts);

    // 月初
    date('Y-m-01 00:00:00', $ts);

    // 月末
    date('Y-m-t 23:59:59', $ts);

    // 週頭（日）
    date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', $ts)), $ts));

    // 週末（土）
    date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', $ts)), $ts));
}
echo microtime(true) - $mts, \PHP_EOL;

$mts = microtime(true);
for ($i = 0; $i < $range; $i++) {
    // 基準となるDateTimeオブジェクトの作成
    $dateTime = new DateTime($base_datetime);

    // 復帰用タイムスタンプ
    $ts = $dateTime->getTimestamp();

    // 当日開始時
    $dateTime->format('Y-m-d 00:00:00');

    // 当日終了時
    $dateTime->format('Y-m-d 23:59:59');

    // 月初
    $dateTime->format('Y-m-01 00:00:00');

    // 月末
    $dateTime->format('Y-m-t 00:00:00');

    // 週頭（日）
    $dateTime->setTimestamp($ts);
    $dateTime->sub(new DateInterval(sprintf('P%sD', $dateTime->format('w'))))->format('Y-m-d 00:00:00');

    // 週末（土）
    $dateTime->setTimestamp($ts);
    $dateTime->add(new DateInterval(sprintf('P%sD', 6 - $dateTime->format('w'))))->format('Y-m-d 23:59:59');
}
echo microtime(true) - $mts, \PHP_EOL;

```

### 単純ループ（オブジェクト指向側少しチューン）

DateTimeオブジェクトを使いまわすようにした。

|手続き型 |オブジェクト指向型 |
|----|----|
|0.19268608093262 |0.21537804603577 |

```php
<?php

// 基準日の設定（実際の使用シーンでは$ts = time();や$ts = $_SERVER[\'REQUEST_TIME\'];が使用されるはず）',
$base_datetime = '2016-08-11 13:00:00';

// 1万回回す
$range = 10000;

$mts = microtime(true);
for ($i = 0; $i < $range; $i++) {
    // 基準となるタイムスタンプの作成
    $ts = strtotime($base_datetime);

    // 当日開始時
    date('Y-m-d 00:00:00', $ts);

    // 当日終了時
    date('Y-m-d 23:59:59', $ts);

    // 月初
    date('Y-m-01 00:00:00', $ts);

    // 月末
    date('Y-m-t 23:59:59', $ts);

    // 週頭（日）
    date('Y-m-d 00:00:00', strtotime(sprintf('-%s day', date('w', $ts)), $ts));

    // 週末（土）
    date('Y-m-d 23:59:59', strtotime(sprintf('+%s day', 6 - date('w', $ts)), $ts));
}
echo microtime(true) - $mts, \PHP_EOL;

$mts = microtime(true);

// 基準となるDateTimeオブジェクトの作成
$dateTime = new DateTime($base_datetime);

for ($i = 0; $i < $range; $i++) {
    // ループ対応のため初期化
    $dateTime->setTimestamp($ts);

    // 復帰用タイムスタンプ
    $ts = $dateTime->getTimestamp();

    // 当日開始時
    $dateTime->format('Y-m-d 00:00:00');

    // 当日終了時
    $dateTime->format('Y-m-d 23:59:59');

    // 月初
    $dateTime->format('Y-m-01 00:00:00');

    // 月末
    $dateTime->format('Y-m-t 00:00:00');

    // 週頭（日）
    $dateTime->setTimestamp($ts);
    $dateTime->sub(new DateInterval(sprintf('P%sD', $dateTime->format('w'))))->format('Y-m-d 00:00:00');

    // 週末（土）
    $dateTime->setTimestamp($ts);
    $dateTime->add(new DateInterval(sprintf('P%sD', 6 - $dateTime->format('w'))))->format('Y-m-d 23:59:59');
}
echo microtime(true) - $mts, \PHP_EOL;

```

### 単純ループ（全体的に最適化）

DateTimeオブジェクト、DateIntervalオブジェクトを使いまわすようにした。
その他変数を出来るだけ定数化。

|手続き型 |オブジェクト指向型 |
|----|----|
|0.16357707977295 |0.23438286781311 |

```php
<?php

// 基準日の設定（実際の使用シーンではTIME_STAMP = time();やTIME_STAMP = $_SERVER[\'REQUEST_TIME\'];が使用されるはず）',
define('BASE_DATETIME', '2016-08-11 13:00:00');

// 基準となるタイムスタンプの作成
define('TIME_STAMP', strtotime(BASE_DATETIME));

// フォーマット
define('DATE_FORMAT_DAY_START', 'Y-m-d 00:00:00');
define('DATE_FORMAT_DAY_END',   'Y-m-d 23:59:59');

define('DATE_FORMAT_MONTH_START',   'Y-m-01 00:00:00');
define('DATE_FORMAT_MONTH_END',     'Y-m-t 23:59:59');

// 1万回回す
$range = 10000;

$mts = microtime(true);
for ($i = 0; $i < $range; $i++) {
    // 当日開始時
    date(DATE_FORMAT_DAY_START, TIME_STAMP);

    // 当日終了時
    date(DATE_FORMAT_DAY_END, TIME_STAMP);

    // 月初
    date(DATE_FORMAT_MONTH_START, TIME_STAMP);

    // 月末
    date(DATE_FORMAT_MONTH_END, TIME_STAMP);

    // 週頭（日）
    date(DATE_FORMAT_DAY_START, strtotime(sprintf('-%s day', date('w', TIME_STAMP)), TIME_STAMP));

    // 週末（土）
    date(DATE_FORMAT_DAY_END, strtotime(sprintf('+%s day', 6 - date('w', TIME_STAMP)), TIME_STAMP));
}
echo microtime(true) - $mts, \PHP_EOL;

$mts = microtime(true);

$dateTime = new DateTime(BASE_DATETIME);
$dateInterval = new DateInterval('P0D');

for ($i = 0; $i < $range; $i++) {
    // ループ対応のため初期化
    $dateTime->setTimestamp(TIME_STAMP);

    // 当日開始時
    $dateTime->format(DATE_FORMAT_DAY_START);

    // 当日終了時
    $dateTime->format(DATE_FORMAT_DAY_END);

    // 月初
    $dateTime->format(DATE_FORMAT_MONTH_START);

    // 月末
    $dateTime->format(DATE_FORMAT_MONTH_END);

    // 週頭（日）
    $dateTime->setTimestamp(TIME_STAMP);
    $dateTime->add($dateInterval->createFromDateString(sprintf('-%s day', date('w', TIME_STAMP))))->format(DATE_FORMAT_DAY_START);

    // 週末（土）
    $dateTime->setTimestamp(TIME_STAMP);
    $dateTime->add($dateInterval->createFromDateString(sprintf('+%s day', 6 - date('w', TIME_STAMP))))->format(DATE_FORMAT_DAY_END);
}
echo microtime(true) - $mts, \PHP_EOL;

```