Article

【Excel】VBA で定期実行マクロを書く方法|OnTime と停止罠の完全ガイド

この記事で分かること

Excel VBA で定期実行マクロを動かす Application.OnTime の使い方を、コピペで動く 3 用途完成コード・停止罠・Wait 比較で実機検証つき解説します。

Excel VBA で「5 秒ごとに更新したい」「毎日 17:00 に自動でファイルを保存したい」「業務時間内だけ定期処理したい」など、時刻に合わせてマクロを動かしたいことがあります。これを実現するのが Application.OnTime メソッドです。

ただし最初に押さえてください。Application.OnTime の予約は Excel が起動している間だけ有効です。Excel を閉じたり PC をスリープ・終了すると予約は失われます。Windows のタスク スケジューラの代わりにはなりません。

本記事では、コピペで動く 3 用途別の完成コードと、TimeSerial / TimeValue / 直接数値 / DateAdd の 4 種の時刻指定を整理します。停止し忘れの罠、二重起動の暴走、Application.Wait との根本的な違いまでを、Excel 2024 build 19929 で 36 シナリオ実機検証しました。

サンプルファイル(xlsm): VBA OnTime 定期実行

本記事の 3 用途完成コード (5 秒ごとの定期実行 / 17 時の単発実行 / 業務時間内の 1 時間ごと実行) を 1 ブックにまとめています。

閉じる前の手動停止手順と Workbook_BeforeClose 補助停止、Wait 比較体験ボタンも同梱。マクロを有効にするとそのまま動作します。

xlsm 形式 ・ 約 39 KB ・ シート 6 枚 ・ Excel 2024 build 19929 で動作確認。

helpaso-vba-ontime-periodic-sample-2026.xlsm

コピペで動く 3 用途別の完成コード

用途ごとに完成コードを 3 つ用意しました。標準モジュールに貼り付け、Alt+F8 から開始用プロシージャ(StartPeriodic など)を実行するだけで動きます。停止用プロシージャ(StopPeriodic など)でループを止められます。

コード A: 5 秒ごとに繰り返し実行する

株価更新マクロや在庫確認マクロのように、一定間隔で同じ処理を回したいときに使います。

Option Explicit

Public g_PeriodicNextRun As Double
Public g_PeriodicIsRunning As Boolean

Public Sub StartPeriodic()
    StopPeriodic  ' 二重起動対策: 既存ループがあれば先に止める
    g_PeriodicIsRunning = True
    PeriodicTask
End Sub

Public Sub PeriodicTask()
    If Not g_PeriodicIsRunning Then Exit Sub
    ' --- ここに繰り返したい処理を書く ---
    ThisWorkbook.Worksheets(1).Range("A1").Value = Now
    ' --- 5 秒後に自分自身を再予約 ---
    g_PeriodicNextRun = Now + TimeSerial(0, 0, 5)
    Application.OnTime g_PeriodicNextRun, "PeriodicTask"
End Sub

Public Sub StopPeriodic()
    g_PeriodicIsRunning = False
    On Error Resume Next
    Application.OnTime g_PeriodicNextRun, "PeriodicTask", , False
    On Error GoTo 0
End Sub

StartPeriodic を実行し A1 セルに現在時刻が 5 秒ごとに更新される Excel 画面

コード B: 指定時刻に一度だけ実行する

毎日 17:00 にブックを自動保存したいような単発予約です。すでに 17:00 を過ぎていれば翌日 17:00 に繰り越します。

Option Explicit

Public g_FixedRun As Double

Public Sub ReserveAt17()
    g_FixedRun = Date + TimeValue("17:00:00")
    If g_FixedRun <= Now Then
        g_FixedRun = g_FixedRun + 1   ' 翌日に繰り越し
    End If
    Application.OnTime g_FixedRun, "FixedTimeTask"
End Sub

Public Sub FixedTimeTask()
    ThisWorkbook.Save
    MsgBox "本日の業務終了処理を実行しました", vbInformation
End Sub

Public Sub CancelFixedTime()
    On Error Resume Next
    Application.OnTime g_FixedRun, "FixedTimeTask", , False
    On Error GoTo 0
End Sub

コード C: 業務時間内(9:00-17:00)で 1 時間ごとに実行

業務時間内だけ 1 時間ごとに定期処理し、業務時間外なら直近の 9:00 (深夜〜早朝の開始なら今日 9:00、17 時以降の開始なら翌日 9:00) に予約します。発火時刻は開始してから 1 時間後、その次の 1 時間後…の順になります(毎正時ではなく、開始時刻を基準にした 1 時間刻みです)。コード A と変数名が衝突しないよう g_Business 接頭辞を使っています。

Option Explicit

Public g_BusinessNextRun As Double
Public g_BusinessIsRunning As Boolean

Public Sub StartBusinessHour()
    StopBusinessHour
    g_BusinessIsRunning = True
    ScheduleNextBusinessHour
End Sub

Private Sub ScheduleNextBusinessHour()
    Dim base As Double
    base = Now + TimeSerial(1, 0, 0)
    If Hour(base) >= 9 And Hour(base) < 17 Then
        g_BusinessNextRun = base
    ElseIf Hour(base) < 9 Then
        ' 深夜〜早朝 (0:00〜7:59 に開始) → 今日 9:00 から
        g_BusinessNextRun = Date + TimeValue("09:00:00")
    Else
        g_BusinessNextRun = Date + 1 + TimeValue("09:00:00")
    End If
    Application.OnTime g_BusinessNextRun, "BusinessHourTask"
End Sub

Public Sub BusinessHourTask()
    If Not g_BusinessIsRunning Then Exit Sub
    ThisWorkbook.Worksheets(1).Range("B1").Value = "毎時更新: " & Now
    ScheduleNextBusinessHour
End Sub

Public Sub StopBusinessHour()
    g_BusinessIsRunning = False
    On Error Resume Next
    Application.OnTime g_BusinessNextRun, "BusinessHourTask", , False
    On Error GoTo 0
End Sub

これら 3 用途の完成版を 1 つのブックにまとめた配布 xlsm を本記事冒頭に置いています。マクロ付きファイルを社内で配布するときの注意点は Excel のマクロ付きファイルを安全に配布する方法もあわせてご確認ください。

OnTime の基本構文と 4 種の時刻指定

Application.OnTime の構文は次の 4 引数です。Microsoft 公式の Application.OnTime メソッド (Excel) に正式な定義があります。

Application.OnTime EarliestTime, Procedure, [LatestTime], [Schedule]
引数必須意味
EarliestTime必須プロシージャを実行する時刻。最も近い秒に丸められます(公式仕様)
Procedure必須実行するプロシージャ名(文字列)。引数は受け取れず、カスタムのクラスやフォーム内では宣言できません
LatestTime任意Excel が EarliestTime 時点でビジーだった場合、待ってもよい最も遅い時刻。省略すると Excel が空くまで待ち続けます
Schedule任意True で新規予約(既定)、False で既存予約を解除

時刻指定の 4 種はどれも同じ結果になる

「5 秒後に発火」を表す書き方は TimeSerialTimeValue、直接の数値、DateAdd の 4 種があります。Excel 2024 で実機検証したところ、すべて完全に同じ Double 値になりました。

' いずれも「現在から 1 秒後」を意味し、Double 値は完全一致
Now + TimeSerial(0, 0, 1)
Now + TimeValue("00:00:01")
Now + 1 / 86400
DateAdd("s", 1, Now)

読みやすさで選んで構いません。本記事のコード A は秒単位を直感的に指定できる TimeSerial を採用しました。

「秒に丸められる」の意味

公式に「EarliestTime は最も近い秒に丸められます」と書かれています。これは 発火タイミングが整数秒境界に揃うという意味です。0.4 秒後を指定しても 0.6 秒後を指定しても、結局は近いほうの整数秒で発火します。1 秒未満の精度で動かしたい用途には Application.OnTime は向きません。

Procedure 名は標準モジュールに置いたプロシージャに限られます。クラスモジュールやユーザーフォーム内に書いたプロシージャは Application.OnTime から呼び出せません(公式記載)。また Procedure に引数を渡す書き方もできません。引数で挙動を切り替えたい場合は、Public 変数に値を入れてからプロシージャを呼ぶ設計にしてください。

停止するには Schedule:=False と予約時刻の保存が必要

Application.OnTime の一番大きな罠が「停止できない」です。停止には Schedule:=False を渡しますが、予約時に渡した EarliestTime と Procedure 名の両方が完全一致している必要があります。

たとえば停止時に新しく Now + TimeSerial(0, 0, 5) を計算して渡しても、予約済みの時刻とは Double 値が違うので一致せず止まりません(この書き方は実行しないでください。実行時エラー 1004 になります)。

正解は、予約時刻を Public 変数に保存しておき、停止時に同じ値を渡すパターンです。コード A の g_PeriodicNextRun がこの役割を担っています。

Public g_PeriodicNextRun As Double

' 予約: 値を保存してから OnTime を呼ぶ
g_PeriodicNextRun = Now + TimeSerial(0, 0, 5)
Application.OnTime g_PeriodicNextRun, "PeriodicTask"

' 停止: 同じ値を渡す
Application.OnTime g_PeriodicNextRun, "PeriodicTask", , False

引数が一致しないと「実行時エラー 1004」

時刻が 1 秒でもズレたり、プロシージャ名が違うと、Excel は次のメッセージで止まります。下の図は実機ダイアログの再現イメージです (本検証ではこの内容を Err.Description で文字列として取得しています)。

Schedule:=False の引数が一致しない場合に表示される実行時エラー 1004 の VBA ダイアログ再現図

本検証では、予約済みの状態で時刻不一致のとき、また名前不一致のとき、いずれも同じ 実行時エラー 1004 'OnTime' メソッドは失敗しました が返りました。予約自体が無い状態で Schedule:=False を呼んでも同じ 1004 になります。つまりエラーメッセージだけでは「予約が無い」「引数が違う」を区別できません。

停止コードは On Error Resume Next で囲む

停止プロシージャはユーザーが連打する可能性もあり、すでに発火済みでキューが空のときも呼ばれます。そのため On Error Resume Next で 1004 を抑止する書き方が安全です。

Public Sub StopPeriodic()
    g_PeriodicIsRunning = False
    On Error Resume Next
    Application.OnTime g_PeriodicNextRun, "PeriodicTask", , False
    On Error GoTo 0
End Sub

VBA のエラーハンドリング全般の整理は エラーハンドリングに関連する Excel VBA ステートメントのリストを参照してください。

過去時刻を渡すと即時実行される

もう 1つ気づきにくい罠が、過去時刻を渡したときの挙動です。Application.OnTime に過去時刻を渡してもエラーにはならず、即時に発火します。コード B で DateValue(Date) + TimeValue("17:00:00") だけを書くと、17:00 を過ぎてから実行した瞬間にマクロが走り出します。コード B で翌日繰り越しガード(If g_FixedRun <= Now Then g_FixedRun = g_FixedRun + 1)を入れているのはこのためです。

Application.Wait との違い

「マクロを N 秒待たせたい」場面で Application.WaitApplication.OnTime のどちらを使うか迷うことがあります。両者は性質がまったく違うので、用途で使い分けます。

観点Application.WaitApplication.OnTime
動作の性質ブロッキング(Excel 内の操作が止まる)非ブロッキング(予約だけして即 return)
Excel での操作不可(セル編集 / スクロールが効かない)可(Excel を使いながら裏で予約)
他のアプリ(ブラウザなど)可(Excel 内だけが固まる)
同期 / 非同期同期非同期
引数Time(1 つ)EarliestTime, Procedure, LatestTime, Schedule(4 つ)
停止方法Esc(Ctrl+Break)Schedule:=False
向いている用途短時間の確実な待機(1〜5 秒)定期実行 / 指定時刻実行 / 非同期処理

Application.Wait 実行中の Excel が操作不能になっている状態のスクリーンショット

「Excel が止まる」は PC 全体が止まる意味ではない

Microsoft 公式の Wait メソッド説明には強めの表現が並びますが、実機で確認すると、Wait 中も他のアプリ(ブラウザやエクスプローラー)は普通に使えます。Excel 内のセル編集とスクロール、ダイアログ操作が一時的にできなくなる、と理解してください。

Wait の精度は最大 1 秒のズレが出る

Wait は秒境界に「丸まる」性質があります。Now + TimeSerial(0, 0, 2) の指定でも、呼び出し時の秒余りに依存して実測 1.0 〜 2.0 秒の幅でブロックします。本検証で連続 5 回計測したところ、最初の 1 回が 1.93 秒、続く 4 回は秒境界に揃って 2.00 秒でした。1 秒未満の精度は期待しないでください。

Dim startT As Double
startT = Timer
Application.Wait Now + TimeSerial(0, 0, 2)
Debug.Print Timer - startT   ' 1.93 〜 2.00 秒(呼び出し時刻に依存)

OnTime のほうは「予約だけして次行へ進む」ので、同じ計測で経過時間はほぼ 0 秒です。長時間ループや「裏で動かしたい」処理には OnTime のみが使えます。

LatestTime は通常省略でよい

LatestTime は「Excel がビジーで EarliestTime に発火できなかった場合、ここまでなら待ってよい」という最終期限を渡す引数です。省略すると Excel が空くまで待ち続ける挙動になり、ほとんどの場面で省略が安全です(公式仕様)。

LatestTime の指定実機での発火
省略(推奨)発火
EarliestTime + 5 秒(広い窓)発火
EarliestTime より前(LatestTime < EarliestTime)発火しませんでした
EarliestTime と同じ(LatestTime = EarliestTime)発火しませんでした

LatestTime を EarliestTime より前にすると発火しないことを示す Excel の画面

狭い LatestTime を入れると、Excel が指定時刻に瞬時に空かない限り エラーも出ずに予約が消えます。「LatestTime を入れたら一度もマクロが動かない」場合は、一旦省略するか EarliestTime より大きく取って試してください。

StartPeriodic を二度押すとマクロが暴走する罠

これがコード A で最も実害の出る罠です。停止せずに StartPeriodic をもう一度実行すると、予約のチェーンは 2 本走ります。1 度発火するたびに 2 本それぞれ次回を予約するので、A1 セルのカウンタが 通常の数倍ペースで進みます。

本検証で 1 秒間隔のコード A を 2 回連続実行し、5.5 秒後に A1 を見たところカウンタは 12 でした。単一起動なら 4 〜 5 程度です。

条件5.5 秒後の A1 (実測)
StartPeriodic を 2 回連続実行(バグ版)12
StartPeriodic を 2 回連続実行(修正版 = 冒頭で StopPeriodic 呼出)4

StartPeriodic を 2 回連続実行したバグ版 (カウント 12) と修正版 (カウント 4) の比較

修正パターン: StartPeriodic 冒頭で StopPeriodic を呼ぶ

本記事のコード A は最初からこの修正を入れています。ボタン連打や複数回起動を想定して、開始用プロシージャの 1 行目に必ず停止呼出を入れるパターンを覚えておくと安全です。

Public Sub StartPeriodic()
    StopPeriodic  ' ← この 1 行が暴走防止の鍵
    g_PeriodicIsRunning = True
    PeriodicTask
End Sub

本検証ではこの 1 行を入れた修正版で、2 回連続実行しても A1 は 4 に収まることを確認しました。

ブックを閉じても予約が残る罠と二段防御

もう 1つ実害の出やすい罠が、Application.OnTime の予約は ブックを閉じても Application(Excel.exe)側に残ることです。停止プロシージャを呼ばずにブックを閉じると、予約時刻が来た瞬間に「マクロを実行できません」というエラーダイアログが出ます。

OnTime 予約を残したままブックを閉じた場合に表示されるマクロが実行できないエラーダイアログ

第一防衛線: ブックを閉じる前に必ず手動で停止する

もっとも確実なのは、ブックを閉じる前にユーザーが停止ボタンを押してから閉じることです。配布 xlsm では各サンプルシートに「停止」ボタンを置いてあるので、定期実行を動かしたらブックを閉じる前に必ず停止ボタンを押す運用にしてください。

' 停止ボタンに割り当てる: 予約取消 + 停止フラグの解除
Public Sub StopPeriodic()
    g_PeriodicIsRunning = False
    On Error Resume Next
    Application.OnTime g_PeriodicNextRun, "PeriodicTask", , False
    On Error GoTo 0
End Sub

停止ボタンを押してからブックを閉じれば、予約時刻が来てもダイアログは出ません。S6 と同じ「5 秒後発火」予約を立てて、ブックを閉じる前に停止ボタンを押した状態が次の画面です。

閉じる前に停止ボタンを押してから閉じたためエラーダイアログが出ない Excel の通常状態

第二防衛線: Workbook_BeforeClose で StopPeriodic を呼ぶ (補助)

停止し忘れに備えて、ThisWorkbook モジュールに Workbook_BeforeClose を置き、ブックが閉じる直前に停止プロシージャを呼びます。配布 xlsm にはこのイベントを最初から組み込んであります。

' ThisWorkbook モジュールに貼り付け
Private Sub Workbook_BeforeClose(Cancel As Boolean)
    On Error Resume Next
    StopPeriodic       ' コード A を使う場合
    CancelFixedTime    ' コード B を使う場合
    StopBusinessHour   ' コード C を使う場合
    On Error GoTo 0
End Sub

使っていない停止プロシージャを呼んでもエラーにならないよう、On Error Resume Next で囲んでおきます。

BeforeClose は「補助」と位置づける理由

本検証では、Excel 2024 build 19929 でこの BeforeClose 経由のキャンセルが silent fail するケースを観測しました。

具体的にはこうです。ブックを閉じる途中の Workbook_BeforeClose 内で Schedule:=False 付きの Application.OnTime を呼ぶと、Err.Number = 0 (正常完了) は返るのに、実際の予約は消えません。

直後の予約時刻でダイアログが出てしまうため、BeforeClose は「ユーザーが停止を忘れたときの保険」と考えるのが安全です

予約時刻通過後に出るダイアログ自体は、ブックが閉じてしまった後の挙動なので、停止フラグ g_PeriodicIsRunning では抑止できません(コールバックそのものを Excel が呼べないため)。停止フラグの役割は「ブックが開いていてコールバックが実行できる状態で、再予約のループを止める」ことに限定されます。

つまり実務では、ユーザー操作による手動停止が第一防衛線、BeforeClose が補助、停止フラグはコールバック内ループ抑止という役割分担で考えてください。

トラブルシュート FAQ

Q: マクロが動かない A: Application.OnTime は標準モジュールのプロシージャしか呼べません。クラスモジュールやユーザーフォーム内のプロシージャは呼び出し対象になりません。

Q: マクロが 2 回続けて走ってしまう A: StartPeriodic を 2 度実行している可能性が高いです。本記事の修正版(StopPeriodic を冒頭で呼ぶ)に差し替えてください。

Q: 停止できない(実行時エラー 1004) A: 予約時の EarliestTime と Procedure 名が完全一致していません。Public 変数に予約時刻を保存し、停止時に同じ値を渡してください。On Error Resume Next で囲むのも忘れずに。

Q: LatestTime を入れたら一度も動かない A: LatestTime が EarliestTime より前か、同じ時刻になっている可能性があります。LatestTime は省略するか、十分広く(EarliestTime + 5 秒以上)取ってください。

Q: Excel を閉じている間も自動で動かしたい A: できません。Application.OnTime は Excel が起動している間だけ有効です。常駐的な自動実行が必要なら Windows のタスク スケジューラから Excel ごと起動するスクリプトを呼ぶ構成にしてください。

Q: 配布 xlsm がマクロブロックされて動かない A: ダウンロードしたファイルは保護ビューやマクロブロックの対象になることがあります。Excel のマクロ付きファイルを安全に配布する方法を参照してください。

Q: 処理が予約間隔より長い場合は A: 本記事のコード A は「処理を実行 → 完了後に次回を予約」の順なので、処理本体が間隔より長くても発火は積もりません (前の処理が終わってから次の予約が走ります)。一方「先に予約を入れて、後で処理する」設計だと、処理が長いと後続の発火が積もります。別パターンを書くときは、間隔を長めにするか、処理中フラグで再入防止してください。

Q: 複数のブックを開いていて同じ名前のプロシージャがある A: Procedure 名にブック名を修飾すると、どのブックのプロシージャを呼ぶか明示できます。キャンセル時も同じ文字列を渡してください(一致判定の対象になります)。

' 予約: ブック名を修飾
Application.OnTime g_PeriodicNextRun, _
    "'" & ThisWorkbook.Name & "'!PeriodicTask"

' 停止: 同じ文字列で
Application.OnTime g_PeriodicNextRun, _
    "'" & ThisWorkbook.Name & "'!PeriodicTask", , False

まとめ

Application.OnTime で定期実行マクロを書くときの押さえどころを早見表にまとめます。

用途推奨パターン
一定間隔の繰り返し(5 秒ごとなど)コード A(Public 変数 + StopPeriodic 先行呼出)
指定時刻の単発実行(17:00 など)コード B(過去時刻ガード付き)
業務時間内に 1 時間ごとの実行コード C(業務時間判定 + 翌日繰り越し)
短時間の確実な待機(1〜5 秒)Application.Wait(Excel 内が止まる)
停止し忘れ防止第一: 閉じる前に手動で停止ボタン / 第二: Workbook_BeforeClose で StopPeriodic

本記事のコードをまとめた配布 xlsm は冒頭のダウンロードカードからお使いいただけます。OnTime を応用してセルを点滅させる方法は別記事で扱う予定です。

Next Read

このあと読む記事

今の内容に近い記事から、次の1本と補助記事を続けて見つけられるようにしています。

Keep Exploring

このテーマをさらに探す

同じテーマの入口記事と更新記事を、一覧の形でまとめています。

コメント