[PHP]第53週の週番号が「01」になる件に対応する関数を書いた

※下書き段階で投稿してしまったので、再投稿

PHPのdate(‘W’)が返す週番号は、第53週では’01’を返します。strftime(‘%W’)も同様です。

  <?php
echo date('W', strtotime('2013-12-31')), PHP_EOL; // '01'

この仕様は、ISO規格に基づいています。

http://ja.wikipedia.org/wiki/ISO_8601#.E5.B9.B4.E3.81.A8.E9.80.B1.E3.81.A8.E6.9B.9C.E6.97.A5

以下、上記Wikipedia記事より引用

年末において以下の曜日に該当する場合、その日は当年最終週の曜日としてでは無く、翌年第1週の曜日として扱うものとされている。
12月29日が月曜日の場合。
12月30日が月曜日または火曜日の場合。
12月31日が月曜日・火曜日・水曜日のいずれかの場合。

この挙動は、PHPのバグではありません。しかし、PHPアプリケーションにおいてバグを作りこみがちな挙動の1つだと思います(実際、私も2014年最初に修正したバグは、この挙動に起因するバグでした)。

以下は、上記条件の場合には、’01’ではなく’53’を返す関数です。

<php
/**
* date('W')は、以下の場合に週番号として'01'を返す。
* (1) 12月29日が月曜日の場合。
* (2) 12月30日が月曜日または火曜日の場合。
* (3) 12月31日が月曜日・火曜日・水曜日のいずれかの場合。
*
* この関数では、12/29~31で、週番号が'01'となる場合には、'53'を返す。
*
* @param $time UNIXタイムのタイムスタンプ
* @return string
*/
function getWeekNumber($time)
{
$week_number     = date('W', $time); // 週番号(01から52)
$month_and_day   = date('m-d', $time); // 月-日
$day_of_the_week = date('w', $time); // 0 (日曜)から 6 (土曜)
// 処理方法(1): 仕様通りに月日と曜日で判定
if ($month_and_day === '12-29' && in_array($day_of_the_week, array(1))) {
$week_number = '53';
}
if ($month_and_day === '12-30' && in_array($day_of_the_week, array(1, 2))) {
$week_number = '53';
}
if ($month_and_day === '12-31' && in_array($day_of_the_week, array(1, 2, 3))) {
$week_number = '53';
}
// 処理方法(2): 月日+date('W')の値で判定(こちらでも実用上は問題ないかと…)
// if (in_array(date('m-d', $time), array('12-29', '12-30', '12-31')) && date('W', $time) === '01') {
//    $week_number = '53';
// }
return $week_number;
}

「53」を返す処理については、仕様通りに「月日」+「曜日」で判定を行っています。ただ、この関数が必要な場合というのは、「01」が返されて困る場合に「53」を返すことだと思います。したがって、処理方法(2)でも、実用上は問題無いでしょう。

以下は、テスト付きのバージョンです。phpコマンドで実行して、結果が「ok」ならテストに合格したことになります。また、TAPに基づいたテストなので、proveコマンドでもテストできます(proveで実行するため、1行目にシバンを付けています)。

#!/usr/bin/env php
<?php
test(9);
is(getWeekNumber(strtotime('2014-12-29')), '53', 1, '2014年12月29日(月) => 53');
is(getWeekNumber(strtotime('2014-12-30')), '53', 2, '2014年12月30日(火) => 53');
is(getWeekNumber(strtotime('2014-12-31')), '53', 3, '2014年12月31日(水) => 53');
is(getWeekNumber(strtotime('2013-12-30')), '53', 4, '2013年12月30日(月) => 53');
is(getWeekNumber(strtotime('2013-12-31')), '53', 5, '2013年12月31日(火) => 53');
is(getWeekNumber(strtotime('2012-12-31')), '53', 6, '2012年12月31日(月) => 53');
is(getWeekNumber(strtotime('2011-12-29')), '52', 7, '2011年12月29日(木) => 52');
is(getWeekNumber(strtotime('2011-12-30')), '52', 8, '2011年12月30日(金) => 52');
is(getWeekNumber(strtotime('2011-12-31')), '52', 9, '2011年12月31日(土) => 52');
/**
* @param int $number_of_tests
*/
function test($number_of_tests = 0)
{
if ($number_of_tests > 0) {
echo '1..', $number_of_tests, PHP_EOL;
}
}
/**
* @param        $got
* @param        $expected
* @param int    $test_number
* @param string $description テストの説明
* @param string $directive   「TODO」又は「SKIP」+ TODO/SKIPである理由
*/
function is($got, $expected, $test_number, $description = '', $directive = '')
{
if ($expected === $got) {
echo 'ok ', $test_number;
} else {
echo 'not ok ', $test_number;
}
if ($description !== '') {
echo ' - ', $description;
}
if ($directive !== '') {
echo ' # ' . $directive;
}
echo PHP_EOL;
if ($expected !== $got) {
echo '# got:      ', $got, PHP_EOL;
echo '# expected: ', $expected, PHP_EOL;
}
}
/**
* date('W')は、以下の場合に週番号として'01'を返す。
* (1) 12月29日が月曜日の場合。
* (2) 12月30日が月曜日または火曜日の場合。
* (3) 12月31日が月曜日・火曜日・水曜日のいずれかの場合。
*
* この関数では、12/29~31で、週番号が'01'となる場合には、'53'を返す。
*
* @param $time UNIXタイムのタイムスタンプ
* @return string
*/
function getWeekNumber($time)
{
$week_number     = date('W', $time); // 週番号(01から52)
$month_and_day   = date('m-d', $time); // 月-日
$day_of_the_week = date('w', $time); // 0 (日曜)から 6 (土曜)
// 処理方法(1): 仕様通りに月日と曜日で判定
if ($month_and_day === '12-29' && in_array($day_of_the_week, array(1))) {
$week_number = '53';
}
if ($month_and_day === '12-30' && in_array($day_of_the_week, array(1, 2))) {
$week_number = '53';
}
if ($month_and_day === '12-31' && in_array($day_of_the_week, array(1, 2, 3))) {
$week_number = '53';
}
//処理方法(2): 月日+date('W')の値で判定(こちらでも実用上は問題ないかと…)
// if (in_array(date('m-d', $time), array('12-29', '12-30', '12-31')) && date('W', $time) === '01') {
//    $week_number = '53';
// }
return $week_number;
}

コメントを残す

コメントを残す