Delayed Expansion Inside Loop with Arithmetic Operations on Time Values - batch-file

I am relatively new to batch files, and I have been trying to get the following timing routine (inspired from Arithmetic operations with HH:MM:SS times in batch file) to work:
set "startTime=%time: =0%"
#rem Removing milliseconds information
set startTime=%startTime:~0,-3%
setlocal EnableDelayedExpansion
for %%i in (1 2 3 4 5) do (
timeout /T 3 /NOBREAK
set endTime=!time: =0!
set endTime=!endTime:~0,-3!
set /A "ss=(((1%endTime::=-100)*60+1%-100)-(((1%startTime::=-100)*60+1%-100)"
set /A "hh=ss/3600+100,ss%%=3600,mm=ss/60+100,ss=ss%%60+100"
#rem Get format in HH:MM:SS (maybe H:MM:SS after midnight.. not tested yet)
echo Start time: %startTime%
echo End time: !endTime!
#rem Issue here: Always get the same output despite delayedExpansion active
echo Diff time: !ss!
echo.
#rem Issue here: Not getting expected values
echo Elapsed time: !hh:~1!:!mm:~1!:!ss:~1!...
echo.
)
endlocal
I am not quite sure why the output value for the time difference is always the same despite the delayed expansion. Also, the formatted time does not give me the correct values.
Thanks for your help!

First, thanks for the helpful comments which helped me find a solution to my question. In case it might help someone else, I am posting the solution here.
As mentioned in the comments, the key was to determine the formatting used on my operating system.
The code that works for me is below, inspired from https://www.py4u.net/discuss/2286184:
#rem Check that the format of %TIME% is HH:MM:SS.CS for example 23:59:59.99
echo "%TIME%"
set STARTTIME=%TIME%
#rem convert start time to centiseconds
set /A STARTTIME=(1%STARTTIME:~0,2%-100)*360000 + (1%STARTTIME:~3,2%-100)*6000 + (1%STARTTIME:~6,2%-100)*100 + (1%STARTTIME:~9,2%-100)
setlocal EnableDelayedExpansion
for %%i in (1 2 3 4 5) do (
timeout /T 3 /NOBREAK
set ENDTIME=!TIME!
#rem convert ENDTIME to centiseconds
#rem I found that double-quotes with set /A and numbers are needed inside the for loop
set /A "ENDTIME=(1!ENDTIME:~0,2!-100)*360000 + (1!ENDTIME:~3,2!-100)*6000 + (1!ENDTIME:~6,2!-100)*100 + (1!ENDTIME:~9,2!-100)"
#rem calculating the duratyion is easy
set /A DURATION=!ENDTIME!-%STARTTIME%
#rem Adjust for cases where timings over multiple days
if !ENDTIME! LSS %STARTTIME% set set /A DURATION=%STARTTIME%-!ENDTIME!
#rem now break the centiseconds down to hours, minutes, seconds and the remaining centiseconds
set /A "DURATIONH=!DURATION! / 360000"
set /A "DURATIONM=(!DURATION! - !DURATIONH!*360000) / 6000"
set /A "DURATIONS=(!DURATION! - !DURATIONH!*360000 - !DURATIONM!*6000) / 100"
set /A "DURATIONHS=(!DURATION! - !DURATIONH!*360000 - !DURATIONM!*6000 - !DURATIONS!*100)"
#rem Enforce double-digit format
if !DURATIONH! LSS 10 set DURATIONH=0!DURATIONH!
if !DURATIONM! LSS 10 set DURATIONM=0!DURATIONM!
if !DURATIONS! LSS 10 set DURATIONS=0!DURATIONS!
if !DURATIONHS! LSS 10 set DURATIONHS=0!DURATIONHS!
#rem Outputing timings
echo STARTTIME: %STARTTIME% centiseconds
echo ENDTIME: !ENDTIME! centiseconds
echo DURATION: !DURATION! in centiseconds
echo Elapsed time: !DURATIONH!:!DURATIONM!:!DURATIONS!.!DURATIONHS!
)
endlocal

Related

Subtraction of a number from a variable in a batch script

I'm new to batch scripting, so please be lenient with this question. When I'm subtracting 1 from 'TodayDay' variable, the value is not getting updated. Below is the line.
set /a "TodayDay=%TodayDay%-1"
My use case is to find if today's date is less than the 'lastOpenedDate' variable, I want to set the 'lastOpenedDate' to yesterday's
set lastOpenedDate=2017-12-22
IF %TodayYear%-%TodayMonth%-%TodayDay% LSS %lastOpenedDate% (
echo Before Subtraction TodayDay is %TodayDay%
set /a "TodayDay=%TodayDay%-1"
echo After Subtraction TodayDay is %TodayDay%
)
When I ran the above code, The output is:
Before Subtraction TodayDay is 20
After Subtraction TodayDay is 20
I got the other variable values from the below-mentioned code
for /F "skip=1 delims=" %%F in ('
wmic PATH Win32_LocalTime GET Day^,Month^,Year /FORMAT:TABLE
') do (
for /F "tokens=1-3" %%L in ("%%F") do (
set TodayDay=0%%L
set TodayMonth=0%%M
set TodayYear=%%N
)
)
set TodayDay=%TodayDay:~-2%
set TodayMonth=%TodayMonth:~-2%
What am I doing wrong?
You need to search SO using the top bar for delayed expansion. It's #1 FAQ.
change
set /a "TodayDay=%TodayDay%-1"
echo After Subtraction TodayDay is %TodayDay%
)
to
set /a "TodayDay=%TodayDay%-1"
)
echo After Subtraction TodayDay is %TodayDay%
which will make sense once you familiarise yourself with delayed expansion.
Now the next problem you'll run into (which won't show itself until the 8th or 9th of the month) is that in batch a leading 0 means "octal" so - you actually need
set /a "TodayDay=1%TodayDay%-1"
)
set "TodayDay=%TodayDay:~-2"
echo After Subtraction TodayDay is %TodayDay%
which adds 100 to the day number by stringing the 1 in front of the day number, then you need to get the last 2 characters.

Loop the command continuously (without pausing) for "X" seconds

Currently I am working on a batch file to execute a command continuously for X no. of seconds, and after elapse of X, I wish to redirect it using goto function.
This is the closest I have got to what I intend the code to do.
:MainMenu
set yourno=
set /p yourno=Input your number:
set /p looptime=Input time:
goto PerformCalc
:PerformCalc
set /a yourno= %yourno%+1
timeout 1
set /a looptime=%looptime%-1
goto CheckLoop
:CheckLoop
if %looptime% equ 0 goto FinishScreen
goto PerformCalc
:FinishScreen
echo Congratulations Your no. is %yourno%
echo Operations completed in %looptime% seconds
pause
Now the problem with the above code is if I set the loop time for 10 seconds, it will perform only 10 operations. But that is not something I intend to do. I realize that I have inputted timeout 1 in my code and reducing it by 1 everytime 1 second passes. However this is because I am unable to find a way to continuously loop the code for 10 seconds, without using timeout 1 thus pausing it to perform only 10 operations.
If you still do not get what I mean, here is the logical flow the code should perform (say for eg. the user inputs looptime as 10 seconds
For Time=10seconds
Do {set /a yourno= %yourno%+1 continuously}
After Time of 10 seconds has elapsed redirect to :FinishScreen
Thankyou for your help.
#echo off
setlocal
rem Get end time
set seconds=10
for /F "tokens=1-4 delims=:.," %%a in ("%time: =0%") do set /A "endTime=1%%a%%b%%c%%d+seconds*100"
echo Start: %time%
echo Working %seconds% seconds, please wait...
set yourNo=0
:loop
set /A yourNo+=1
rem Check if end time had been reached
for /F "tokens=1-4 delims=:.," %%a in ("%time: =0%") do if 1%%a%%b%%c%%d lss %endTime% goto loop
echo End: %time%
echo This program could complete %yourNo% loops in 10 seconds
This method may fail if the processing time pass over midnight, but a simple adjustment can solve this point, if needed...

Script works in CMD but not in batch

I have this script below to track process time of each part of my script. When I run this in a CMD prompt, it works perfectly, but when put into a .bat, it fails to run. This is also constructed in two IF statements.
Also shout out to #driblio who I got this from.
#echo off
set starttime=%TIME%
set startcsec=%STARTTIME:~9,2%
set startsecs=%STARTTIME:~6,2%
set startmins=%STARTTIME:~3,2%
set starthour=%STARTTIME:~0,2%
set /a starttime=(%starthour%*60*60*100)+(%startmins%*60*100)+(%startsecs%*100)+(%startcsec%)
::Process
set endtime=%time%
set endcsec=%endTIME:~9,2%
set endsecs=%endTIME:~6,2%
set endmins=%endTIME:~3,2%
set endhour=%endTIME:~0,2%
if %endhour% LSS %starthour% set /a endhour+=24
set /a endtime=(%endhour%*60*60*100)+(%endmins%*60*100)+(%endsecs%*100)+(%endcsec%)
set /a timetaken= ( %endtime% - %starttime% )
set /a timetakens= %timetaken% / 100
set timetaken=%timetakens%.%timetaken:~-2%
echo.
echo Took: %timetaken% sec.
Read entire set /?
Numeric values are decimal numbers, unless prefixed by 0x for
hexadecimal numbers, and 0 for octal numbers. So 0x12 is the same
as 18 is the same as 022. Please note that the octal notation can
be confusing: 08 and 09 are not valid numbers because 8 and
9 are not valid octal digits.
So echo "%startttime%" could return something like
" 9:08:09.04"
↑↑ invalid in set /a 08
↑↑ invalid in set /a 09
↑↑ valid in set /a 04
Use
#ECHO OFF
SETLOCAL EnableExtensions DisableDelayedExpansion
set starttime=%TIME%
rem debugging output echo "%starttime%"
set /A startcsec=1%STARTTIME:~9,2% %% 100
set /A startsecs=1%STARTTIME:~6,2% %% 100
set /A startmins=1%STARTTIME:~3,2% %% 100
set starthour=%STARTTIME:~0,2%
set /a starttime=(%starthour%*60*60*100)+(%startmins%*60*100)+(%startsecs%*100)+(%startcsec%)
rem debugging output set start
::Process
:: pause to ensure that endtime >> starttime
pause
set endtime=%time%
rem debugging output echo "%endtime%"
set /A endcsec=1%endTIME:~9,2% %% 100
set /A endsecs=1%endTIME:~6,2% %% 100
set /A endmins=1%endTIME:~3,2% %% 100
set endhour=%endTIME:~0,2%
if %endhour% LSS %starthour% set /a endhour+=24
set /a endtime=(%endhour%*60*60*100)+(%endmins%*60*100)+(%endsecs%*100)+(%endcsec%)
rem debugging output set end
set /a timetaken= ( %endtime% - %starttime% )
set /a timetakens= %timetaken% / 100
set timetaken=%timetakens%.%timetaken:~-2%
echo.
echo Took: %timetaken% sec.
Please note that above code snippet still depends on locale and user preferences. For locale and user preference independent solution, use output from wmic os get localdatetime rather than %time% as follows:
for /F %%G in ('wmic OS get localdatetime /Value ^| find "="') do #set %%G
Then, %localdatetime% would contain something like 20170413030820.447000+120 in (fixed-length) CIM_DATETIME format yyyymmddHHMMSS.mmmmmmsUUU where the fields in the format are
yyyy Four-digit year (0000 through 9999). Your implementation can restrict the supported range. For example, an implementation can
support only the years 1980 through 2099.
mm Two-digit month (01 through 12).
dd Two-digit day of the month (01 through 31). This value must be appropriate for the month. For example, February 31 is not valid.
However, your implementation does not have to check for valid data.
HH Two-digit hour of the day using the 24-hour clock (00 through 23).
MM Two-digit minute in the hour (00 through 59).
SS Two-digit number of seconds in the minute (00 through 59).
mmmmmm Six-digit number of microseconds in the second (000000 through 999999). Your implementation does not have to support
evaluation using this field. However, this field must always be
present to preserve the fixed-length nature of the string.
s Plus sign (+) or minus sign (-) to indicate a positive or negative offset from Coordinated Universal Times (UTC).
UUU Three-digit offset indicating the number of minutes that the originating time zone deviates from UTC. For WMI, it is encouraged,
but not required, to convert times to GMT (a UTC offset of zero).

Missing operand error in batch statement with variable

I'm getting a syntax error on the timeout command supposedly because of a "missing operand". But I don't see any operand missing.
rem description for the user to read
echo Insert time in minutes:
rem setting default (1 hour)
set /a timeto=3600
rem asking user for input (integer)
set /p timeto=
rem converting minutes to seconds
set /a timeto=%timeto%*60
rem command based on the inputted value
timeout /t %timeto% /nobreak
Invalid syntax. Commenting commands inline is not possible in cmd CLI nor a batch scripts:
==>set /a timeto=3600 ::setting default time (1 hour)
Missing operator.
==>set /a timeto=3600
3600
==>
Note that %timeTo%*60 exceeds timeout /T valid range -1 to 99999 for default 3600*60.
If your code snippet appears enclosed in () parentheses, then use Delayed Expansion.
#ECHO ON >NUL
#SETLOCAL EnableExtensions EnableDelayedExpansion
if 1==1 (
rem description for the user to read
echo Insert time in minutes:
rem setting default time (1 hour = 60 minues)
set /a timeTo=60
rem asking user for input (integer)
set /p timeTo=
rem converting minutes to seconds - erroneous
set /a timeTo=%timeTo%*60
rem converting minutes to seconds - right approach
set /a timeTo=!timeTo!*60
rem command based on the inputted value
echo timeout /t !timeTo! /nobreak
)
Output: note that set /a timeTo=%timeTo%*60 line causes the Missing operand error as results to erroneous set /a timeTo=*60 in the parse time.
==>D:\bat\SO\32410773.bat
==>if 1 == 1 (
rem description for the user to read
echo Insert time in minutes:
rem setting default time (1 hour = 60 minues)
set /a timeTo=60
rem asking user for input (integer)
set /p timeTo=
rem converting minutes to seconds - erroneous
set /a timeTo=*60
rem converting minutes to seconds - right approach
set /a timeTo=!timeTo!*60
rem command based on the inputted value
echo timeout /t !timeTo! /nobreak
)
Insert time in minutes:
Missing operand.
timeout /t 3600 /nobreak
==>
Batch does not support inline comments.All comments need to be in separate line:
::description for the user to read
echo Insert time in minutes:
::setting default time (1 hour)
set /a timeto=3600
::asking user for input (integer)
set /p timeto=
::converting minutes to seconds
set /a timeto=%timeto%*60
::command based on the inputted value
timeout /t %timeto% /nobreak

Creating a geometrically decaying pause loop

So I am working on creating a batch file game analogious to a "Memory" game. (ie where the player is presented a list of objects for a short time, then asked to repeat the pattern)
My problem comes in how to decrease the time the pattern is exposed to the player as the round # increases.
Here is my current code:
#echo off
set /a y=50
set /a x=1000
:foo
set /a y=%y% + %y%
set /a x= %x% - %y%
echo %y%
echo %x%
ping -n 10 -w %x% 127.0.0.1 > nul
goto foo
When run, the above code does present x and y values that change as expected, however the wait time is always the same. Why is this and how can I fix it?
Thank you for your time.
Firstly why dont you use sleep? It would work fine (type sleep /? for more info)
However, here is another way of doing this with for /l loops
#echo off
setlocal enabledelayedexpansion
set score=0
title Memory Test : Current Score = !score!
for /l %%a in (0,1,20) do (
Rem In the above sequence, increase 20 to the amount of times you want the test to be performed
set number[%%a] = !random!!random!
echo Number: !number[%%a]!
set /a wait=21-%%a
set /a wait=!wait!*1000/4
sleep -m !wait!
cls
set /p "input=What was the last number youy saw? "
if !number[%%a]! equ "!input!" (
set /a score=!score!+1
Echo Correct !
title Memory Test : Current Score = !score!
)else(
Echo Incorrect! Coreect Answer = !number[%%a]!
)
)
echo Calculating score...
pause
cls
echo.
if %score% leq 14 set msg="Nice Try! But you can do better!"
if %score% geq 15 set msg="Good Job! Your on your way to the top!"
if %score% equ 20 set msg="Your So Close! Almost a perfect socre!"
if %score% equ 21 set msg="You got a perfect score! Woderful!"
Echo %score%/21 : %msg%
echo.
pause
And that should work fine. Note you can change how long the test goes for, but for the first game they'll have a bit more then 5 seconds to study the question, and in the last round a quarter of a second!
Mona
In order for the ping/wait trick to work, the ip address must not exist. 127.0.0.1 is your own computer, so it has no opportunity to timeout since the ping response is successful and immediate.
Instead, choose an ip address that doesn't exist. E.g. 10.20.30.40 (assuming that doesn't exist.)
You can adjust the 10 in this to give approximately second resolution. Is that sufficient for your code?
ping -n 10 127.0.0.1 > nul
This is another option for later windows.
timeout /t 10 >nul

Resources