○箇月後の○曜日――改良版
「○箇月後の○曜日」割り出しFunctionの改良
VbDayOfWeek型の引数
Twitterのフォロワー氏、及びid:imihito さんが教えてくださった。
vbSundayとかvbMondayって、単なる組み込み定数だと思っていたけれど、正体はVbDayOfWeek列挙体というやつなのですね。
んで、引数の指定の際にVbDayOfWeek型で宣言しておくと、引数入力時にインテリセンスが効くとか。
具体的には、
Public Function hogehoge(ByVal fuga As VbDayOfWeek) As Integer
こんな風に引数を宣言しておくと、
引数入力時にインテリセンスが働くということ。
こりゃあ便利だ。
自作列挙体型の引数
……ということは、自分で宣言した列挙体でも似たようなことができるのか、と思い、やってみた。
前回のリスト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という列挙体を宣言してある。
そうすると、引数入力時に
やはりこのようにインテリセンスが働く。おお! 便利じゃん!
これで、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箇月後の直近の木曜日」は、
?calcAnyWeekdayAfterAnyMonths("2017/10/3",2,vbthursday,modeJustAfter)
「2017年10月3日の2箇月後の直後の木曜日」は、
?calcAnyWeekdayAfterAnyMonths("2017/10/3",2,vbthursday,modeLast)
「2017年10月3日の2箇月後の直前の木曜日」は、
たぶん、バッチリです。
おわりに
今回は、結構勉強になったなあ。
列挙体型の引数なんて、これから結構使えそうだ。