○箇月後の直近の○曜日は?

○箇月後の○曜日の日付を求める自作Function

「1箇月後の直近の木曜日を求める」という作業が発生した。別に、暦を見たらいいんだけれど、もし今後たくさんの日付について同じ作業をする必要が生じたらメンドウなので、ちょこちょこっと「1箇月後の直近の木曜日の日付」を返すFunctionプロシージャを作ってみた。

ただ、「1箇月後の直近の木曜日」にしか対応できないのではツマラナイので、

せめて○箇月後の○曜日ぐらいにはに対応できるようにしておこう

と思い立って、ちょこちょこっと書いてみた。

考え方

VBAには、組み込み定数でvbSunday(正体は"1")~vbSaturday(正体は"7")が定められている。

○箇月後の日付は、元の日付がtargetDateという変数に入っているとすると、

DateSerial(Year(targetDate), Month(targetDate) + ○, Day(targetDate))

で求められる。

で、その返り値(=○箇月後の日付)がtmpDateという変数に入っているとすると、

Weekday(tmpDate)

で曜日を表す整数("1"~"7")が得られる。

あとは、そこから何日ずらせば求めたい曜日にたどり着くかを求め、tmpDateに入っている日付を調整すれば良いだけ。

それぞれの曜日をいくつずらせば求めたい曜日にたどり着くか、というのは規則性のあることなので、一覧表にすれば一般的な計算式が導き出せる。

○箇月後の曜日\目的の曜日 vbSunday(1) vbMonday(2) vbTuesday(3) vbWednesday(4) vbThursday(5) vbFriday(6) vbSaturday(7)
vbSunday(1) +1 +2 +3 -3 -2 -1
vbMonday(2) -1 +1 +2 +3 -3 -2
vbTuesday(3) -2 -1 +1 +2 +3 -3
vbWednesday(4) -3 -2 -1 +1 +2 +3
vbThursday(5) +3 -3 -2 -1 +1 +2
vbFriday(6) +2 +3 -3 -2 -1 +1
vbSaturday(7) +1 +2 +3 -3 -2 -1

とまあ、こんな感じ。

目的の曜日を x 、○箇月後の曜日を y とすると、

  1. x-y>3のとき (x-y)-7
  2. -3≦x-y≦3のとき x-y
  3. x-y<-3のとき (x-y)+7

で、それぞれ目的の曜日にするためにずらす日数が求められる。

コーディング

上記のことを踏まえてコーディングする。

引数は、

  1. 元の日付
  2. 何箇月後か
  3. 直近の何曜日か

の3つ。もちろん、返り値は「○箇月後の○曜日の日付」である。

リスト1 標準モジュール
Option Explicit

Public Function calcAnyWeekdayAfterAnyMonths(ByVal targetDate As Date, _
                                             ByVal months As Integer, _
                                             ByVal weekDayAt As Integer) As Date
  If weekDayAt < 1 Or weekDayAt > 7 _
    Then Err.Raise 10001, "引数weekDayが不正です。"    '……(1)'
  Dim tmpDate As Date
  tmpDate = DateSerial(Year(targetDate), _
                       Month(targetDate) + months, _
                       Day(targetDate))    '……(2)'
  Dim objWeekday As Integer
  objWeekday = Weekday(tmpDate)    '……(3)'
  Dim adjustDateBy As Integer
  adjustDateBy = calcWeekday(weekDayAt, objWeekday)    '……(4)'
  calcAnyWeekdayAfterAnyMonths = tmpDate + adjustDateBy    '……(6)'
End Function

Private Function calcWeekday(ByVal tgtWeekday As Integer, _
                             ByVal objWeekday As Integer) As Integer    '……(5)'
  If (tgtWeekday - objWeekday) > 3 Then calcWeekday = tgtWeekday - objWeekday - 7: Exit Function
  If (tgtWeekday - objWeekday) >= -3 And _
     (tgtWeekday - objWeekday) <= 3 Then calcWeekday = tgtWeekday - objWeekday: Exit Function
  If (tgtWeekday - objWeekday) < -3 Then calcWeekday = tgtWeekday - objWeekday + 7
End Function

(1)の

If weekDayAt < 1 Or weekDayAt > 7 Then Err.Raise 10001, "引数weekDayが不正です。"

では、第3引数を調べ、整数の1~7以外が渡されていたらエラーを吐くようにしている。

(2)の

tmpDate = DateSerial(Year(targetDate), _
                       Month(targetDate) + months, _
                       Day(targetDate))

では、DateSerial関数によって、「months」箇月後の日付を取得。

(3)の

objWeekday = Weekday(tmpDate)

で「months」箇月後の日の曜日を取得。ちなみに、DateSerial関数を使っているので、例えば「1月30日の1箇月後」は、「2月30日」、すなわち平年ならば「3月2日」ということになるので注意。

ここまでで、「months」箇月後の曜日、目的の曜日の2つが判明しているので、(4)の

adjustDateBy = calcWeekday(weekDayAt, objWeekday)

で、これまた自作のcalcWeekday関数に2つの曜日を表す整数を渡して、調整すべき日数を求める。

(5)の

Private Function calcWeekday(ByVal tgtWeekday As Integer, _
                             ByVal objWeekday As Integer) As Integer
  If (tgtWeekday - objWeekday) > 3 Then calcWeekday = tgtWeekday - objWeekday - 7: Exit Function
  If (tgtWeekday - objWeekday) >= -3 And _
     (tgtWeekday - objWeekday) <= 3 Then calcWeekday = tgtWeekday - objWeekday: Exit Function
  If (tgtWeekday - objWeekday) < -3 Then calcWeekday = tgtWeekday - objWeekday + 7
End Function

で調整すべき日数を求める。3つの場合分けで、それぞれ計算方法を分岐。If~ElseIf~Elseで書けるけれど、そうすると読みづらくなるので、ガード節っぽい書き方で3つのIf文を連ねる形にした。

あとは、(6)の

calcAnyWeekdayAfterAnyMonths = tmpDate + adjustDateBy

で日数を調整して、結果を返す。

実行結果

2017年10月3日の1箇月後の直近の木曜日は、

f:id:akashi_keirin:20171003230446j:plain

このとおり。

2017年10月3日の2箇月後の直近の金曜日は、

f:id:akashi_keirin:20171003230453j:plain

このとおり。

2017年10月3日の3箇月後の直近の水曜日は、

f:id:akashi_keirin:20171003230501j:plain

このとおり。

おわりに

まあ、ここまでやったところで、使い道があるかどうかは不明w

車輪の再発明」でないことを祈る。

@akashi_keirin on Twitter

改訂しました

コチラもどうぞ。

akashi-keirin.hatenablog.com