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

Excel VBA の OnTime — 指定時刻や N 分ごとにマクロを実行し、確実に止める

|

Excel VBA の OnTime — 指定時刻や N 分ごとにマクロを実行し、確実に止める

TL;DR — Application.OnTime はタイマーを起動しません。Excel のキューに 予約 を入れるだけです。この時刻になったら、このマクロを実行する、という予約です。コードはすぐに終わり、Excel はそのまま使えます。時刻が来て Excel の準備ができていれば、Excel がマクロを実行します。予約は 正確な時刻とマクロ名 で識別されるので、取り消すには両方をもう一度渡す必要があり、そのためには時刻をモジュールレベル変数に保存しておかなければなりません。繰り返し実行したいなら、マクロ自身が次の実行を予約します。取り消しは Workbook_BeforeClose で行ってください。ブックを閉じても何も取り消されず、Excel は約束を守るためにファイルを開き直すからです。

Private mNextRun As Date                       ' 取り消す必要があるかもしれない予約

Sub StartRefresh()
    mNextRun = Now + TimeSerial(0, 5, 0)       ' 今から 5 分後
    Application.OnTime mNextRun, "RefreshPrices"
End Sub

Sub StopRefresh()
    On Error Resume Next                       ' 何も予約されていなければエラー 1004
    Application.OnTime mNextRun, "RefreshPrices", Schedule:=False
    On Error GoTo 0
End Sub

この記事は、Excel があなたの代わりに実行するマクロ を扱うシリーズの第二回です。ショートカットキーのガイド ではキーボードから起動するマクロを、SendKeys のガイド では偽装したキー入力を扱っています。三つをつなぐ考え方はこうです。どれもマクロを直接呼び出してはいない。それぞれが Excel のキューに要求を入れ、Excel は手が空いて準備ができたときにそれを実行する。 OnTime の場合、要求には時刻が付いていて、実行されるか、まったく同じ内容で取り除かれるまでキューに残り続けます。

この記事で学べること

  • 考え方の軸:動いているタイマーではなく、予約である
  • 決まった時刻に、そして N 分ごとにマクロを実行する
  • 取り消しが失敗する理由と、それを解決する変数
  • コードのリセット後に止められなくなるタイマー
  • ブックを閉じると Excel がそれを開き直す理由
  • 忙しい Excel、LatestTime、そしてずれていくスケジュール
  • OnTime を使うべきでない場面

考え方の軸:タイマーではなく予約

OnTime と、待機するループを比べてみましょう。

' Excel をブロックする:5 分間ほかに何もできない
Application.Wait Now + TimeSerial(0, 5, 0)
RefreshPrices

' 予約を入れてすぐに戻る:Excel はそのまま使える
Application.OnTime Now + TimeSerial(0, 5, 0), "RefreshPrices"

Application.Wait や Sleep は、マクロを実行させ続け、Excel を固まらせます。OnTime は Excel にメモを渡して終わります。今から予約の時刻までの間、VBA のコードは一行も動いていません。ユーザーは普段どおりに作業できます。

Excel はこうした予約の一覧を持っています。各項目には日時とプロシージャ名という二つのキーがあり、Excel は その両方 が一致するもので項目を探します。この記事のほかの内容はすべてここから導かれます。複数の予約を入れられること、一つを取り消すには正確に名指しする必要があること、そして一覧はブックではなく Excel のものであることです。

決まった時刻にマクロを実行する

どの日のことかで迷わないよう、OnTime には日付と時刻をそろえて渡します。

Sub BookDailyExport()
    Dim runAt As Date
    runAt = Date + TimeSerial(17, 0, 0)        ' 今日の 17:00
    If runAt <= Now Then runAt = runAt + 1     ' すでに過ぎていたら明日の 17:00
    Application.OnTime runAt, "DailyExport"
End Sub

多くのサンプルは TimeValue("17:00:00") だけを渡しています。日付を自分で組み立てれば、ルールがコードの上で見えるようになります。時刻が過ぎていたら、次の実行は今ではなく明日です。Date に時刻を足すと本物の日時の値になる理由は Now のガイド で説明しています。TimeSerial を使えば、計算が地域設定の影響を受けません。

N 分ごとにマクロを実行する

繰り返しのオプションはありません。繰り返し実行するジョブとは、最後に 自分自身の次の実行を予約する マクロのことです。

Private mNextRun As Date
Private Const INTERVAL_MIN As Long = 5

Sub StartRefresh()
    mNextRun = Now + TimeSerial(0, INTERVAL_MIN, 0)
    Application.OnTime mNextRun, "RefreshPrices"
End Sub

Sub RefreshPrices()
    ThisWorkbook.Worksheets("Prices").Range("A1").CurrentRegion.Calculate
    ' ... 実際の処理 ...
    StartRefresh                               ' 次の実行を予約する
End Sub

StartRefresh が連鎖を始め、RefreshPrices がそれを続けます。連鎖を止めるには、いま予約されている一つの予約を取り除かなければなりません。そして、OnTime のコードの大半がつまずくのがここです。

取り消し:正確な時刻か、エラー 1004 か

取り消すには、Schedule:=False を付けてもう一度 OnTime を呼びます。すると Excel は、同じ時刻と同じプロシージャ名を持つ項目を探します。次のコードは正しそうに見えて、動きません。

' 誤り:Now は進んでいるので、この時刻はどの予約とも一致しない
Application.OnTime Now + TimeSerial(0, 5, 0), "RefreshPrices", Schedule:=False

その新しい時刻には予約が存在しないので、Excel は Run-time error 1004: Method 'OnTime' of object '_Application' failed(日本語版:「実行時エラー '1004': 'OnTime' メソッドは失敗しました: '_Application' オブジェクト」)を発生させ、本物の予約は残ったままになります。だからこそ、時刻はモジュールレベル変数(上の mNextRun)に置くのです。予約にも取り消しにもその変数を使えば、内容は常に一致します。

StopRefresh の On Error Resume Next は意図的なもので、対象は一行だけです。たとえばマクロがすでに実行済みで何も予約されていなければ、取り消しは 1004 を発生させますが、それに対してできることは何もありません。こうしたエラー処理を狭く保つ方法は On Error のガイド で説明しています。

止められなくなったタイマー

この変数には弱点があります。モジュールレベル変数は、VBA プロジェクトがリセットされるたびに消去されます。リセットボタンを押したとき、処理されないエラーでコードが終了したとき、コードが End を実行したとき、中断中にコードを編集したときです。すべての場合は グローバル変数のガイド に挙げています。

リセット後、mNextRun は 0 になります。予約は Excel の一覧にまだ残っていますが、コードはもうその時刻を知らないので、StopRefresh は何も取り消せません。マクロは 5 分ごとに実行され続け、そのたびに自分自身を予約し直します。

リセットを生き延びる場所、たとえば非表示の設定シートのセルに、時刻のコピーを残しておきましょう。

Sub StartRefresh()
    mNextRun = Now + TimeSerial(0, INTERVAL_MIN, 0)
    ThisWorkbook.Worksheets("Settings").Range("B2").Value = mNextRun
    Application.OnTime mNextRun, "RefreshPrices"
End Sub

Sub StopRefresh()
    If mNextRun = 0 Then mNextRun = ThisWorkbook.Worksheets("Settings").Range("B2").Value
    On Error Resume Next
    Application.OnTime mNextRun, "RefreshPrices", Schedule:=False
    On Error GoTo 0
    mNextRun = 0
End Sub

それでも開発中に暴走するタイマーが生まれてしまったら、Excel を完全に終了すれば一覧は消去されます。

ブックを閉じても何も取り消されない

予約は Excel のものであり、ブックのものではありません。ユーザーがブックを閉じても Excel が開いたままなら、予約の時刻に Excel はマクロを実行するために ブックを開き直します。ユーザーから見れば、閉じたはずのファイルが勝手にまた現れるわけです。しかもマクロが次の実行を予約するなら、それが繰り返されます。

ですから、実行を予約するブックはすべて、閉じるときにそれを取り消さなければなりません。

' ThisWorkbook モジュールに記述
Private Sub Workbook_BeforeClose(Cancel As Boolean)
    StopRefresh
End Sub

そのあとユーザーが保存確認のメッセージで閉じる操作をキャンセルすると、ブックはタイマーが止まった状態で開いたままになります。それが問題になるなら、Workbook_Activate で再開してください。このケースは BeforeClose のガイド で扱っています。

忙しい Excel、LatestTime、そしてずれていくスケジュール

ここでもシリーズ共通のルールが当てはまります。Excel が予約を実行するのは 準備ができているとき だけです。17:00 の時点でユーザーがセルに入力中だったり、ダイアログを開いていたり、別のマクロが実行中だったりすれば、マクロはそれが終わるまで待ちます。

三つ目の引数 LatestTime は、この待ち時間に上限を設けます。その時刻までに Excel がマクロを実行できなければ、予約は破棄されます。

' 17:00 に実行する。17:05 になっても Excel が忙しければ実行しない
Application.OnTime runAt, "DailyExport", runAt + TimeSerial(0, 5, 0)

この待ちは、繰り返しのジョブがずれていく理由でもあります。各実行の終わりに測った Now + 5 分 は、実行にかかった時間と遅延をすべての間隔に上乗せします。:00、:05、:10 ちょうどに実行したいなら、次の実行は直前の予約時刻から計算し、すでに過ぎた時刻は飛ばします。

Sub BookNext()
    mNextRun = mNextRun + TimeSerial(0, INTERVAL_MIN, 0)
    Do While mNextRun <= Now                   ' Excel が忙しかった:逃した枠は飛ばす
        mNextRun = mNextRun + TimeSerial(0, INTERVAL_MIN, 0)
    Loop
    Application.OnTime mNextRun, "RefreshPrices"
End Sub

OnTime の精度は秒単位で、それより細かくはなりません。もっと短い間隔が必要なら、それは向いていない道具です。短い時間の計測は Timer のガイド で扱っています。

判断の分かれ目:OnTime はスケジューラではない

OnTime がマクロを実行するのは、誰かの Excel でこのブックが開いている間 だけです。数分ごとに更新するダッシュボード、自動保存のリマインダー、自然に消えるステータスメッセージには最適な道具です。一方、毎晩午前 2 時にエクスポートを実行する には向いていません。その時刻には誰の Excel も開いておらず、スリープ中のノート PC は時刻を逃し、リセットやクラッシュが一度起きるだけで連鎖は跡形もなく途切れます。

誰が席にいるかに関係なく実行しなければならないジョブには、Windows のタスク スケジューラでブックを開かせ、Workbook_Open に処理をさせます。やり方は Workbook_Open のガイド で説明しています。OnTime はセッション内のタイミング制御に限って使い、すべての予約に取り消す手段を用意しておきましょう。

ExcelMaster の活用

OnTime のバグは、あとになって、別の場所で表面化します。昼に勝手に開き直す閉じたはずのファイル、止まらない更新処理、誰かがリセットボタンを押したあとでだけおかしくなるタイマー。

ExcelMaster はブックのコードを読み、OnTime の予約をすべて見つけ出し、それぞれに同じ保存済みの時刻を使う対の取り消し処理があるかを確認し、ファイルが勝手に開き直すのを防ぐ Workbook_BeforeClose の後片付けを追加することもできます。

よくある質問

Excel VBA でマクロを 5 分ごとに実行するには?

最初の実行を Application.OnTime Now + TimeSerial(0, 5, 0), "MyMacro" で予約し、MyMacro の最後で同じように次の実行を予約します。取り消せるように、予約した時刻は毎回モジュールレベル変数に保存しておきます。

Application.OnTime を止めるには?

予約したときと同じ時刻とプロシージャ名に Schedule:=False を付けて Application.OnTime を呼びます。時刻は正確に一致しなければならないので、新たに Now から計算し直すのではなく、保存しておいた変数を使ってください。

OnTime を取り消すとエラー 1004 になるのはなぜですか?

渡した時刻とプロシージャ名に一致する予約がないからです。たいていは時刻を計算し直したか、予約がすでに実行済みだったためです。保存しておいた時刻で取り消し、取り消しの一行を On Error Resume Next で囲んでください。

ブックが勝手に開き直すのはなぜですか?

ブックを閉じた時点で、OnTime で入れた予約が Excel の一覧にまだ残っていたため、Excel がマクロを実行するためにファイルを開き直したのです。予約は Workbook_BeforeClose で取り消してください。

Excel を閉じていても OnTime は実行されますか?

いいえ。予約は実行中の Excel の中にしか存在しません。Excel を閉じているときにマクロを実行したいなら、Windows のタスク スケジューラでブックを開くよう設定し、処理を Workbook_Open に書いてください。

検証環境

検証環境: Excel 365 (Windows 11)、VBA 7.1 — 最終確認 2026-10-03。

関連ガイド: VBA Shortcut Key · VBA SendKeys · VBA Wait · VBA Sleep · VBA Timer · VBA Now · VBA Global Variable · VBA Workbook_BeforeClose