指定した期間内の特定の曜日をカウントする方法【JavaScript例】

標題の通り.考え方と実装例をメモしておきます. 「5月10日〜7月20日の間の水曜日の数」というような感じで値を得る方法です.

実装

擬似コード

切り捨て((期間の日数+(6+開始曜日-指定曜日)%7)/7);

例えば JavaScript では以下のような感じ.

const numOfDay = 10;     // 期間の日数=>10日
const beginDay = 3;     // 期間の開始日=>水曜日
const targetDay = 5;     // カウントする曜日=>金曜日

result = Math.floor((numOfDay + (6 + beginDay - targetDay) % 7) / 7);     // => 2

考え方

パッと最初に思いつきそうな方法として,期間(開始日から終了日まで)の日数を求めて,それを7で割るというものがあります.しかし,考えてみるとこの方法ではうまくいかないことがわかります.例えば,期間が8日だったとして,7で割ると1余ることになりますが,この余った1日分がカウントしたい特定の曜日なのかどうかを調べなければいけません.一方で,ある方法を採ると,単純に期間を7で割るだけで曜日をカウントできるようになります.その方法の考え方は,以下に書かれている「その1」と「その2」が参考になりました.

Excel一般機能:期間内の指定曜日の数

要は,期間の開始日を意図的にずらすことで,日数を7で割ったときの余りの扱いを簡単にするということです.上の記事のように開始日をずらせば,余った日が指定曜日になることがないため,余りは無視(切り捨て)すれば良くなります(6日前までなら開始日をずらしてもカウントに影響はない).

この方法で重要なのは「開始日の前の週の指定曜日の翌日」に開始日をずらす(戻す)ことです.こうすることで,7で割った商がカウント結果になります.となると,どうやってその開始日までもどすのか,という話になりますが,ここでは開始曜日(期間が始まる曜日)と指定曜日(カウントしたい曜日)が重要になります.

例えば,開始曜日が(火)で指定曜日も(火)だった場合,6日後ろにずらせば,「開始日の前の週の指定曜日の翌日」である前週の(水)に開始日をセットできます.次に指定曜日が開始曜日の1つ先である(水)だったとすると,1日分ずらす曜日が減って,5日後ろにずらすことになります.同じように指定曜日が(木)だと,2日分ずらす曜日が減って,4日ずらします.逆に開始曜日の1つ前である(月)が指定曜日だと,ずらす必要がなくなります.また,(日)が開始曜日だと,1日だけずらすことになります.

これを観察してみると,6日分戻すことをベースとして考えて,開始曜日と指定曜日の差でそこに調整を加えればよさそうです.差が開始曜日から後ろ方向に広がっていればずらす日数をその差分だけ減らし,前方向に広がっていればその差分だけプラスして,余剰をとります.これで,開始日から「何日前にずらせばいいか」を求められます.

で,日数を前にずらすというのは,期間をその分長くしているとも捉えられます.なのであとは,もともとの期間日数に上で求めた値を足してあげて,それを7で割った結果を切り捨てすれば完了です.

ずらす日数 = (6 + 開始曜日 - 指定曜日) % 7;
期間日数 = 期間日数 + ずらす日数;
指定曜日の数 = 切り捨て(期間日数/7);

つまり冒頭で記したように,

切り捨て((期間の日数+(6+開始曜日-指定曜日)%7)/7);

のようになります.