○箇月後の○曜日――改良版

「○箇月後の○曜日」割り出しFunctionの改良

akashi-keirin.hatenablog.com

VbDayOfWeek型の引数

Twitterのフォロワー氏、及びid:imihito さんが教えてくださった。

vbSundayとかvbMondayって、単なる組み込み定数だと思っていたけれど、正体はVbDayOfWeek列挙体というやつなのですね。

んで、引数の指定の際にVbDayOfWeek型で宣言しておくと、引数入力時にインテリセンスが効くとか。

具体的には、

Public Function hogehoge(ByVal fuga As VbDayOfWeek) As Integer

こんな風に引数を宣言しておくと、

f:id:akashi_keirin:20171007213126j:plain

引数入力時にインテリセンスが働くということ。

こりゃあ便利だ。

自作列挙体型の引数

……ということは、自分で宣言した列挙体でも似たようなことができるのか、と思い、やってみた。

前回のリスト1に出てきたFunction、「calcAnyWeekdayAfterAnyMonths」に引数を一つ追加する。

追加前
Public Function calcAnyWeekdayAfterAnyMonths( _
                  ByVal targetDate As Date, _
                  ByVal months As Integer, _
                  ByVal weekDayAt As Integer) As Date
追加後
Public Function calcAnyWeekdayAfterAnyMonths( _
                  ByVal targetDate As Date, _
                  ByVal months As Integer, _
                  ByVal weekDayAt As VbDayOfWeek, _
                  ByVal mode As ModeSwitch) As Date

第4引数として mode を追加。「ModeSwitch型」という見慣れない型になっているが、これは宣言セクションで

Public Enum ModeSwitch
  modeMostRecent = 0
  modeLast
  modeJustAfter
End Enum

と、このようにModeSwitchという列挙体を宣言してある。

そうすると、引数入力時に

f:id:akashi_keirin:20171007213136j:plain
やはりこのようにインテリセンスが働く。おお! 便利じゃん!

これで、3つのモード、すなわち、

  • ○箇月後の直近の○曜日
  • ○箇月後の直前の○曜日
  • ○箇月後の直後の○曜日

を算出するよう切り替えられるようにすることができる。

DateAdd関数の使用

Twitterのフォロワー氏からのもう一つのご指摘。

1月30日の1箇月後が2月30日、すなわち3月2日になるのではマズい

とのこと。DateAdd関数使やいーじゃん、ということなので、調べてみた。

コチラによると、

構文
DateAdd(Interval,Num,Date)
引数Intervalには、追加する日付や時間の間隔を指定します。
引数Numには、Dateに対して増加させる日付や時間を指定します。
引数Dateには、Numを増加させる元になる日付や時間を指定します。
解説
DateAdd関数は、任意の日付や時間に特定の間隔を追加してその結果を返します。 引数Intervalに指定できる間隔は次のとおりです。
設定値 内容
yyyy
q 四半期
m
y 年間通算日
d
w 週日
ww
h
m
s

とのこと。

例えば、「2017年10月7日の1箇月後の日付」なら、

DateAdd("m", 1, "2017/10/7")

とすれば良い。

Select Case構文の使用

id:imihito さんからのもう一つのご指摘。

あとこちらは私の好みになりますが、calcWeekday 内の処理のように 一つの値の範囲で分岐する処理は Select Caseを使うと見やすくなると思います。
Select Case tgtWeekday - objWeekday
Case Is > 3
calcWeekday = tgtWeekday - objWeekday - 7
Case -3 To 3
calcWeekday = tgtWeekday - objWeekday
Case Is < -3
calcWeekday = tgtWeekday - objWeekday + 7
End Select

ははは。おっしゃる通り。なんでこんな簡単なことに気づかずにうれしそうにIf文を連ねていたんだろ。

あと、「-3≦tgtWeekday - objWeekday≦3」を

Case -3 To 3

と書きゃいいってのも、恥ずかしながら初めて知りました。うん、こりゃ簡単だ。

コードの書き換え

以上のことを踏まえて、コードを書き換えた。

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

Public Enum ModeSwitch    '……(1)'
  modeMostRecent = 0
  modeLast
  modeJustAfter
End Enum

Public Function calcAnyWeekdayAfterAnyMonths( _
                  ByVal targetDate As Date, _
                  ByVal months As Integer, _
                  ByVal weekDayAt As VbDayOfWeek, _
                  ByVal mode As ModeSwitch) As Date    '……(2)'
  If weekDayAt < 1 Or weekDayAt > 7 _
    Then Err.Raise 10001, "引数weekDayAtが不正です。"
  If mode < 0 Or mode > 2 _
    Then Err.Raise 10002, "引数modeが不正です。"
  Dim tmpDate As Date
  tmpDate = DateAdd("m", months, targetDate)    '……(3)'
  Dim objWeekday As Integer
  objWeekday = Weekday(tmpDate)
  Dim adjustDateBy As Integer
  adjustDateBy = calcWeekday(mode, weekDayAt, objWeekday)
  calcAnyWeekdayAfterAnyMonths = tmpDate + adjustDateBy
End Function

Public Function hogehoge(ByVal fuga As VbDayOfWeek) As Integer
  hogehoge = fuga
End Function

Private Function calcWeekday(ByVal mode As ModeSwitch, _
                             ByVal tgtWeekday As Integer, _
                             ByVal objWeekday As Integer) As Integer
  If mode < 0 Or mode > 2 _
    Then Err.Raise 10002, "引数modeが不正です。"
  Dim tmp As Integer
  tmp = tgtWeekday - objWeekday
  Select Case mode    '……(4)'
    Case modeMostRecent    '……(5)'
    '直近の○曜日'
      Select Case tmp
        Case Is > 3
          calcWeekday = tmp - 7
        Case -3 To 3
          calcWeekday = tmp
        Case Is < 3
          calcWeekday = tmp + 7
      End Select
    Case modeLast    '……(6)'
    '直前の○曜日'
      If tmp = 0 Then calcWeekday = tmp: Exit Function
      If tmp > 0 Then calcWeekday = tmp - 7: Exit Function
      If tmp < 0 Then calcWeekday = (tmp * -1) - 7: Exit Function
    Case modeJustAfter    '……(7)'
    '直後の○曜日'
      If tmp >= 0 Then calcWeekday = tmp: Exit Function
      If tmp < 0 Then calcWeekday = tmp + 7: Exit Function
  End Select
End Function

(1)からの5行

Public Enum ModeSwitch
  modeMostRecent = 0
  modeLast
  modeJustAfter
End Enum

では、先述の通り、モード切替スイッチ用の引数のために列挙体を宣言。

(2)の

Public Function calcAnyWeekdayAfterAnyMonths( _
                  ByVal targetDate As Date, _
                  ByVal months As Integer, _
                  ByVal weekDayAt As VbDayOfWeek, _
                  ByVal mode As ModeSwitch) As Date

も先述の通り。第3引数weekDayAtをVbDayOfWeek型、第4引数modeをModeSwitch型にしたことで、使用時の引数入力を省力化している。

(3)の

tmpDate = DateAdd("m", months, targetDate)

で、引数targetDateで渡された日付のmonth箇月後の日付を変数tmpに格納。

(4)からの20行

Select Case mode    '……(4)'
  Case modeMostRecent
     ・
     ・
     ・
  Case modeLast
     ・
     ・
     ・
  Case modeJustAfter
     ・
     ・
     ・
End Select

では、まず引数modeの値によって処理を3通りに分岐。それぞれの処理の中身は、まず(5)の

Case modeMostRecent
  '直近の○曜日'
  Select Case tmp
    Case Is > 3
      calcWeekday = tmp - 7
    Case -3 To 3
      calcWeekday = tmp
    Case Is < 3
      calcWeekday = tmp + 7
  End Select

は、直近の○曜日に補正するための計算。変数tmpにはcalcWeekDayプロシージャの入口のところで、「tgtWeekday - objWeekday」を代入している。

(6)の

Case modeLast    '……(6)'
  '直前の○曜日'
  If tmp = 0 Then calcWeekday = tmp: Exit Function
  If tmp > 0 Then calcWeekday = tmp - 7: Exit Function
  If tmp < 0 Then calcWeekday = (tmp * -1) - 7: Exit Function

は、直前の○曜日に補正するための値の算出。

(7)の

Case modeJustAfter    '……(7)'
  '直後の○曜日'
  If tmp >= 0 Then calcWeekday = tmp: Exit Function
  If tmp < 0 Then calcWeekday = tmp + 7: Exit Function

は直後の○曜日に補正するための値の算出。

実行

イミディエイト・ウインドウに次のコードを入力して実験。

まずは、

?calcAnyWeekdayAfterAnyMonths("2017/10/3",2,vbthursday,modeMostRecent)

「2017年10月3日の2箇月後の直近の木曜日」は、

f:id:akashi_keirin:20171007213206j:plain

?calcAnyWeekdayAfterAnyMonths("2017/10/3",2,vbthursday,modeJustAfter)

「2017年10月3日の2箇月後の直後の木曜日」は、

f:id:akashi_keirin:20171007213218j:plain

?calcAnyWeekdayAfterAnyMonths("2017/10/3",2,vbthursday,modeLast)

「2017年10月3日の2箇月後の直前の木曜日」は、

f:id:akashi_keirin:20171007213228j:plain

たぶん、バッチリです。

おわりに

今回は、結構勉強になったなあ。

列挙体型の引数なんて、これから結構使えそうだ。

@akashi_keirin on Twitter