The minimum possible delay varies from machine to machine. In the two machines I have tested, the minimum delay ranged from 30 to 55 msec.
The concept is quite simple - call the utility, passing in the desired delay, along with the %TIME% at the time of call. The embedded JScript subtracts the start time from the current time to determine how much time has already elapsed, and then subtracts that time from the delay time to figure out how long to sleep using WScript.sleep().
The SLEEP utility can supply its own %TIME% value, but then the accuracy may suffer a bit due to the time it takes to CALL the utility.
The only potential problem with SLEEP.BAT is it can give an incorrect delay if called the instant before changeover from standard to daylight savings time, or vice versa.
SLEEP.BAT
Code: Select all
@if (@X)==(@Y) @end /* harmless hybrid line that begins a JScript comment
@goto :batch
:::
:::SLEEP.BAT msec [%time%]
:::SLEEP.BAT /?
:::
::: Suspend processing for msec milliseconds. The optional %time% argument
::: can be added to improve timing accuracy. If called within a FOR loop,
::: then !time! should be used instead, after enabling delayed expansion.
:::
::: There is a startup time for SLEEP.BAT that limits the shortest delay
::: possible. The startup time varies from machine to machine. Delays longer
::: than the minimum are usually accurate to within 10 msec if the %time%
::: argument is provided. One exception is when SLEEP.BAT is called the
::: instant before changing from standard to daylight savings time, in which
::: case the delay is extended by the startup time. The other exception occurs
::: when changing from daylight savings to standard, in which case the delay
::: never exceeds the startup time.
:::
::: A single /? argument displays this help.
:::
::: SLEEP.BAT Version 1.0 - written by Dave Benham
:::
============= :Batch portion ===========
@echo off
if "%~1" equ "/?" (
for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
exit /b 0
) else cscript //E:JScript //nologo "%~f0" %* %time%
exit /b
============ JScript portion ==========*/
try {
var arg2 = WScript.Arguments.Item(1).split(/[:.,]/);
var start = new Date();
if (start.getHours()<Number(arg2[0])) start.setDate( start.getDate()-1 );
start.setHours( Number(arg2[0]),
Number(arg2[1]),
Number(arg2[2]),
Number(arg2[3])*10
);
var delay = Number(WScript.Arguments.Item(0));
var adjustedDelay = delay - ((new Date())-start);
if (adjustedDelay>0) WScript.sleep( (adjustedDelay>delay) ? delay : adjustedDelay );
WScript.Quit(0);
} catch(e) {
WScript.Stderr.WriteLine("SLEEP.BAT - Invalid call");
WScript.Quit(1);
}
Here is a test script derived from an Aacini StackOverflow post that tests the accuracy.
Code: Select all
@echo off
setlocal enableDelayedExpansion
set runs=10
for %%t in (20 30 40 50 70 100 250 500 1000) do (
(
set t0=!time!
for /l %%p in (1,1,%runs%) do call sleep %%t !time!
set t1=!time!
)
for /f "tokens=1-8 delims=:.," %%a in ("!t0: =0!:!t1: =0!") do (
set /a "a=(((1%%e-1%%a)*60)+1%%f-1%%b)*6000+1%%g%%h-1%%c%%d, a+=(a>>31) & 8640000"
)
set /a average_time=a*10/runs
echo(Input:%%t ms - Output: Average time !average_time! ms
)
And here is a sample result
Code: Select all
Input:20 ms - Output: Average time 58 ms
Input:30 ms - Output: Average time 57 ms
Input:40 ms - Output: Average time 56 ms
Input:50 ms - Output: Average time 56 ms
Input:70 ms - Output: Average time 75 ms
Input:100 ms - Output: Average time 102 ms
Input:250 ms - Output: Average time 250 ms
Input:500 ms - Output: Average time 500 ms
Input:1000 ms - Output: Average time 1001 ms
It is possible to write a version that does not have problems at standard/daylight savings changeover if both the date and time are passed in. Unfortunately, the only batch method to quickly get the date is %DATE%, and that is locale dependent. The version below will work with any locale where %DATE% can be parsed by the Date object's parse() method. This includes U.S. machines, and many others.
LocaleSleep.bat
Code: Select all
@if (@X)==(@Y) @end /* harmless hybrid line that begins a JScript comment
@goto :batch
:::
:::localeSleep.bat msec ["%date% %time%"]
:::localeSleep.bat /?
:::
::: Suspend processing for msec milliseconds. The optional "%date% %time%"
::: argument can be added to improve timing accuracy. If called within a
::: FOR loop, then "!date! !time!" should be used instead, after enabling
::: delayed expansion.
:::
::: This utility is locale specific. It only works properly if %date% is in
::: a format that is parsed properly by the Date object parse() method.
:::
::: There is a startup time for SLEEP.BAT that limits the shortest delay
::: possible. The startup time varies from machine to machine. Delays longer
::: than the minimum are usually accurate to within 10 msec if the
::: "%date% %time%" argument is provided.
:::
::: A single /? argument displays this help.
:::
::: localeSleep.bat Version 1.0 - written by Dave Benham
:::
============= :Batch portion ===========
@echo off
if "%~1" equ "/?" (
for /f "tokens=* delims=:" %%A in ('findstr "^:::" "%~f0"') do @echo(%%A
exit /b 0
) else cscript //E:JScript //nologo "%~f0" %* "%date% %time%"
exit /b
============ JScript portion ==========*/
try {
var arg2 = WScript.Arguments.Item(1);
var delay = Number(WScript.Arguments.Item(0)) - ((new Date())-(new Date(arg2.slice(0,-3))).setMilliseconds( Number(arg2.slice(-2))*10 ));
if (delay>0) WScript.sleep(delay);
WScript.Quit(0);
} catch(e) {
WScript.Stderr.WriteLine("localeSleep.bat - Invalid call");
WScript.Quit(1);
}
Sample Usage:
Code: Select all
call LocaleSleep 250 "%date% %time%"
Performance is virtually identical to SLEEP.BAT
Dave Benham