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
OnTimewith aDoEventsloop that checksGetTickCountorTimerto detect idle resume and run the macro.
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
- 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. - 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 theTimerfunction when the schedule was set.
Step 2: Create a Start Routine That Sets the Schedule
- Write a public subroutine named StartSchedule
Inside this sub, setm_targetTime = Timer + 60to schedule the macro to run 60 seconds from now. Replace 60 with your desired delay in seconds. - Call the polling loop
After setting the target time, callPollingLoop(which you will create next). Example:Call PollingLoop.
Step 3: Write the Polling Loop That Checks the Time
- Create a subroutine named PollingLoop
Add the following code structure:Public Sub PollingLoop() Do While Timer < m_targetTime DoEvents Loop Call YourActualMacro End SubThis loop runs continuously, yielding to other processes via
DoEvents, until the current time reaches or passes the target time. - 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
- Assign StartSchedule to a button or AutoOpen event
To start the schedule automatically when the document opens, placeCall StartSchedulein theDocument_Openevent ofThisDocument. - Test the macro
RunStartSchedulemanually 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.
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.