🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA の DateAdd・DateDiff — 月の長さバグのない日付計算

|

Excel VBA の DateAdd・DateDiff — 月の長さバグのない日付計算

TL;DR — 日付は Double なので、myDate + 1 は明日、myDate - 7 は先週です — 素の足し算は 日数なら正しい。月と年では破綻します。ひと月は 30 日ではなく、1 年は 365 日ではないからです。 DateAdd("m", 1, d) はカレンダーを知っています。DateDiff("d", a, b) は丸ごとの日数を数え、 DateDiff("yyyy", a, b) は経過時間ではなく越えた年の境界の数を数えます — だから 12/31 → 1/1 が 「1 年」になるのです。

Sub DateMathDemo()
    Debug.Print #2026-01-31# + 1                       ' 2026-02-01  — 日数:素の + で足りる
    Debug.Print DateAdd("m", 1, #2026-01-31#)          ' 2026-02-28  — 月を理解し、丸め込む
    Debug.Print DateDiff("d", #2026-01-01#, #2026-03-01#) ' 59       — 2 つの日付の間の丸ごとの日数
    Debug.Print DateSerial(2026, 3, 0)                 ' 2026-02-28  — 2 月の最終日
End Sub

VBA の日付計算は、たった一つの事実に立っています。日付は日数を数える Double だ、と。だからの 計算は自明です — + 1- 7Date + 30 はどれもそのまま動きます。1 日はちょうど 1 だからです。 厄介ごとが始まるのは、同じ手が月や年にもそのまま通じると思い込んだときです。月や年には一定の 長さがありません。DateAddDateDiffDateSerial は、まさに素の +- にはできない カレンダー依存の部分を担うために存在します。

この記事で学べること

  • 考え方の軸 — 素の +/-日数なら正しく、月と年では誤り
  • DateAdd(interval, number, date) と、噛みついてくるインターバルコード("m""n""y""yyyy"
  • DateAdd("m", 1, 1月31日) が 2 月 28 日を返す理由 — 素の + では得られない月末の知恵
  • DateDiff は経過時間ではなく越えた境界を数える — 1 日から生まれる「1 年」
  • DateSerial は地域差なく部品から日付を組み立てる、そして月末のイディオム
  • 素の足し算でよい場面と、関数が必須の場面

考え方の軸:素の計算でよいのは日数だけ

日付は日数の Double なので、日の計算には関数がまったく要りません。

tomorrow = Date + 1
lastWeek = Date - 7
Debug.Print #2026-02-28# + 1        ' 2026-03-01 — VBA が月をまたいでくれる

これは正真正銘に正しい — 丸ごとの日を足すとき、VBA は月末もうるう年も処理してくれます。ただ シリアル値に足しているだけだからです。破綻するのは、ひと月や 1 年を「日数」だと考えたときです。

nextMonth = Date + 30      ' 誤り — 30 日は「ひと月」ではない
nextYear  = Date + 365     ' 誤り — うるう日を取りこぼし、毎年ずれていく

+ 30 は月によって着地する日が変わり、+ 365 は 2 月 29 日を含むどの期間でも 1 日ずれます。単位が になった瞬間、素の計算はバグであり、DateAdd が要ります。

DateAdd と、噛みついてくるインターバルコード

DateAdd(interval, number, date)dateinterval 単位を number 個足します。interval文字列コードで、2 つの衝突がほとんどの混乱を生みます。

コード 単位 注意
"yyyy" "y" は年ではない — 年内通算日を意味する
"q" 四半期
"m" "m" は月。分は "n"
"ww"
"d" "y""w" も日として振る舞う
"h"
"n" "m" ではない
"s"
DateAdd("m", 15, startTime)   ' 15 か月を足す — たぶん意図と違う
DateAdd("n", 15, startTime)   ' 15 分を足す — 本当にほしかった "n"

"m""n" の取り違えが目玉のバグです。「minute(分)」のつもりで "m" に手を伸ばすと、 タイムスタンプに 15 か月を無言で足してしまいます。コードを一度声に出しましょう — m は月、n は 分、yyyy は年。そうすればこの一族はもう間違えやすくなくなります。number は負にすれば減算になり、 結果はつねに正しい日付です。

DateAdd で 1 月 31 日に 1 か月足すと 2 月 28 日が返る理由

これは素の計算が決して与えてくれない見返りです。1 月 31 日に 1 か月足しても、着地すべき「2 月 31 日」は 存在しないので、DateAdd対象月の有効な最終日に丸め込みます

DateAdd("m", 1, #2026-01-31#)   ' 2026-02-28  (2028 年なら 2028-02-29)
DateAdd("m", 1, #2026-03-31#)   ' 2026-04-30  (4 月は 30 日まで)
DateAdd("yyyy", 1, #2028-02-29#) ' 2029-02-28 (翌年にうるう日はない)

DateAdd は各月の日数を知り、うるう年も自動で調整します — それはまさに + 30 が捨ててしまう知識 です。期限日、更新日、「翌月の同じ日」を計算するなら、この丸め込みこそがほしい挙動であり、月の計算を 手作りしてはいけない理由です。

DateDiff は経過時間ではなく越えた境界を数える

DateDiff(interval, date1, date2) は「2 つの日付の間の時間」に見え、"d"(日)ではそのとおりです。 しかし "m""yyyy""q""ww" では、もっと微妙で意外な意味になります。2 つの日付の間に 何個のインターバル境界があるかであって、実際にどれだけ時間が経ったかではありません。

DateDiff("yyyy", #2025-12-31#, #2026-01-01#)  ' 1  — 年越しの境界は 1 つ、でも 1 日差
DateDiff("m",    #2026-01-31#, #2026-02-01#)  ' 1  — 月の境界 1 つ、1 日差
DateDiff("d",    #2026-01-31#, #2026-02-01#)  ' 1  — 実際に 1 日(これは文字どおり)

つまり DateDiff("yyyy", …) は暦年が変わった回数の数え上げであって、年齢でもなければ経過年数でもありません。本当の経過数を得るには、境界の差を計算してから調整します — 年齢なら、 今年の誕生日がまだ来ていなければ 1 を引きます。

age = DateDiff("yyyy", dob, Date)
If DateSerial(Year(Date), Month(dob), Day(dob)) > Date Then age = age - 1

DateDiffdate1date2 より後だとの数も返すので、順序の判定に便利です。ルールは こうです。丸ごとの日数には "d" を気軽に使い、"m"/"yyyy"/"q" は境界の数え上げとして扱い、 結果を期間と呼ぶ前に調整すること。

DateSerial は地域差なく部品から日付を組み立てる

年・月・日が別々の数値であるときは、それらを文字列に貼り合わせて CDate が 正しい順で読んでくれることを当てにしないでください — それはマシンの地域設定に依存し、国境を 越えると日と月を無言で入れ替えます。DateSerial(year, month, day) は、テキストもあいまいさもなく、 日付を直接組み立てます。

d = DateSerial(2026, 2, 1)     ' どのマシンでもつねに 2026 年 2 月 1 日

さらに良いことに、DateSerialあふれた値を正規化します。月 13 は翌年に繰り上がり、日 0 は 前月の最終日になります。ここから、日付コードで最も役立つ 2 つのイディオムが生まれます。

lastDayOfMonth = DateSerial(y, m + 1, 0)   ' 日 0 = m 月の最終日
firstOfNextQ   = DateSerial(y, m + 3, 1)   ' 3 か月先の 1 日

DateSerial(y, m + 1, 0) は「今月の最終日」の定番形です — 月の日数の対応表も、うるう年の特別扱いも 要りません。部品取り出しのガイドYearMonthDay と組み合わせれば、 ある日付を基準にどんな日付でも組み立てられます。

率直な結論:日は手計算、それ以外は関数で

境界線ははっきりしていて、その正しい側にとどまるだけで、日付計算のバグはほとんど消えます。

  • 日数 → 素の計算date + ndate - n。正しくて速い。月やうるう年は VBA が繰り上げて くれます。
  • 月/年 → DateAdd+ 30+ 365 は禁物。DateAdd("m", …) は実際の月の日数に丸め込み ます。"m" は月、"n" は分を忘れずに。
  • 差 → DateDiff、ただし境界として読む"d" は文字どおりの日数、"m"/"yyyy" は境界の 越えた回数なので、期間と呼ぶ前に調整します。
  • 部品から組み立て → DateSerial → 地域差がなく、DateSerial(y, m + 1, 0) が月末のイディオム です。

覚えておくべき一文はこれです。+ 演算子はカレンダーを知らず、DateDiff は時間を測らない。それぞれが 正しく扱える、ただ一つのことに使いましょう。

ExcelMaster の活用

高くつく日付計算のバグは、もっともらしく見えるものです。+ 30 で計算して月の長さの間でずれる 更新日、DateDiff("yyyy", …) で出した「年齢」が誕生日のあたりで 1 年ずれる、文字列から組み立てて 同僚の地域設定で日と月が入れ替わる期限日。どれも軽いテストでは正しく見え、端のケースで失敗します。

ExcelMaster は、カレンダーに 正しい版を書きます。計算を説明すれば — 「翌月の最初の営業日」、「45 日後が期限の請求」、「2 つの 日付の間の月数」 — 日数には素の計算を、月と年には正しいインターバルコードの DateAdd を、部品 からの組み立てには地域差のない DateSerial を選び、DateDiff の境界カウントを本物の期間へ調整 します。あなたは必要な日付を説明するだけ。月末やうるう年を乗り越える計算を書くのは、それがやります。

よくある質問

VBA で日付に日数を足すには?

ただ計算するだけです。日付は日数なので、newDate = myDate + 7 は 1 週間後、myDate - 1 は昨日です。 丸ごとの日を足すとき、VBA は月末もうるう年も繰り上げてくれます。DateAdd に手を伸ばすのは、単位が 月や年のときだけ。それらには一定の日数がないからです。

VBA の DateAdd と、ただ + を使うことの違いは?

素の +日数を足し、日の計算では正しく動きます。DateAdd(interval, number, date)カレンダーを理解するので、DateAdd("m", 1, …) は本物のひと月を足し — 1月31日 はありえない 31 日ではなく 2月28日 になり — DateAdd("yyyy", 1, …) はうるう年を扱います。日数には +、月と年 には DateAdd を使いましょう。+ 30+ 365 ではずれます。

VBA の DateDiff が年数を間違えるのはなぜ?

DateDiff("yyyy", …) が経過年数ではなく越えた年の境界を数えるからです。12 月 31 日から 1 月 1 日までは、1 日しか経っていなくても 1 を返します。年齢や本当の経過年数には、 DateDiff("yyyy", dob, Date) を計算し、今年の誕生日がまだなら 1 を引きます。文字どおりの日数が ほしいときは "d" を使いましょう。

DateAdd のインターバルコードは何を意味しますか?

単位を表す文字列コードです。"yyyy" 年、"q" 四半期、"m" 月、"ww" 週、"d" 日、"h" 時、 "n" 分、"s" 秒。落とし穴は 2 つ。分は "n""m"(月)ではないこと、そして**"y" は年 ではなく年内通算日**(年は "yyyy")であることです。同じコードが DateDiffDatePart でも 使えます。

VBA で月の最終日を取得するには?

翌月の日 0 を使う DateSerial です — DateSerial(Year(d), Month(d) + 1, 0)。日 0 は前月の最終日と 定義されているので、月の日数の表もうるう年の特別扱いもなしに、d の月の最終日を返します。 DateSerial は、文字列を解析するときの地域による日/月のあいまいさなしに、部品から日付を組み立て ます。

検証環境

検証環境: Excel 365 (Windows 11), VBA 7.1 — 最終確認 2026-08-26。

関連ガイド: VBA Now・Date・Time — 時計を読む · VBA Weekday・DatePart — 日付の部品を取り出す · VBA CStr・CDate・Val — 文字列を数値・日付へ変換 · VBA Format — 日付・数値の書式設定 · VBA For ループ — 実務例