See the comments at the end for corrected versions of the code.

I recently bought a new 250GB external hard disk (a Buffalo HD-HB250U2-1 USB 2.0 Drivestation 250GB External Hard Drive ) – it seems very quiet and fast. I decided to create a DOS batch file to do backups (I’ve never found a graphical backup program that I like and trust).

The copying itself is pretty straightforward – just use xcopy, supplying it with flags to copy file attributes and only to back up files that are newer than the existing backed-up versions.

The tricky bit was that I wanted to display the elapsed time. For example, if I choose to run the script automatically in the middle of the night, I’d like to know how long it took. Printing the current time at any point (e.g. at the start) is straightforward: ECHO %TIME% would do. However, I’m too lazy to work out the difference between two times by hand, and figured that it must be easy to get the batch file to do this automatically. Well, it wasn’t as easy as I thought, but after much faffing around with poorly documented DOS commands that don’t work the way I expected, I present the following method for displaying the elapsed time (note that I’ve used underscores at the end of lines where they are too wide to fit here). Enjoy.

:: Set START_TIMESTAMP as the number of
:: minutes so far today
FOR /F "tokens=1,2 delims=:" %%t in ('time/t') do SET /a _
      START_TIMESTAMP=%%t * 60 + %%u

:: ........................................
:: ... do time-consuming processes here ...
:: ........................................

:: Work out the elapsed time expressed in minutes
FOR /F "tokens=1,2 delims=:" %%t in ('time/t') do SET /a _
      END_TIMESTAMP=%%t * 60 + %%u

:: Do the correct calculation to account for roll-over
:: (assumes the whole elapsed time is always less than
:: 24 hours)
IF %END_TIMESTAMP% GEQ %START_TIMESTAMP% (
   SET /a ELAPSED_TIME=%END_TIMESTAMP% - %START_TIMESTAMP%
)
:: ELSE
IF %END_TIMESTAMP% LSS %START_TIMESTAMP% (
   SET /a ELAPSED_TIME=%END_TIMESTAMP% + 1440 - _
         %START_TIMESTAMP%
)

:: Split elapsed time into hours and minutes
SET /a ELAPSED_HOURS=%ELAPSED_TIME% / 60
SET /a ELAPSED_MINUTES=%ELAPSED_TIME% - _
      (%ELAPSED_HOURS% * 60)
:: The following could be piped to a log file, if required
ECHO Backup took %ELAPSED_HOURS% hours and _
      %ELAPSED_MINUTES% minutes.

:: Tidy up
SET START_TIMESTAMP=
SET END_TIMESTAMP=
SET ELASPED_TIME=
SET ELASPED_HOURS=
SET ELASPED_MINUTES=

The original version of this article on Blogger attracted a number of useful comments, so I’ve brought these across too.

11 Comments:

Anonymous Anonymous said...

Does it work at e.g. 08:08, 09:09 etc . ? :)

4:32 pm  
Blogger Greg said...

This does not work if any of the digits in the time stamp are 08 or 09. When that is true, the numeric variables are set to 0 instead of the time in seconds.

8:24 pm  
Blogger Eyahlin said...

Hi!

THANKS! This is grand!

I picked up from where you left, fixed the octal conversion bug, and did some optimization tweaks.

Hope this helps!


@echo off
:: Clock in START_TIME
set START_TIME=%TIME%

:: Do process here!
%*

:: Clock in END_TIME
set END_TIME=%TIME%

:: Now that the timestamps are recorded,
:: Fix possible bug where 08 and 09 are treated erroneously from octal conversion
:: Set START_TIMESTAMP as the number of seconds so far today
if %START_TIME:~0,1% EQU 0 (set /a START_TIMESTAMP=%START_TIME:~1,1%*3600) ELSE (set /a START_TIMESTAMP=%START_TIME:~0,2%*3600)
if %START_TIME:~3,1% EQU 0 (set /a START_TIMESTAMP=%START_TIMESTAMP%+%START_TIME:~4,1%*60) ELSE (set /a START_TIMESTAMP=%START_TIMESTAMP%+%START_TIME:~3,2%*60)
if %START_TIME:~6,1% EQU 0 (set /a START_TIMESTAMP=%START_TIMESTAMP%+%START_TIME:~7,1%) ELSE (set /a START_TIMESTAMP=%START_TIMESTAMP%+%START_TIME:~6,2%)
:: Set END_TIMESTAMP as the number of seconds so far today
if %END_TIME:~0,1% EQU 0 (set /a END_TIMESTAMP=%END_TIME:~1,1%*3600) ELSE (set /a END_TIMESTAMP=%END_TIME:~0,2%*3600)
if %END_TIME:~3,1% EQU 0 (set /a END_TIMESTAMP=%END_TIMESTAMP%+%END_TIME:~4,1%*60) ELSE (set /a END_TIMESTAMP=%END_TIMESTAMP%+%END_TIME:~3,2%*60)
if %END_TIME:~6,1% EQU 0 (set /a END_TIMESTAMP=%END_TIMESTAMP%+%END_TIME:~7,1%) ELSE (set /a END_TIMESTAMP=%END_TIMESTAMP%+%END_TIME:~6,2%)

:: Work out the elapsed time expressed in seconds
:: Do the correct calculation to account for roll-over
:: (assumes the whole elapsed time is always less than 24 hours)
IF %END_TIMESTAMP% GEQ %START_TIMESTAMP% (
SET /a ELAPSED_TIME=%END_TIMESTAMP% - %START_TIMESTAMP%
) ELSE (
:: ELSE IF %END_TIMESTAMP% LSS %START_TIMESTAMP% (
SET /a ELAPSED_TIME=%END_TIMESTAMP% + 86400 - %START_TIMESTAMP%
)

:: Split elapsed time into hours, minutes and seconds
SET /a ELAPSED_HOURS=%ELAPSED_TIME% / 3600
SET /a ELAPSED_MINUTES=(%ELAPSED_TIME% - (%ELAPSED_HOURS% * 3600)) / 60
SET /a ELAPSED_SECONDS=%ELAPSED_TIME% - (%ELAPSED_HOURS% * 3600) - (%ELAPSED_MINUTES% * 60)

:: The following could be piped to a log file, if required
echo.
ECHO Process took %ELAPSED_HOURS% hours, %ELAPSED_MINUTES% minutes, and %ELAPSED_SECONDS% seoonds.

:: Tidy up
SET START_TIME=
SET END_TIME=
SET START_TIMESTAMP=
SET END_TIMESTAMP=
SET ELAPSED_TIME=
SET ELAPSED_HOURS=
SET ELAPSED_MINUTES=
SET ELAPSED_SECONDS=

4:29 am  
Anonymous Matthew said...

Thanks eyahlin

I've been meaning to look into this for a while, but as I don't use the original backup script any more I haven't got round to it. Looks like you've saved me the bother!

Thanks for posting your solution for others to share.

3:59 pm  
Blogger S said...

When time is between 0:01 to 9:59am, The Octal conversion section returns this error.

0 was unexpected at this time.
C:\>if EQU 0 (set /a START_TIMESTAMP=1*3600) ELSE (set /a START_TIMESTAMP= 1*3600)

I think the time command is not returning a leading zero, causing this error.

10:53 pm  
Blogger S said...

Detect leading "blank" in the hour conversion will work around the "0 value unexpected issue". Change
from
if %START_TIME:~0,1% EQU 0
to
if "%START_TIME:~0,1%"==" "

Do the same for END_TIME.

12:25 am  
Anonymous Anonymous said...

There are more efficient ways of doing this. I use the following syntax in my scripts.

@Call :BeginTime
Echo %TimeStamp% - Begin Session.
%*
@Call :EndTime
Echo Process took %ELAPSED_SEC% seconds.
Echo %TimeStamp% - End Session.

It works more efficiently because you can create new EndTimes or StartTimes at any moment. So if you only want to know how each segment of your session took, that can be accomplished. If want to know how long your entire session took, that can be accomplished as well.

I might post my script here if I remember.

5:05 am  
Anonymous Anonymous said...

Oh, I actually have incomplete copy of my script on me. Here ya go. Works on WinNT only. Won't actually work in DOS, but it can be modified to work in DOS. Also, you'll notice that EndTime is incomplete. It will work, but it will only generate the total amount of seconds an action took.

In my newest version (not posted), I have added in a few more conditional statements to deduce the days, hours, minutes, and relative seconds. It is all converted into a single statement (%ELAPSED_TIME%). I'll post that version if I remember. Until then, you guys are stuck with this.



:Timestamp
@REM * Create the date and time elements.
For /f "tokens=1-7 delims=.:/-, " %%i In ('Echo exit^|cmd /q /k"prompt $D $T"') Do (
For /f "tokens=2-4 delims=/-,() skip=1" %%a In ('Echo.^|date') Do (
Set dow=%%i
Set %%a=%%j
Set %%b=%%k
Set %%c=%%l
Set hh=%%m
Set min=%%n
Set ss=%%o
)
)
Set TimeStamp=%yy%%mm%%dd%%hh%%min%%ss%
@Goto :EOF

:BeginTime
@Call :TimeStamp
Set /A START_SEC=%ss%
Set /A START_MIN=%mm% * 60
Set /A START_HOUR=%hh% * 3600
Set /A START_DAY=%dd% * 86400
@Goto :EOF

:EndTime
@Call :TimeStamp
Set ELAPSED_SEC=0
Set ELAPSED_MIN=0
Set ELAPSED_HOUR=0
Set ELAPSED_DAY=0
Set /A END_SEC=%ss%
Set /A END_MIN=%mm% * 60
Set /A END_HOUR=%hh% * 3600
Set /A END_DAY=%dd% * 86400
Set /A ELAPSED_SEC=%END_DAY% + %END_HOUR% + %END_MIN% + %END_SEC% - %START_DAY% - %START_HOUR% - %START_MIN% - %START_SEC%

@REM ending this thing early because the rest is incomplete.
@Goto :EOF

If ELAPSED_SEC GTE 86400 (
Set /A ELAPSED_DAY=%ELAPSED_SEC%/86400
Set /A TMP_NUM=86400*%ELAPSED_DAY%
Set /A ELAPSED_SEC=%ELAPSED_SEC% - %TMP_NUM%
)
If %ELAPSED_SEC% GTE 3600 (
Set /A ELAPSED_HOUR=%ELAPSED_SEC%/3600
Set /A TMP_NUM=3600*%ELAPSED_HOUR%
Set /A ELAPSED_SEC=%ELAPSED_SEC% - %TMP_NUM%
)
If %ELAPSED_SEC% GTE 60 (
Set /A ELAPSED_MIN=%ELAPSED_SEC%/60
Set /A TMP_NUM=60*%ELAPSED_MIN%
Set /A ELAPSED_SEC=%ELAPSED_SEC% - %TMP_NUM%
)
If %ELAPSED_DAY% GTE 2 Set TEXT_DAY=%ELAPSED_DAY% days,
If %ELAPSED_DAY% EQU 1 Set TEXT_DAY=%ELAPSED_DAY% day,
If %ELAPSED_DAY% EQU 0 Set TEXT_DAY=
If %ELAPSED_HOUR% GTE 2 Set TEXT_HOUR=%ELAPSED_HOUR% hours,
If %ELAPSED_HOUR% EQU 1 Set TEXT_HOUR=%ELAPSED_HOUR% hour,
If %ELAPSED_HOUR% EQU 0 Set TEXT_HOUR=
If %ELAPSED_MIN% GTE 2 Set TEXT_MIN=%ELAPSED_MIN% minutes, and
If %ELAPSED_MIN% EQU 1 Set TEXT_MIN=%ELAPSED_MIN% minute, and
If %ELAPSED_MIN% EQU 0 Set TEXT_MIN=
If %ELAPSED_SEC% GTE 2 Set TEXT_SEC=%ELAPSED_SEC% seconds
If %ELAPSED_SEC% EQU 1 Set TEXT_SEC=%ELAPSED_SEC% second
If %ELAPSED_SEC% EQU 0 Set TEXT_SEC=%ELAPSED_SEC% seconds

Set TEXT_ELAPSED=%TEXT_DAY%%TEXT_HOUR%%TEXT_MIN%%TEXT_SEC%

@Goto :EOF

5:13 am  
Anonymous Anonymous said...

Hey this works great, thanks to you and your commenters.

9:41 pm  
Blogger George Camann said...

Here's a new way that corrects the problem if you run it before 10AM ;-)
Also shows how to create a time processing batch file called from another file.
==file1.bat==
:: Clock in START_TIME
set START_TIME=%TIME%
:: Introduce delay, do your work here
pause
:: Call the time processing script
proc_time %START_TIME%
exit /b 1
==/file1.bat==
==proc_time.bat==
:: Make sure we have an input before proceeding.
if %1.==. GOTO NO_ARG

GOTO NO_ARG_SKIP
:NO_ARG
echo No start time passed in
exit /b 1
:NO_ARG_SKIP

:: Clock in START_TIME
set START_TIME=%1

:: ================ CALCULATE ELAPSED TIME ===================
:: Clock in END_TIME
set END_TIME=%TIME%

:: Parse the time string for HMS
for /f "tokens=1,2,3 delims=:. " %%a in ("%START_TIME%") do (
set START_HOUR=%%a
set START_MINUTE=%%b
set START_SECOND=%%c
)
if {%START_HOUR:~0,1%}=={0} set START_HOUR=%START_HOUR:~1%
if {%START_MINUTE:~0,1%}=={0} set START_MINUTE=%START_MINUTE:~1%
if {%START_SECOND:~0,1%}=={0} set START_SECOND=%START_SECOND:~1%
:: Reduce HMS into pure seconds
set /a START_TIMESTAMP=%START_HOUR%*3600+%START_MINUTE%*60+%START_SECOND%

:: Parse the time string for HMS
for /f "tokens=1,2,3 delims=:. " %%a in ("%END_TIME%") do (
set END_HOUR=%%a
set END_MINUTE=%%b
set END_SECOND=%%c
)
if {%END_HOUR:~0,1%}=={0} set END_HOUR=%END_HOUR:~1%
if {%END_MINUTE:~0,1%}=={0} set END_MINUTE=%END_MINUTE:~1%
if {%END_SECOND:~0,1%}=={0} set END_SECOND=%END_SECOND:~1%
:: Reduce HMS into pure seconds
set /a END_TIMESTAMP=%END_HOUR%*3600+%END_MINUTE%*60+%END_SECOND%

:: Do the correct calculation to account for roll-over
:: (assumes the whole elapsed time is always less than 24 hours)
IF %END_TIMESTAMP% GEQ %START_TIMESTAMP% (
SET /a ELAPSED_TIME=%END_TIMESTAMP% - %START_TIMESTAMP%
) ELSE (
:: ELSE IF %END_TIMESTAMP% LSS %START_TIMESTAMP% (
SET /a ELAPSED_TIME=%END_TIMESTAMP% + 86400 - %START_TIMESTAMP%
)

:: Split elapsed time into hours, minutes and seconds
SET /a ELAPSED_HOURS=%ELAPSED_TIME% / 3600
SET /a ELAPSED_MINUTES=(%ELAPSED_TIME% - (%ELAPSED_HOURS% * 3600)) / 60
SET /a ELAPSED_SECONDS=%ELAPSED_TIME% - (%ELAPSED_HOURS% * 3600) - (%ELAPSED_MINUTES% * 60)

:: The following could be piped to a log file, if required
echo.
ECHO Elapsed Time = %ELAPSED_HOURS%:%ELAPSED_MINUTES%:%ELAPSED_SECONDS%
echo Elapsed Seconds = %ELAPSED_TIME%

:: Tidy up
SET START_TIME=
SET END_TIME=
SET START_TIMESTAMP=
SET END_TIMESTAMP=
SET ELAPSED_TIME=
SET ELAPSED_HOURS=
SET ELAPSED_MINUTES=
SET ELAPSED_SECONDS=
==/proc_time.bat==
Hope this helps someone.

2:44 pm  
Anonymous Rduke15 said...

OMG, cmd.exe is really insane!

9:59 pm  

Post a Comment

<< Home