SLEEP.BAT - Hybrid script utility for sub-second delays

Discussion forum for all Windows batch related topics.

Moderator: DosItHelp

Post Reply
Message
Author
dbenham
Expert
Posts: 2461
Joined: 12 Feb 2011 21:02
Location: United States (east coast)

SLEEP.BAT - Hybrid script utility for sub-second delays

#1 Post by dbenham » 27 Apr 2015 17:27

I have developed a hybrid JScript/batch utility called SLEEP.BAT that suspends processing (low CPU usage) to achieve sub-second delays. The delay is generally accurate to within 10 msec, as long as the delay exceeds a minimum possible delay.

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

Post Reply