月末締め、請求書の発行日、勤怠の締日、売上の集計期間など、実務では「その月の最終日」を正しく扱う場面が頻繁にあります。
ところがVBAで日付を作るとき、月末だけは意外と落とし穴が多いです。たとえば「2月は28日(うるう年は29日)」「4月は30日」など、月ごとに日数が違うため、固定で「31日」を入れると簡単に壊れます。
この記事では、Excel VBAで月の最終日を“安全に・簡単に”求める方法を、実務での使い方まで含めて整理します。単にコードを出すだけではなく、なぜその書き方が堅牢なのか、運用でどこに注意すべきかも一緒に解説します。
目次
- ✅ VBAで月の最終日を求める前に知っておくべき落とし穴
- ・「31日固定」「月ごとの日数テーブル」は壊れやすい
- ✅ DateSerialで月末を求める基本|「翌月の0日」が最終日
- ・月の最終日を返す関数の作り方(おすすめ)
- ✅ 指定した「年・月」から月末を求める|締め処理で使いやすい形
- ・年・月を指定して月末を返す関数
- ✅ 月末を基準に「当月の期間」を作る|集計・抽出の実務パターン
- ・当月の開始日と終了日を返す(例:配列で返す)
- ✅ DateAddで月末を求める方法と注意点|ズレるケースを理解する
- ・DateAddを使う場合の考え方(参考)
- ✅ 入力チェック込みで“壊れない”月末関数にする|実務の保守性を上げる
- ・年・月入力をチェックして月末を返す(堅牢版)
- ✅ 応用:月末を使った実務シナリオ例(締め処理・フォルダ名・抽出条件)
- ・ファイル名に月末日を入れる例
- ✅ まとめ:VBAで月の最終日を安全に求めるならDateSerial
✅ VBAで月の最終日を求める前に知っておくべき落とし穴
月末は「分かっているつもり」で実装すると、後から静かに事故ります。特に怖いのは、テストが通っても“特定の月だけ”壊れることです。2月やうるう年、30日までの月が混ざった瞬間に処理が止まると、締め処理や請求処理のような重要業務ほど影響が大きくなります。さらに、日付が文字列として扱われていると、見た目は正しくても比較や加算でズレが出ることがあります。ここを理解せずに「とりあえず動いた」実装をすると、翌月・来年・別PCで再現するタイプの不具合になりやすいです。月末を扱うコードは、最初から“壊れにくい形”にしておくのが結局いちばん安いです。
・「31日固定」「月ごとの日数テーブル」は壊れやすい
よくあるNG例は、次のようなものです。
- 「月末=31日」と決め打ちして
DateSerial(2026, 4, 31)のように作る - 月ごとの日数を配列やSelect Caseで管理する
- うるう年だけ別分岐を足す
これらは、追加条件が増えるほど保守がつらくなり、最終的に「どこが正なのか」分からなくなります。
月末はVBAの仕様をうまく使う方が、短く・安全で・読みやすくなります。
✅ DateSerialで月末を求める基本|「翌月の0日」が最終日
月末取得で最もおすすめなのが DateSerial を使う方法です。理由は明確で、月ごとの日数差・うるう年を全部VBA側に任せられるからです。自前で分岐を持たないので壊れにくく、読み手にも意図が伝わりやすいです。ここを押さえずに他の方法に進むと、月末処理が“テクニック”の寄せ集めになりやすいので、まずは王道から固めるのが安全です。月末が必要な処理ほど、後で仕様変更(締日変更、期間変更、複数拠点対応)が入りやすいので、最初に堅牢な型を作っておく価値が大きいです。
・月の最終日を返す関数の作り方(おすすめ)
以下は、指定した日付が属する「月の最終日」を返す関数です。実務ではこの形にしておくと再利用しやすく、別マクロでもすぐ使えます。
Option Explicit
'指定した日付が属する月の最終日を返す
Public Function GetEndOfMonth(ByVal targetDate As Date) As Date
Dim nextMonthFirstDay As Date
'翌月の1日を作ってから「0日」を指定すると当月末になる(DateSerialの仕様)
nextMonthFirstDay = DateSerial(Year(targetDate), Month(targetDate) + 1, 1)
GetEndOfMonth = DateSerial(Year(nextMonthFirstDay), Month(nextMonthFirstDay), 0)
End Function
なぜこの書き方が良いのか(設計意図)
- 日数計算やうるう年判定を自前で持たないため、例外が増えても壊れにくい
DateSerial(年, 月+1, 0)のパターンは「月末を返す」意図が読み手に伝わりやすい- 関数化しておけば、締め処理・請求処理・勤怠処理などに横展開しやすい
別案との違い(メリット差)
- Select Caseで月ごとの日数を管理する案は、仕様追加や修正に弱い
DateAdd("m", 1, ...)で月を進める案も良いが、起点が月末だとズレることがある(後述)- 「最後の日を求める」用途では、DateSerialが最も直球で事故が少ない
実務で気をつけるポイント
targetDateは Date型で受け取る(文字列のまま渡すと環境依存で解釈がズレることがある)- シート上の値が文字列になっている場合は、先にDateへ変換してから渡す
✅ 指定した「年・月」から月末を求める|締め処理で使いやすい形
日付ではなく、年と月(例:2026年2月)だけが入力として与えられるケースも多いです。たとえば「対象年月」をセルに持っておき、月末を基準に集計するような締め処理です。この場合、日付からYear/Monthを抜くより、最初から「年・月」を引数にしておく方が設計が素直になります。後から「対象年月が複数」「締日が月末固定ではない」などの仕様変更が入っても、変更点を局所化できます。月末処理は“業務ルールの中心”になりやすいので、入力の形に合わせた関数を用意しておくのが実務的です。
・年・月を指定して月末を返す関数
Option Explicit
'年・月を指定してその月の最終日を返す
Public Function GetEndOfMonthByYearMonth(ByVal targetYear As Long, ByVal targetMonth As Long) As Date
'翌月の0日 = 当月末
GetEndOfMonthByYearMonth = DateSerial(targetYear, targetMonth + 1, 0)
End Function
なぜこの書き方が良いのか(設計意図)
- 年月指定のため、入力ルールが明確(締め処理で扱いやすい)
- シートの「対象年月」セルと相性が良い(Year/Monthで分解しやすい)
実務で気をつけるポイント
targetMonthに 1〜12 以外が入る可能性があるなら、入力チェックを入れる(後述)
✅ 月末を基準に「当月の期間」を作る|集計・抽出の実務パターン
月末が取れると、次に必要になるのは「当月の開始日〜終了日」を作ることです。ここでよくある失敗が、開始日と終了日のどちらかだけを月末基準で作って、期間がズレることです。期間がズレると、集計の数字が合わないのにエラーにならないという最悪の状態になります。月末は“目に見える境界”なので安心しがちですが、開始日(1日)もセットで設計しておくことが重要です。特に、売上や勤怠のように「1件でも漏れたらNG」なデータは、期間設計を関数として固定化するのが安全です。
・当月の開始日と終了日を返す(例:配列で返す)
Option Explicit
'指定日が属する月の開始日と終了日を返す
Public Function GetMonthRange(ByVal targetDate As Date) As Variant
Dim startOfMonth As Date
Dim endOfMonth As Date
startOfMonth = DateSerial(Year(targetDate), Month(targetDate), 1)
endOfMonth = DateSerial(Year(targetDate), Month(targetDate) + 1, 0)
GetMonthRange = Array(startOfMonth, endOfMonth)
End Function
なぜこの書き方が良いのか
- 月の開始日と終了日を同じルールで作るため、期間ズレが起きにくい
- 集計処理(AutoFilterやSQL、PowerQuery取り込み等)に使い回しやすい
実務で気をつけるポイント
- 呼び出し側で
Array(0)とArray(1)の意味が曖昧になりがちなので、業務規模が大きいならユーザー定義型やクラス化も検討(ただし過剰設計は避ける)
✅ DateAddで月末を求める方法と注意点|ズレるケースを理解する
DateAddを使って「翌月に進めてから引く」という発想もあります。ただし、DateAddは起点の日付によって結果がズレるケースがあり、月末取得だけを目的にするならDateSerialの方が事故が少ないです。ここを知らないと、月末から1か月進めたつもりが「翌月の同日」にならず、日付が丸められる挙動に引っ張られます。もちろんDateAddが悪いわけではなく、用途に応じた使い分けが重要です。月末を確実に取るという目的では、DateSerialが“意図どおりに読める”点でも優位です。
・DateAddを使う場合の考え方(参考)
- 当月1日を作る
- 1か月進めて翌月1日を作る
- 1日引いて月末にする
Option Explicit
Public Function GetEndOfMonth_DateAdd(ByVal targetDate As Date) As Date
Dim firstDay As Date
Dim nextMonthFirstDay As Date
firstDay = DateSerial(Year(targetDate), Month(targetDate), 1)
nextMonthFirstDay = DateAdd("m", 1, firstDay)
GetEndOfMonth_DateAdd = DateAdd("d", -1, nextMonthFirstDay)
End Function
こう書く理由
- DateAddは月末起点だとズレやすいので、必ず当月1日を起点にする
- 月末取得の計算手順が明示され、意図が伝わる
DateAddを使った月末取得は、
考え方を理解していないと日付がズレるケースがあります。
実務で月末処理を安全に行うには、
日付計算の仕様を前提から整理できる方法を選ぶことが重要です。
DateSerial関数を軸に、
日付計算を壊れにくく設計する考え方については、
【VBA】DateSerial関数の完全ガイド|日付計算を安全に行う実務設計 で詳しく解説しています。
✅ 入力チェック込みで“壊れない”月末関数にする|実務の保守性を上げる
実務では、想定外の入力が必ず混ざります。月末関数も例外ではありません。たとえば、対象月が空白、0、13、文字列、エラー値…など、現場のExcelでは普通に起きます。ここで「とりあえずDateSerialに投げる」だと、エラーが出るか、意図しない日付が生成される可能性があります。月末が狂うと締め処理全体が狂うので、入口で弾く設計を入れておくと、後工程の安心感が段違いです。保守性を重視するなら、入力の正当性を関数側で担保し、呼び出し側をシンプルに保つのが実務的です。
・年・月入力をチェックして月末を返す(堅牢版)
Option Explicit
'年・月の妥当性をチェックして月末を返す(不正ならエラーを返す)
Public Function GetEndOfMonthSafe(ByVal targetYear As Long, ByVal targetMonth As Long) As Date
If targetMonth < 1 Or targetMonth > 12 Then
Err.Raise vbObjectError + 1000, "GetEndOfMonthSafe", "月は1~12で指定してください。"
End If
GetEndOfMonthSafe = DateSerial(targetYear, targetMonth + 1, 0)
End Function
なぜこの書き方が良いのか
- 不正入力を早期に検出できる(誤った月末で処理が進む方が危険)
- エラー原因が明確になり、運用・保守が楽になる
実務で気をつけるポイント
- エラーを“止める”のが困る運用なら、エラーではなく「0日」や空白を返す設計にするなど、運用方針に合わせて調整する
✅ 応用:月末を使った実務シナリオ例(締め処理・フォルダ名・抽出条件)
月末が取れるようになると、単に日付を表示するだけでなく、業務の自動化に直結します。たとえば締め処理では、期間の抽出条件に使えますし、請求書や帳票のファイル名にも使えます。さらに、フォルダ名を「2026-02」のように年月単位で切る設計とも相性が良いです。こういう“周辺の設計”まで含めておくと、月末関数は単発の小技ではなく、再利用できる部品になります。逆に、月末をその場で都度計算する作り方にすると、仕様変更時に修正漏れが出やすくなります。
・ファイル名に月末日を入れる例
- 対象日付から月末を取得する
Formatで"yyyymmdd"に整形する- ファイル名に結合する
Option Explicit
Public Sub CreateMonthlyFileNameExample()
Dim targetDate As Date
Dim endOfMonth As Date
Dim fileName As String
targetDate = Date
endOfMonth = GetEndOfMonth(targetDate)
fileName = "請求書_" & Format(endOfMonth, "yyyymmdd") & ".xlsx"
Debug.Print fileName
End Sub
✅ まとめ:VBAで月の最終日を安全に求めるならDateSerial
- 月末は月ごとの日数差・うるう年が絡むため、決め打ち実装は壊れやすい
DateSerial(年, 月+1, 0)を使うと、当月末をシンプルに取得できる- 関数化しておくと、締め処理・集計・保存設計に横展開しやすい
- 実務では不正入力が混ざるため、必要に応じて入力チェックも入れる
- 月末は単発の小技ではなく、業務自動化の“基礎部品”として扱うと保守性が上がる
月末処理は、地味ですが実務では致命傷になりやすい部分です。
最初から壊れにくい形で関数化しておくと、締め処理や帳票作成が一段ラクになります。
今回は、
VBAで月の最終日を安全に求める方法を解説しました。
一方で、実務では
「ここまで作り込むべきか」
「もっとシンプルで十分ではないか」
と迷う場面も少なくありません。
Excel業務改善を進めるうえで、
コスパが悪くなりやすい判断ラインを整理した記事として、
Excel業務改善はどこまでやるべきか?コスパが悪くなる判断ライン
もあわせて参考にしてみてください。