20

How can I write a script to calculate the time the script took to complete?

I thought this would be the case, but obviously not..

@echo off
set starttime=%time%
set endtime=%time%

REM do stuff here

set /a runtime=%endtime%-%starttime%
echo Script took %runtime% to complete
Peter Mortensen
  • 28,342
  • 21
  • 95
  • 123

6 Answers6

40

Two things leap out about the original batch file. but neither is going to help in the long run.

  1. Whatever your benchmark method, be sure to capture the start time before the operation, and the end time after the operation. Your sample doesn't do that.

  2. %time% evaluates to something like "12:34:56.78", and the SET /A command can't subtract those. You need a command that produces a simple scalar time stamp.

I was going to say it can't be done, but the batch language is a lot more powerful than it is given credit for, so here is a simple implementation of TIMER.BAT. For the record, Pax beat me to an answer showing the string splitting while I was fiddling around, and Johannes Rössel suggested moving the arithmetic outside of the measured region:

@echo off
setlocal

rem Remeber start time. Note that we don't look at the date, so this
rem calculation won't work right if the program run spans local midnight.
set t0=%time: =0%

rem do something here.... but probably with more care about quoting.
rem specifically, odd things will happen if any arguments contain 
rem precent signs or carets and there may be no way to prevent it.
%*

rem Capture the end time before doing anything else
set t=%time: =0%

rem make t0 into a scaler in 100ths of a second, being careful not 
rem to let SET/A misinterpret 08 and 09 as octal
set /a h=1%t0:~0,2%-100
set /a m=1%t0:~3,2%-100
set /a s=1%t0:~6,2%-100
set /a c=1%t0:~9,2%-100
set /a starttime = %h% * 360000 + %m% * 6000 + 100 * %s% + %c%

rem make t into a scaler in 100ths of a second
set /a h=1%t:~0,2%-100
set /a m=1%t:~3,2%-100
set /a s=1%t:~6,2%-100
set /a c=1%t:~9,2%-100
set /a endtime = %h% * 360000 + %m% * 6000 + 100 * %s% + %c%

rem runtime in 100ths is now just end - start
set /a runtime = %endtime% - %starttime%
set runtime = %s%.%c%

echo Started at %t0%
echo Ran for %runtime%0 ms

You could simplify the arithmetic and be a little more honest about the overall accuracy of this by not bothering with the 100ths of a second part. Here it is in action, assuming you have a sleep command or some other time waster:

C:> TIMER SLEEP 3
Script took 3000 ms to complete
C:> 

Edit: I revised the code and its description as suggested in a comment.

I think that when the NT team replaced COMMAND.COM with CMD.EXE, they thought they wouldn't get away with making it very different. But in effect, it is almost an entirely new language. Many of the old favorite commands have new features if the extensions are enabled.

One of those is SETLOCAL which prevents variables from modifying the caller's environment. Another is SET /A which gives you a remarkable amount of arithmetic. The principle trick I've used here is the new substring extraction syntax where %t:~3,2% means the two characters starting at offset 3 in the value of the variable named t.

For a real shocker, take a look at the full description of set (try SET /? at a prompt) and if that doesn't scare you, look at FOR /? and notice that it can parse text out of files...

Edit 2: Fixed mis-handling of time fields containing 08 or 09 reported by Frankie in comments. Tweaked a couple of things, and added some comments.

Note that there is a glaring oversight here that I'm probably not going to fix. It won't work if the command starts on a different day than it ends. That is, it will do some math related to the time of day and report a difference, but the difference won't mean much.

Fixing it to at least warn about this case is easy. Fixing it to do the right thing is harder.

Edit 3: Fixed error where the %h% is not set properly for single digit hours. This is due to %time% returning " 9:01:23.45". Notice the space. Using %time: =0% replaces the space with a leading zero and %h% will be set correctly. This error only occurred when a script ran from one single digit hour to the next.

Community
  • 1
  • 1
RBerteig
  • 38,361
  • 7
  • 80
  • 122
  • +1 for going the extra mile... can you explain the set /a business (booted to Linux currently) – ojblass Apr 11 '09 at 06:52
  • set /a does arithmetic in Batch. What he does here is reducing everything to 1/100 of a second. Afterwards you can simply subtract them since they're both just numbers. – Joey Apr 11 '09 at 07:07
  • You might want to do all the math after your command executed, though ... math in cmd isn't exactly fast so just store the start time and fiddle around with it after your command has ended so it doesn't get counted to the time of the command. Dunno whether it makes a difference, apparently not – Joey Apr 11 '09 at 07:10
  • 1
    @Johannes, good point... the start time accounting should be done after the end time snapshot. I'll revise the code. – RBerteig Apr 11 '09 at 07:14
  • set /a is an extension in the "new" CMD.EXE language that quietly replaced COMMAND.COM when NT was released. Lots of the old commands got new features, but it seems like noone noticed since there is hardly any documentation other than starting from CMD /? at a CMD prompt. – RBerteig Apr 11 '09 at 07:15
  • @RBerteig it's perfect. You just have a minor typo at `set c=%t:~8,2%` where it should read `set c=%t:~9,2%`. Great answer! – Frankie Dec 30 '10 at 05:27
  • @Frankie, good catch. I fixed it in both places, and caught a formatting glitch while I was there. Thanks. – RBerteig Dec 30 '10 at 08:59
  • @RBerteig the guy who ported bath to XP decided that leading 0 only should indicate an octal so the script fails sometimes. Can be easily solved by changing all offensive lines to something like this: `set h=1%t0:~0,2%-100`. (that adds and removes 100) – Frankie Jan 11 '11 at 18:35
  • 2
    @Frankie, thanks for the catch. Looks like SET/A follows C integer literal conventions. I fixed it and tested it during a bad octal minute. – RBerteig Jan 11 '11 at 20:27
  • Thanks @Community for noticing one more pitfall when attempting to do math on text in a language that never intended that to be possible, and starting from text that was never intended to be used for math. For the record, I'm still surprised this was possible to do with just a batch file in the first place. – RBerteig Apr 13 '11 at 18:01
  • This is great. To do use it to time another batch file, you *must* call `timer call batch.bat` or you don't get any output from the timing code. However, if my `batch.bat` file ends with an `@echo on` (best practices?), I get to see all your code after execution. There should be a second `@echo off` after your `%*`. – darda Aug 04 '14 at 16:55
  • @pelesl, Yes, `CALL` is needed to nest execution of one `BAT` inside another, which is arguably a bug in the language design since DOS 1.0. It would never have occurred to me to ever write `echo on` unless I *really* wanted to see the execution, mostly because the setting is so sticky and is not restored when returning to an outer batch file. Restoring `echo` isn't needed at the command prompt either. – RBerteig Aug 04 '14 at 17:38
4

This is kind of tangential, but it may help you out.

Microsoft has a timeit.exe program that works more or less like an enhanced version of the unix 'time' command. It comes in the Windows Server 2003 Resource Kit, and it has pretty much replaced all the times I've wanted to do something like what you're suggesting.

It may be worth a look.

Dan Olson
  • 21,303
  • 4
  • 37
  • 52
3

Excellent little routine. However, if the calculation spans across two days, the calculation is incorrect. The result needs subtracting from 24 hours (8640000 centiseconds).

Also remove one of the duplicate 'set' commands in the relevant line.

Note that regional settings affect the format of the TIME function, the decimal being a fullstop in UK rather than a comma.

The lines

rem we might have measured the time inbetween days

if %ENDTIME% LSS %STARTTIME% set set /A DURATION=%STARTTIME%-%ENDTIME%

need changing to

rem we might have measured the time across days

if %ENDTIME% LSS %STARTTIME% set /A DURATION=8640000 - (%STARTTIME% - %ENDTIME%)
2

Check out this script that will retrieve time through WMI so it's Regional Setting independent.

@echo off
::::::::::::::::::::::::::::::::::::::::::
::  TimeDiff v1.00 by LEVENTE ROG       ::
::       www.thesysadminhimself.com     ::
::::::::::::::::::::::::::::::::::::::::::
 
::[ EULA ]:::::::::::::::::::::::::::::::::::::::::::::::::::::::::
::  Feel free to use this script. The code can be redistributed  ::
::  and edited, but please keep the credits.                     ::
:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
 
::[ CHANGELOG ]::::::::::::::
::  v1.00 - First Version  ::
:::::::::::::::::::::::::::::
 
 
FOR /F "skip=1 tokens=1-6" %%A IN ('WMIC Path Win32_LocalTime Get Day^,Hour^,Minute^,Second /Format:table ^| findstr /r "."') DO (
 set Milisecond=%time:~9,2% 
 set Day=%%A
 set Hour=%%B
 set Minute=%%C
 set Second=%%D
)
set /a Start=%Day%*8640000+%Hour%*360000+%Minute%*6000+%Second%*100+%Milisecond%
 
::
::
:: PUT COMMANDS HERE
ping www.thesysadminhimself.com
::
::
 
FOR /F "skip=1 tokens=1-6" %%A IN ('WMIC Path Win32_LocalTime Get Day^,Hour^,Minute^,Second /Format:table ^| findstr /r "."') DO (
 set Day=%%A
 set Hour=%%B
 set Minute=%%C
 set Second=%%D
)
set Milisecond=%time:~9,2% 
set /a End=%Day%*8640000+%Hour%*360000+%Minute%*6000+%Second%*100+%Milisecond%
set /a Diff=%End%-%Start%
set /a DiffMS=%Diff%%%100
set /a Diff=(%Diff%-%DiffMS%)/100
set /a DiffSec=%Diff%%%60
set /a Diff=(%Diff%-%Diff%%%60)/60
set /a DiffMin=%Diff%%%60
set /a Diff=(%Diff%-%Diff%%%60)/60
set /a DiffHrs=%Diff%
 
:: format with leading zeroes
if %DiffMS% LSS 10 set DiffMS=0%DiffMS!%
if %DiffSec% LSS 10 set DiffMS=0%DiffSec%
if %DiffMin% LSS 10 set DiffMS=0%DiffMin%
if %DiffHrs% LSS 10 set DiffMS=0%DiffHrs%
 
echo %DiffHrs%:%DiffMin%:%DiffSec%.%DiffMS%
Brandon
  • 65,640
  • 30
  • 189
  • 218
  • +1 nice solution, but it is slow, each timestamp needs ~100ms-200ms (on my pc 2.4GHz Core2 Duo) – jeb Mar 11 '11 at 19:55
2

My own personal preference is to install Cygwin and use the time command but, if you actually have to do it as a batch file, you can't just subtract the strings, you have to treat them as parts.

The following script will time a 10-second ping command with the ability to cross a single day boundary without getting tied up in negative numbers. A slight enhancement would allow it to cross many day boundaries.

@echo off
setlocal enableextensions enabledelayedexpansion
set starttime=%time%
ping -n 11 127.0.0.1 >nul: 2>nul:
set endtime=%time%

set /a hrs=%endtime:~0,2%
set /a hrs=%hrs%-%starttime:~0,2%

set /a mins=%endtime:~3,2%
set /a mins=%mins%-%starttime:~3,2%

set /a secs=%endtime:~6,2%
set /a secs=%secs%-%starttime:~6,2%

if %secs% lss 0 (
    set /a secs=!secs!+60
    set /a mins=!mins!-1
)
if %mins% lss 0 (
    set /a mins=!mins!+60
    set /a hrs=!hrs!-1
)
if %hrs% lss 0 (
    set /a hrs=!hrs!+24
)
set /a tot=%secs%+%mins%*60+%hrs%*3600

echo End     = %endtime%
echo Start   = %starttime%
echo Hours   = %hrs%
echo Minutes = %mins%
echo Seconds = %secs%
echo Total   = %tot%

endlocal
paxdiablo
  • 772,407
  • 210
  • 1,477
  • 1,841
1

You can't do time arithmetic directly in batch scripting, so you'll either need an external program to calculate the time difference, or extract each part of the time and do arithmetic that way.

Take a look at the bottom of this forum thread for an example of how to do the latter. Excerpted here:

@echo off
cls
REM ================================================== ======
REM = Setting Date Time Format =
REM ================================================== ======
set DT=%DATE% %TIME%
set year=%DT:~10,4%
set mth=%DT:~4,2%
set date=%DT:~7,2%
set hour=%DT:~15,2%
set min=%DT:~18,2%
set sec=%DT:~21,2%
set newDT=%year%_%mth%_%date% %hour%%min%%sec%

set hour=14
set min=10

REM ===============================
REM = Getting End Time =
REM ===============================
set EndTime=%TIME%
set EndHour=%EndTime:~0,2%
set EndMin=%EndTime:~3,2%


REM ===============================
REM = Finding Difference =
REM ===============================
set /a Hour_Diff=EndHour - hour >nul
set /a Min_Diff=EndMin - min >nul


REM ===============================
REM = Hour Hand Change? =
REM ===============================
IF [%Hour_Diff]==[0] (

Set Duration=%Min_Diff%
) ELSE (

Set /a Duration=60-%Min_Diff% >nul
)


echo Start Time = %hour% : %min%
echo End Time = %EndHour% : %EndMin%
echo.
echo Min Diff = %Min_Diff%
echo.
echo time difference (mins) = %Duration%

I also assume it's a typo, but you do want to be sure to set endtime after processing.

lc.
  • 105,606
  • 20
  • 147
  • 176