PHP
MySQL
Laravel
YEARWEEK

ISOに頼り切って52週を集計するコピペコード

image.png


「52週間前」って何月何日?

1年の数字の変化を追うのに、12か月だとばっくりし過ぎていて365日だとギザギザすぎ。

そこで「週単位」にすると、曜日の影響もなくなってスムーズ。

52個の数値の変化がグラフにしたとき見た目にちょうどいい。

そこで、52週分の集計をしようと思ったけど、


  • 週単位って、月曜始まり? 日曜始まり?

  • 52週前の月曜日って何日?

  • どうやって計算するの?

そんなこと、もう悩まなくて大丈夫。偉い人が考えていて「国際規格」にまとめてくれていて、各種プログラミング言語でも標準で使えるようになっています。

この記事は、その国際規格に合わせたコードを、PHP + MySQL + Laravel という環境で、コピペで使えるようにまとめたものです。


ISO-8601 による 週番号

そもそも、馴染みがありませんが、1年を52週に割って、1月から番号を振ったものを「週番号」と言うそうです(そう言われればスケジュール帳には週番号が書いてあります)。

その週番号のカウント方法はいろいろあるのですが、いろいろあるとめんどくさいので、ISOがビシッと定めてくれています。

ISO_8601#年と週と曜日 (wikipedia)

ずばり、


  • 月曜始まり

  • 1月4日を含む週が第1週(第1週には4日以上含む)

これを MySQL、PHP、Laravelでどう扱うか…。


MySQL

MySQL YEARWEEK() Function

YEARWEEK関数の「モード3」です。


3 - First day of week is Monday and the first week has more than 3 days


とありますが、要するにこれが ISO-8601 ということ。

こうなります。

SELECT YEARWEEK( created_at, 3 ) AS week


PHP

date には W を与えると週番号になります(単にWをそのまま出力したい場合は \W とエスケープする必要があります)。

strtotime では、年と週番号を W を挟んで 'yyyyWww' という形式で与えます。たとえば '2018W35'

date('Y/m/d'); // 今日が 2018/08/29 だとすると…

date('Y\WW',strtotime("-0 weeks")); // 2018W35(1つ目のWはエスケープしてそのまま出力)
date('Y/m/d D',strtotime("2018W35")); // 2018/08/27 Mon

date('Y\WW',strtotime("-52 weeks")); // 2017W35
date('Y/m/d D',strtotime("2017W35")); // 2017/08/28 Mon

-0 weeks だと今日を含む週で、まだ7日経過していないません。

通常は集計対象から外して、集計期間を -52週~-1週 を対象とします。


Laravel

MyOrdersList という Eloquent モデルがあったとして…

// 集計したい週数

$weeks = 52;
// 開始日時をタイムスタンプにしておく
$start_time = strtotime(date('Y\WW',strtotime("-{$weeks} weeks")));

$result = MyOrdersList::select(
DB::raw('YEARWEEK(created_at,3) AS week'),
DB::raw('sum(*) AS price')
)->whereRaw("created_at >= '".date('Y-m-d 00:00:00',$start_time)."'")
->groupBy('week')
->pluck('price','week')
->toArray();

// 歯抜けなしデータにしながらキーをY-m-d形式に
$filled_result = [];
for( $i=$weeks; $i>=1; $i-- ){
$week = date('YW',strtotime("-$i weeks"));
$date = date('Y-m-d',strtotime("-$i weeks"));
$filled_result[$date] = isset($result[$week]) ? $result[$week] : 0;
}
return $filled_result;


return

=> [

"2017-08-30" => 351,
"2017-09-06" => 0,
"2017-09-13" => 524,
"2017-09-20" => 434,

......

"2018-08-01" => 144,
"2018-08-08" => 264,
"2018-08-15" => 0,
"2018-08-22" => 491,
]



感想

また誰得な記事が出来上がってしまいましたが…。ユーザーが一人でもいればいっか(自分を含めて)、と思うことにしています。

個人的には週始まりは月曜日派ですが、カレンダーは日曜始まりが多いことないですか?(iPhoneのカレンダーアプリも日曜始まり。これは確か言語と地域の設定による)きっとこれが、僕がカレンダーを全然めくらない理由に違いない。