Why Word VBA Application.OnTime Skips Schedule on Idle Mode Resume
🔍 WiseChecker

Why Word VBA Application.OnTime Skips Schedule on Idle Mode Resume

If you use Word VBA Application.OnTime to schedule a macro to run at a specific time, you may notice the macro does not run after your computer resumes from idle mode such as sleep or hibernate. This happens because OnTime relies on the system clock and Word’s internal timer queue, both of which pause during idle states. When the system wakes, Word does not automatically re-evaluate pending OnTime calls against the new clock time, so the scheduled event is skipped entirely. This article explains the root cause in detail and provides a reliable workaround to ensure your scheduled macros run correctly after idle mode resume.

Key Takeaways: Why OnTime Fails After Idle and How to Fix It

  • Application.OnTime uses absolute time values, not relative timers: When the system sleeps, the scheduled time passes and Word does not fire the macro upon resume.
  • Word does not queue a system wake timer for VBA OnTime events: No built-in mechanism exists to wake the computer or re-fire missed schedules.
  • Use a polling loop with DoEvents and a system idle check: Replace OnTime with a DoEvents loop that checks GetTickCount or Timer to detect idle resume and run the macro.

ADVERTISEMENT

Why Application.OnTime Misses Scheduled Events After Idle Resume

The Application.OnTime method in Word VBA schedules a macro to run at a specific date and time. Internally, Word stores this absolute time and checks it against the system clock every few milliseconds while the application is active. When the computer enters sleep, hibernate, or even extended idle that triggers a display sleep, the system clock stops advancing for the duration of the idle state. Word also does not process any VBA events during this time because the application thread is suspended.

When the system resumes, the system clock jumps forward to the current real time. Word does not retroactively fire OnTime events whose scheduled time has already passed. The event is simply discarded from the timer queue. This behavior is by design: OnTime is not a recurring alarm system; it is a one-shot scheduler that expects the computer to remain active. If the scheduled time is in the past at the moment Word checks the queue after resume, the event is skipped without any error or notification.

Another factor is that Word’s internal timer queue is not persisted to disk. If the system loses power or the application is closed during sleep, all pending OnTime calls are lost. The VBA engine does not provide a built-in method to query which OnTime events are still pending or to re-register them after a resume event.

Reliable Workaround: Polling Loop With Idle Detection

Because OnTime cannot be trusted after idle resume, you must replace it with a polling mechanism that runs continuously and checks the system uptime or a stored target time. The following approach uses a DoEvents loop in a macro that runs on a timer set by Application.OnTime but with a short interval, such as every 10 seconds. Inside the loop, you compare the current Timer value against a stored target time. If the target time is in the past, the macro executes. This method survives idle resume because the loop continues after the system wakes.

Step 1: Store the Target Time in a Module-Level Variable

  1. Open the VBA Editor
    Press Alt + F11 to open the VBA editor. In the Project Explorer, double-click ThisDocument or a standard module where you want the code.
  2. Declare a module-level variable for the target time
    At the top of the module, add: Private m_targetTime As Double. This variable stores the absolute time value from the Timer function when the schedule was set.

Step 2: Create a Start Routine That Sets the Schedule

  1. Write a public subroutine named StartSchedule
    Inside this sub, set m_targetTime = Timer + 60 to schedule the macro to run 60 seconds from now. Replace 60 with your desired delay in seconds.
  2. Call the polling loop
    After setting the target time, call PollingLoop (which you will create next). Example: Call PollingLoop.

Step 3: Write the Polling Loop That Checks the Time

  1. Create a subroutine named PollingLoop
    Add the following code structure:
    Public Sub PollingLoop()
        Do While Timer < m_targetTime
            DoEvents
        Loop
        Call YourActualMacro
    End Sub
    

    This loop runs continuously, yielding to other processes via DoEvents, until the current time reaches or passes the target time.

  2. Replace YourActualMacro with the name of your intended macro
    The macro runs immediately after the loop exits, regardless of idle resume.

Step 4: Start the Schedule From a User-Friendly Trigger

  1. Assign StartSchedule to a button or AutoOpen event
    To start the schedule automatically when the document opens, place Call StartSchedule in the Document_Open event of ThisDocument.
  2. Test the macro
    Run StartSchedule manually from the VBA editor by pressing F5. Put your computer to sleep for 30 seconds, then wake it. The macro should execute within a few seconds after resume.

ADVERTISEMENT

Common Pitfalls When Replacing OnTime With a Polling Loop

Word Becomes Unresponsive During the Loop

Without DoEvents, the loop blocks Word's UI thread, causing the application to freeze. Always include DoEvents inside the loop. For very long delays, consider adding a Sleep API call to reduce CPU usage. Example: Private Declare PtrSafe Sub Sleep Lib "kernel32" (ByVal dwMilliseconds As Long) and call Sleep 500 inside the loop to pause half a second between checks.

The Macro Runs Immediately After Resume Even If the Target Time Has Not Arrived

This indicates that Timer jumped forward during sleep, and your target time was already in the past. To prevent this, store the target time as an absolute UTC value using DateAdd and Now instead of Timer. Compare using Now >= m_targetDateTime. This method works because Now reflects the current system date and time, which is corrected after resume.

The Polling Loop Prevents Word From Closing Properly

If the user tries to close Word while the loop is running, the application may hang. Add an exit condition using a public boolean variable. For example, set Public m_cancelPolling As Boolean to False at the start. Inside the loop, check If m_cancelPolling Then Exit Do. In the Document_BeforeClose event, set m_cancelPolling = True.

OnTime vs Polling Loop: Behavior Comparison After Idle Resume

Item Application.OnTime Polling Loop With DoEvents
Behavior after sleep or hibernate Skips the scheduled macro entirely Continues checking and fires macro after resume
CPU usage during idle Near zero (no loop) Low to moderate depending on Sleep API usage
Requires manual cancellation on document close No Yes, to avoid hang
Supports recurring schedules Yes, by re-calling OnTime inside the macro Yes, by re-entering the loop after the macro runs
Works with system power state changes No Yes

After applying the polling loop method, your scheduled macros will survive idle mode resume without skipping. The key change is replacing a passive timer with an active check that yields control via DoEvents. For production code, add error handling inside the loop using On Error Resume Next and a maximum iteration count to prevent infinite loops. Test the macro with multiple sleep cycles to confirm reliability.

ADVERTISEMENT