1

In one of my batch scripts I need to calculate the duration of an interval in a video file. First the user is asked to input the start and end times:

set /p StartPosition=Start position (HH:MM:SS):
set /p EndPosition=End position (HH:MM:SS):

Then, I would like the batch script to calculate the duration in between.

How can I subtract %StartPosition% from %EndPosition% like this, for example:

00:10:40 - 00:10:30 = 00:00:10

The reason why I can't figure out how to do this is because these numbers are separated by colons.

Edit: This question is different to this question because I do not need the scrip to treat the numbers as time values.

3
  • 1
    Possible duplicate of How to measure execution time of command in windows command line? Commented Mar 5, 2017 at 0:55
  • 2
    What if the times were 11:05 - 10:45? Would you expect an answer of :20? That is not "standard" math. It requires awareness of 60-seconds in a minute. Commented Mar 5, 2017 at 1:04
  • 1
    You have to strip the leading zeros from each field for the set command to do the math correctly. Basically you will need to convert the entire time to seconds and then have a function that converts it back to hours minutes and seconds. Personally I would go with a hybrid batch file with some embedded Jscript. Commented Mar 5, 2017 at 1:28

9 Answers 9

15
@echo off
setlocal

set /p "StartPosition=Start position (HH:MM:SS): "
set /p "EndPosition=End position (HH:MM:SS):   "

set /A "ss=(((1%EndPosition::=-100)*60+1%-100)-(((1%StartPosition::=-100)*60+1%-100)"
set /A "hh=ss/3600+100,ss%%=3600,mm=ss/60+100,ss=ss%%60+100"

echo Duration=%hh:~1%:%mm:~1%:%ss:~1%

EDIT: Some explanations added

This program use the usual method to convert a time in HH:MM:SS format into a number of seconds via the standard formula: seconds = (HH*60+MM)*60+SS. However, the set /A command consider the numbers that start with 0 as written in octal base, and hence 08 and 09 would be invalid octal numbers. To avoid this problem, a digit 1 is placed before expand the number and a 100 is subtracted after, so if HH=08 then 1%HH%-100 correctly gives 8; that is:

set /A seconds = ((1%HH%-100)*60+1%MM%-100)*60+1%SS%-100

There are several methods to split a time given in HH:MM:SS format into its three parts. For example, if we take set EndPosition=HH:MM:SS as base, then we may use a for /F command this way:

for /F "tokens=1-3 delims=:" %%a in ("%EndPosition%") do (
   set /A "seconds=((1%%a-100)*60+1%%b-100)*60+1%%c-100"
)

In this program a different method is used. If we match the original EndPosition=HH:MM:SS string with the desired formula, we may construct this mapping scheme:

     HH       :      MM       :      SS

((1  HH  -100)*60+1  MM  -100)*60+1  SS  -100

In other words: if we replace the colons of the original string by -100)*60+1 and insert ((1 at beginning and -100 at end, we obtain the desired formula; that is:

set /A "seconds=((1%EndPosition::=-100)*60+1%-100"

This is a very efficient method that even allows to replace both EndPosition and StartPosition strings in the same formula (enclosing both parts in parentheses) and directly subtract them:

set /A "ss=(((1%EndPosition::=-100)*60+1%-100)-(((1%StartPosition::=-100)*60+1%-100)"

You may cancel the @echo off command and run the program to review the exact formula that is evaluated after the values of the variables are replaced. For example, when StartPosition=00:10:30 and EndPosition=00:10:40, this is the expression that is evaluated:

set /A "ss=(((100-100)*60+110-100)*60+140-100)-(((100-100)*60+110-100)*60+130-100)"

Just to complete this description, this is the "standard" way to evaluate the same formula using a for /F command:

for /F "tokens=1-6 delims=:" %%a in ("%EndPosition%:%StartPosition%") do (
   set /A "ss=(((1%%a-100)*60+1%%b-100)*60+1%%c-100)-(((1%%d-100)*60+1%%e-100)*60+1%%f-100)"
)

The opposite conversion from number of seconds to HH:MM:SS parts is straightforward:

HH=SS/3600, rest=SS%3600, MM=rest/60, SS=rest%60

However, each part in the result must be displayed with two digits, but this formatting may be achieved in a very simple way. Instead of insert three if commands that check if each part is less than 10 and insert a padding zero in such a case, the number 100 is just added to the parts (converting an 8 into 108, for example), and when each part is displayed the first digit is omitted (so just 08 is shown). This is a very efficient method to format numbers that may be performed in the same set /A command used to obtain the parts. For example:

set /A "hh=ss/3600+100,ss%%=3600,mm=ss/60+100,ss=ss%%60+100"

echo Duration=%hh:~1%:%mm:~1%:%ss:~1%

In this way, the conversion of two times into two number of seconds, their subtraction and the opposite conversion and formatting to HH:MM:SS is performed in two SET /A commands, that even may be written in a single, long line.

Output examples:

Start position (HH:MM:SS): 00:10:30
End position (HH:MM:SS):   00:10:40
Duration=00:00:10

Start position (HH:MM:SS): 00:10:45
End position (HH:MM:SS):   00:11:05
Duration=00:00:20
Sign up to request clarification or add additional context in comments.

1 Comment

This is by far more elegant than I expected for an answer. Thanks a bunch! Although I realise batch scripting is probably not the syntax for this operation.
1

This is possible to do in pure batch by parsing each field as an independent string, then doing arithmetic on them. Many practical solutions call into some other program to do the date math.

The following code calls into PowerShell to use the .NET DateTime class to do the parsing for you.

C:\> set "StartPosition=00:10:30"
C:\> set "EndPosition=00:10:40"
C:\> PowerShell.exe -c "$span=([datetime]'%EndPosition%' - [datetime]'%StartPosition%'); '{0:00}:{1:00}:{2:00}' -f $span.Hours, $span.Minutes, $span.Seconds"
00:00:10

This executes two lines of PowerShell code; one to convert both times into DateTime objects and subtract them, and the other to output the result in the format you specified.

14 Comments

@Arete what do you mean "batch only"? The line I put above is a batch file command that does what you want. It just happens to call into another Windows program to perform the calculation.
This is interesting! If an "extremely difficult" Batch file that solve this problem (like this one) uses two lines for the conversion and one line for the formatting, is this PowerShell solution "two thirds extremely difficult"? ^_^ Certainly, this PS solution is extremely slow when compared vs. the Batch file one...
@Aacini Yes, you're very clever; you've created something in batch. You don't need to be an ass about it. I would argue this PowerShell solution is much more readable as the critical bits don't look like line noise. Re: speed, PS is quite comparable once the .NET framework is loaded. If speed really is critical, you need to drop batch and write an .exe anyway.
I agree there are pros and cons to every programming language, but Powershell is objectively superior to batch files in almost every measure: expressivity, readability, maintainability, interoperability, and learning curve. I make the assumption that not everybody who asks a batch file question (or visits this question later) has weighed the pros and cons of every scripting language available on the OS, but rather that they might be using batch as a "default" Windows scripting language. I want to make sure people in that situation understand that there are other options.
Whenever we have this discussion, your only argument against PS seems to be performance. I don't know what caused you to be such a batch file purist, @Aacini. I respect that you know a great deal about batch, and that you live and breathe its esoteric and inscrutable syntax. Neither I nor Microsoft believe it is the future, though, and I think you do a disservice by discouraging people from being made aware of other scripting options on their computers.
|
0

Here's a working prototype:

@echo off 

set T1=00:10:45
set T2=00:11:05

set HOUR1=%T1:~,2%
set MIN1=%T1:~3,-3%
set SEC1=%T1:~-2%

set HOUR2=%T2:~,2%
set MIN2=%T2:~3,-3%
set SEC2=%T2:~-2%

set /A TOTAL_SEC1=%HOUR1%*3600+%MIN1%*60+SEC1
set /A TOTAL_SEC2=%HOUR2%*3600+%MIN2%*60+SEC2
set /A DIFF=%TOTAL_SEC2%-%TOTAL_SEC1%

echo %DIFF%

Output:

20

Its not complete, but its a reasonable start.

Comments

0

I think, @Aacini has cleared Everything here. He got you, Before I Do. But, I want to Improved on him as - by using For Loop to make code Easier.

Note: Everything after 'REM' is a Comment for the sake of understanding easily...
All You need to DO is Copy It into Your Batch File. And, Use it as follows (in your main code):

Syntax: Call :Time [Your Time 1] [Operation] [Your Time 2]

And, You can Now apply - any operation - including 'Addition, Substraction, Division, Multiplication' ;) The Time Function
--------------Copy the Below Code----------

:Time [Start_Time] [Operation] [End_Time]
SetLocal EnableDelayedExpansion
REM Creating a portable Function for your Job. :)

REM Reading Start-time...
For /F "Tokens=1,2,3 Delims=:" %%A in ("%~1") Do (
Set _Start_Hour=%%A
Set _Start_Min=%%B
Set _Start_Sec=%%C
)

REM Reading End-time...
For /F "Tokens=1,2,3 Delims=:" %%A in ("%~3") Do (
Set _End_Hour=%%A
Set _End_Min=%%B
Set _End_Sec=%%C
)

REM Removing leading Zero's - if any... 'CMD assumes it as octal - otherwise'
For %%A In (Hour Min Sec) Do (
    For %%B In (Start End) Do (
        IF /I "!_%%B_%%A:~0,1!" == "0" (Set _%%B_%%A=!_%%B_%%A:~1!)
        )
)

REM Applying Operation on the given times.
For %%A In (Hour Min Sec) Do (Set /A _Final_%%A=!_Start_%%A! %~2 !_End_%%A!)

REM Handling a little Exceptional errors! - due to the nature of time (60 sec for a min.)
SET _Extra_Hour=0
SET _Extra_Min=0

REM Two Cases can arise in each part of time...
:Sec_loop
IF %_Final_Sec% GTR 59 (Set /A _Extra_Min+=1 & Set /A _Final_Sec-=60 & Goto :Sec_loop)
IF %_Final_Sec% LSS 0 (Set /A _Extra_Min-=1 & Set /A _Final_Sec+=60 & Goto :Sec_loop)

Set /A _Final_Min+=%_Extra_Min%

:Min_loop
IF %_Final_Min% GTR 59 (Set /A _Extra_Hour+=1 & Set /A _Final_Min-=60 & Goto :Min_loop)
IF %_Final_Min% LSS 0 (Set /A _Extra_Hour-=1 & Set /A _Final_Min+=60 & Goto :Min_loop)

Set /A _Final_Hour+=%_Extra_Hour%

REM Saving Everything into a Single Variable - string.
Set _Final_Time=%_Final_Hour%:%_Final_Min%:%_Final_Sec%

REM Displaying it on the console. ;)
Echo.%_Final_Time%
Goto :EOF
--------------End OF Code----------------------

You can Also visit, my Website - based on Batch Programming. (www.thebateam.org) You'll find alot of stuff there - to help you out. :) Here's the Final Output - When I saved the Code in Answer.bat File

2 Comments

Multiplication and division will not work without significant changes such as converting the start and end times to seconds. With only that change, multiplication and division could work, albeit with awkward syntax. (00:20:00 * 00:00:02 for 20 mins * 2?) Also, output should be padded with zeros.
:o Thats a good hint about fixing a bug. and, Padding with the zeros is not a difficult task all I need is to add few more lines to check the width of the final output and add zeros if they are single digits. Although, @Aacini has fixed everything here. I'm too gonna follow his short and amazing solution. :)
0

To offer a concise alternative to Ryan Bemrose's helpful, PowerShell-based answer:

:: Sample variable values.
set "StartPosition=00:10:30"
set "EndPosition=00:10:40"

:: Use PowerShell to perform the calculation, 
:: using the .NET System.Timespan ([timespan]) type.
powershell -c \"$([timespan] '%EndPosition%' - '%StartPosition%')\"

Yes, you pay a performance penalty for invoking the PowerShell CLI, but I invite you to compare this solution to Aacini's clever, but highly obscure batch-language-only solution in terms of readability and conceptual complexity.

Generally speaking:

  • cmd.exe is a shell and, historically, shells have provided very limited language capabilities themselves, as their focus was on calling built-in or external commands.

    • cmd.exe's language, as used in batch files (.cmd, .bat) is very limited, and saddled with many counterintuitive behaviors that can't be fixed so as not to break backward compatibility.

      • Over the decades, users have learned to stretch the language to its limits, coming up with many clever techniques to squeeze more functionality out of it. While helpful if you're stuck on pre-PowerShell systems (virtually extinct at this point) or you must use batch files and performance is paramount (rarely the case), the obscurity of these techniques makes them both hard to understand and to remember.
    • cmd.exe's successor, PowerShell, with its .ps1 scripts, offers a far superior language that offers virtually unlimited access to .NET functionality and COM.

      • PowerShell too has its fair share of counterintuitive behaviors that can't be fixed, but, by and large, it is a far more capable and predictable language than the batch language; some of its undeniable, but unavoidable complexity comes from having to talk to multiple worlds (.NET, COM, WMI) while still also acting as a shell (with respect to calling external programs and the shell-like syntax of its built-in command as well as user-defined ones).
  • Here, the batch file uses a call an external program, powershell.exe, the PowerShell CLI, to delegate the task at hand to its superior language.

    • Calling the PowerShell CLI is expensive in terms of performance, but offers a way to perform tasks that batch files either cannot, or can only do with much more effort and/or highly obscure techniques.

Of course, needing to "speak" both the batch language and PowerShell to implement a given task adds complexity of its own, so the logical progression is to implement the entire task in PowerShell (in a .ps1 script).

  • Unfortunately, PowerShell puts up some road blocks here, in the name of security:

    • In workstation editions of Windows, execution of scripts is disabled by default, and requires a one-time call such as the following to enable it (see this answer for background information):

      Set-ExecutionPolicy -Scope CurrentUser RemoteSigned
      
    • .ps1 scripts - unlike batch files - cannot be executed directly from outside PowerShell, notably not by double-clicking in File Explorer or from a cmd.exe session.

      • From inside a PowerShell session that is not a concern, but if you do need this capability in a given scenario, a simple workaround is to create a companion batch file with the same base file name as the .ps1 script (e.g., foo.cmd in the same directory as the target PowerShell script, foo.ps1, with the following, generic content:

        @powershell -noprofile -file "%~dpn0.ps1"
        

Comments

0

When setlocal enableextensions enabledelayedexpansion %time% and %date% may return the same value, to avoid use:

for /F "usebackq delims=" %%T in (`echo "^!time^!"`) do set _start=%%T

consume time

for /F "usebackq delims=" %%T in (`echo "^!time^!"`) do set _stop=%%T

then use !start! and !stop! variables

Comments

0
::                         Get Duration in Time-format (Within one Day [24 hours])

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION

set      "StartTime=%TIME:~,-3%"
REM set      "StartTime= 7:08:09"
REM set      "StartTime=09:08:09"
REM set      "StartTime= 9:59:59"
REM set      "StartTime= 0:00:00"
REM set      "StartTime=23:59:59"
REM set        "EndTime=%TIME:~,-3%"
REM set        "EndTime= 7:08:09"
REM set        "EndTime=09:08:09"
REM set        "EndTime= 9:59:59"
REM set        "EndTime= 0:00:00"
set        "EndTime=23:59:58"

set cmd.RegQueryTimeFormat=C:\Windows\System32\reg.exe QUERY "HKCU\Control Panel\International" /v
for /f "skip=2 tokens=3 delims= "          %%i in ('%cmd.RegQueryTimeFormat% sTime')       do set "_tdelim=%%i"
for /f "skip=2 tokens=3 delims=%_tdelim% " %%i in ('%cmd.RegQueryTimeFormat% sTimeFormat') do set    "_fHH=%%i"

set "_HHfirst= "
if /i "%_fHH%"=="HH" set "_HHfirst=0"
for %%i in (MM SS) do set "_%%ifirst=0"

for %%i in (Start End) do for /f "tokens=1-3 delims=%_tdelim%" %%j in ("!%%iTime!") do (
  set /a "%%iHH=%%j"
  if ERRORLEVEL 1 set /a "%%iHH=0x%%j"
  set /a "%%iMM=%%k"
  if ERRORLEVEL 1 set /a "%%iMM=0x%%k"
  set /a "%%iSS=%%l"
  if ERRORLEVEL 1 set /a "%%iSS=0x%%l"
)1>nul 2>&1

for %%i in (Start End) do set /a %%iTimeSec=%%iHH*3600+%%iMM*60+%%iSS

set /a DurationSec=EndTimeSec-StartTimeSec+86400
set /a DD=DurationSec/86400, HH=DurationSec/3600-DD*24, MM=DurationSec/60%%60, SS=DurationSec%%60

for %%i in (HH MM SS) do if !%%i! lss 10 set "%%i=!_%%ifirst!!%%i!"

set "DurationTime=%HH%%_tdelim%%MM%%_tdelim%%SS%"

echo Start Time: %StartTime%
echo   End Time: %EndTime%
echo   Duration: %DurationTime%

@PAUSE
ENDLOCAL
EXIT

1 Comment

Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.
0

This is brillant.

However, some precautions need to be taken if you obtain one of the times involved from the %TIME% variable.

Although this is system dependent, the %TIME% variable typlically will have a decimal place after the seconds, and it also won't have a leading zero for the early morning hours (as I found out the hard way in one night). It can typically look like :

< 1:53:37,4>

Using it as-is in the formulas proposed will result in a sysntax error. You want to:

  1. trim it to its first 8 characters

  2. replace the leading space, if any, by a zero

    set my_time=%TIME%
    set my_time=%my_time:~,8"
    set my_time=%my_time: =0%

and now you can use it safely...

Comments

-1
::                         Set Duration in Seconds

@ECHO OFF
SETLOCAL ENABLEDELAYEDEXPANSION

set      "StartTime=%TIME:~,-3%"
REM set      "StartTime= 7:08:09"
REM set      "StartTime=09:08:09"
REM set      "StartTime= 9:59:59"
REM set      "StartTime= 0:00:00"
REM set      "StartTime=23:59:59"
set /a "DurationSec=4271"
REM set /a "DurationSec=-4261"

set cmd.RegQueryTimeFormat=C:\Windows\System32\reg.exe QUERY "HKCU\Control Panel\International" /v
for /f "skip=2 tokens=3 delims= "          %%i in ('%cmd.RegQueryTimeFormat% sTime')       do set "_tdelim=%%i"
for /f "skip=2 tokens=3 delims=%_tdelim% " %%i in ('%cmd.RegQueryTimeFormat% sTimeFormat') do set    "_fHH=%%i"

set "_HHfirst= "
if /i "%_fHH%"=="HH" set "_HHfirst=0"
for %%i in (MM SS) do set "_%%ifirst=0"

for /f "tokens=1-3 delims=%_tdelim%" %%i in ("%StartTime%") do (
  set /a "HH=%%i"
  if ERRORLEVEL 1 set /a "HH=0x%%i"
  set /a "MM=%%j"
  if ERRORLEVEL 1 set /a "MM=0x%%j"
  set /a "SS=%%k"
  if ERRORLEVEL 1 set /a "SS=0x%%k"
)1>nul 2>&1

set /a EndTimeSec=HH*3600+MM*60+SS+DurationSec+(1-DurationSec/86400)*86400
set /a DD=EndTimeSec/86400, HH=EndTimeSec/3600-DD*24, MM=EndTimeSec/60%%60, SS=EndTimeSec%%60, DD-=(1-DurationSec/86400)

for %%i in (HH MM SS) do if !%%i! lss 10 set "%%i=!_%%ifirst!!%%i!"

set "_tdays=Today in "
if %DD% neq 0 (
  set "_tdays=Next"
  if %DurationSec% lss 0 set "_tdays=Previous"
  set "_tdays=!_tdays! %DD:-=% day(s) in "
)

set "EndTime=%_tdays%%HH%%_tdelim%%MM%%_tdelim%%SS%"

echo Start Time: %StartTime%
echo   Duration: %DurationSec% sec
echo   End Time: %EndTime%

@PAUSE
ENDLOCAL
EXIT

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.