和風スパゲティのレシピ

日本語でコーディングするExcelVBA

「年度」を使って日付を求めるDateSerial関数

日本企業でエクセルを使う場合、「年度」で日付を管理したい場面が非常に多いです。
ですが、残念ながらタイトルの関数はデフォルトでは用意されていません。

と言うことで作りましょう。

ソースコード

Function DateSerial年度版(ByVal 年度y As Long, ByVal m As Long, ByVal d As Long) As Date
    
    ' 1~3月はyを1増やす
    If m <= 3 Then
        DateSerial年度版 = DateSerial(年度y + 1, m, d)
    Else
        DateSerial年度版 = DateSerial(年度y, m, d)
    End If
    
    ' m,dがカレンダーの日付にない組ならば、エラーとする
    If Month(DateSerial年度版) <> m Or Day(DateSerial年度版) <> d Then
        Call Err.Raise(1000, , "年月日の指定がカレンダーにない日付です。")
    End If
    
End Function

' 実行例
?DateSerial年度版(2019, 1, 1) ' = 2020/01/01

使い方

「月」と「日」のセルが別々になっているデータに対し、
日付の取得を1行にまとめるときなどに、よく使用します。

For R = 2 To 100

    売上日 = DateSerial年度版(2019, Cells(R, CNo売上データ.) , Cells(R, CNo売上データ.))

Next

このように、「2019.4.1~2020.3.31」のデータを1行で表現できます。

コードの解説

メインの処理は、ほとんど解説不要ですね。
1~3月のときに、Yearに1を足して、DateSerialを使っているだけです。
※ エラー処理については後述するので読み飛ばしてOKです。


関数名がちょっと不自然な名前ですが、無理やり「DateSerial」から始めているのは、→がやりたいからです。

detes まで打ってCtrl + Spaceで、どっちを使うか選べます。



作りは非常に単純ですが、とても便利な関数です。

簡単な計算なので、計算を楽にしてくれるような利便性はないですが、
真価を発揮するのは「コードの可読性(読みやすさ)」です。


年度の調整をメインのSubプロシージャ内に書こうとすると、大した処理じゃないのに5行くらいになったり、yとy+1の違いだけで同じコードを複製するはめになったりします。

日付なんて、数ある項目のひとつでしかないのに、その部分のコードが肥大してしまうと、本当にやりたい処理が、どうでもいい処理に埋もれてしまいます。

こういった、「たいして意味はないが、システム上必要なIf文」を関数に隠し、
「メインコードを意味のある処理だけに保つ」ことが、関数の大きな役割です。

「年度」関連の処理は、こういった邪魔なIfがとても多いので、積極的に関数にしていきましょう。
 ・関連関数:日付から「年度」を求める関数
 ・関連関数:4月から3月を連番に変換してコードを整理する

DateSerialのちょっと特殊な仕様

本家DateSerialには、少し特殊な仕様があります。

DateSerial(2020, 1, 32) ' = 2020/02/01

このように、日付に32を代入すると、月が繰り上がります。
同じく月に13でも、念が繰り上がります。

DateSerial(2020, 1, 0) ' = 2019/12/31
DateSerial(2020, -1, 1) ' = 2019/11/01

逆に日付に0や、さらには-1を入れると、月や年が繰り下がったりもします。


この仕様、本記事の「年度版」ではどうしましょう?
「If m <= 3 Then」と書いていますが、dが32だと、(2019, 3,32)は、4月です。

「If m <= DateSerial(年度y, m, d) Then」 と書き替えた方がいいでしょうか?
しかし、これはこれで、「うるう年」の問題に引っ掛かります。
ってことはまたIfを使って…


いやちょっと待ってください。

本家DateSerialがこの仕様で大丈夫なのは、「連続関数」だからです。
年度版は連続関数ではありませんから、足し算は想定していません。

そもそも(2019, 3,32) に、2019と2020の、どっちの4月1日を返すべきかなんて、
それこそ関数の製作者の決めの問題でしかありません。


原点に立ち返ってこの関数の目的を考えましょう。

○○年度の○月○日が欲しい」でした。

じゃあ、○に「0」やら「32」やらは、そもそもおかしい話ですよね?
mやdには、変な数字は入れられないようにしましょう。

ということで、mとdの組がカレンダーにない場合は、
エラーで止める関数にしました。

「カレンダーに存在する年月日の組かどうか」は、
「最終的に出来上がった日付のmとdが、元のmとdから変わっていないか」
で判定しています。


ちなみに、正しい日付かどうかは、
「DateValeu(y/m/d)がエラーかどうか」を調べる方が楽です。

こっちは↑の仕様を持つDateSerialと違い、
DateValue("2020/3/32")がエラーとなります。

ただし、今回のように「年の値が条件で切り替わる」場合は、
うるう年の判定をするために、完成後にm,dを判定しなければいけませんので気をつけましょう。