ある案件で、現在日時から納期までの残り日数を土日、祝日を除いて知りたいという要件があって難儀しました。そのメモです。
これを実装するにあたって、以下のサイトのお陰でかなり楽ができたのでまずはご紹介。
Excel一般機能:期間内の指定曜日の数
特定の曜日の日数を数える考え方の参考になりました。
[PHP] Google Calendar API から日本の祝日データを取得 | memo.dogmap.jp
Google Mapの日本の祝日カレンダーから指定期間の祝日を取得する処理を使わせていただきました。
同郷の岡本さんの記事だったことにびっくり!お陰で助かりました!
僕は日付を扱うクラスを作成してメソッドとして実装しましたが、関数としても動かせるので抜き出して公開します。
/** * @param date $start 開始日(Y-m-d) * @param date $end 終了日 (Y-m-d) * @param array $week 休日とカウントする曜日(date('w')の返り値の曜日idを配列で指定) デフォルトは土日 * @param bool $japaneseHolidayFlag 祝日をカウントするかどうか デフォルトはカウントする * * @return int|number */ public function getHolidayCount($start, $end, $week = array(6, 0), $japaneseHolidayFlag = true) { $holidays = 0; // 指定曜日日数を算出 $startTime = strtotime($start); $endTime = strtotime($end); $diffDays = 1 + ($endTime - $startTime) / 86400; $firstWeekday = date('w', $startTime); if (!empty($week)) { foreach ($week as $w) { $cutDay = ($w == 6) ? 0: $w + 1; $adjustDays =1 { $holidays += array_sum($weekdayCount); } } // 日本の祝日日数を算出 if ($japaneseHolidayFlag) { $japaneseHolidays = $this->getHolidays($start, $end, $week); if (!empty($japaneseHolidays)) { $holidays += count($japaneseHolidays); } } return $holidays; } /** * GoogleカレンダーAPIから指定期間の祝日を取得 * * @param date $start 開始日 * @param date $end 終了日 * @param array $denyWeek 取得しない曜日(曜日日数で取得した曜日を指定すると重複を省ける) * * @return array */ public function getHolidays($start, $end, $denyWeek = array()) { $holidays = array(); $holidays_url = sprintf( 'http://www.google.com/calendar/feeds/%s/public/full-noattendees?start-min=%s&start-max=%s&max-results=%d&alt=json' , 'outid3el0qkcrsuf89fltf7a4qbacgt9@import.calendar.google.com' , // 'japanese@holiday.calendar.google.com' , $start , // 取得開始日 $end , // 取得終了日 50 // 最大取得数 ); if ( $results = file_get_contents($holidays_url) ) { $results = json_decode($results, true); if (!empty($results['feed']['entry'])) { foreach ($results['feed']['entry'] as $val ) { $date = $val['gd$when'][0]['startTime']; if (in_array(date('w', strtotime($date)), $denyWeek)) { continue; } $title = $val['title']['$t']; $holidays[$date] = $title; } ksort($holidays); } } return $holidays; }
指定曜日の算出
自分でも理解するのに時間がかかったので説明が難しいのですが、Chiquilin Site Excel一般機能:期間内の指定曜日の数 の
例えば「月曜日の数」を求めようと思ったら 開始日は火曜日の時が 一番計算が楽です。「開始日から5日(6日間)は月曜日がない」ことがはっきりするからです。つまり6日後(期間にして7日)にようやく月曜日がきます。ということは単純に期間を7で割って端数を切るだけで答えが出ます。
というのを参考に、期間開始日と算出する曜日から調整日数を出して、期間日数に加算。その値を7で割って指定曜日ごとの日数を算出しています。
参考サイトのやり方だと、「期間日数から調整日数を引いて7で割り切れる数にして割ってから1を加算する」というやり方をとっているようでしたが、何となく頭がスッキリしなかったので加算方式で、端数を切り捨てる方法をとりました。
祝日日数の算出
ほぼwokamotoさんのコード任せなのですが、そのままのコードだと指定曜日の日数と祝日日数が重複してしまうので、指定曜日の場合は配列に含めないように少し書き加えています。
あと、getHolidaysのメソッドを切り分けて、返り値をカウント数ではなく配列で返しているのはwokamotoさんへのリスペクトはもちろんですが、案件でjavascriptライブラリのFullCalendarを利用しているのでついでにこのカレンダーにも祝日表示しちゃえ的な理由からです。
ブログに書いた理由
当初は現在日時から納期までの差分日数でいいはずだったのに、「やっぱ土日抜いて計算して」とか、「土日抜けるなら祝日も抜いてよ」と簡単に言ってくださるけどその裏側では先人の知恵を授かりつつこんな面倒臭いことをやっているんだよというのを(クライアントにはもちろん告げませんが)この世界にヒッソリとでも刻んでおきたい気持ちになったからです。次からはコピペでいいしね。
- $firstWeekday - $cutDay) < 0) ? 6: $firstWeekday - $cutDay; $weekdayCount[$w] = floor(($diffDays + $adjustDays) / 7); } if (!empty($weekdayCount [↩]