Re: Is there a POSIX compliant way of turning a "HH:MM:SS" formatted string to seconds? ...
On Thu, Jul 17, 2025 at 23:39:02 +0200, lbrtchx@tutamail.com wrote:
> Video durations are formatted in youtube's .info.json files as "HH:MM:SS";
OK.
> _HHMMSS="19:09"
> _HHMMSS="19:08"
> IFS=$(echo -en "\n\b"); _SEKNDS_AR=($(echo "${_HHMMSS}" | tr ':' '\n')); _SEKNDS_ARL=${#_SEKNDS_AR[@]}
Your Subject header is asking for "POSIX compliant" but you're using
commands here that aren't POSIX compliant. echo with options isn't
compliant, and array variables aren't either.
If you're using array variables, then you're writing for bash, not for
sh, and that means you can drop the "POSIX compliant" requirement.
This means you can use $'...' to set IFS, you can use array variables,
you can use local variables in functions, you can use parameter expansions
that replace characters or extract substrings by character index, etc.
> if [[ ${_SEKNDS_ARL} -eq 2 ]]; then
> _SEKNDS=$(( 60 * ${_SEKNDS_AR[0]} )); _SEKNDS=$(( _SEKNDS + ${_SEKNDS_AR[1]} ))
> echo "// __ \$_SEKNDS: |${_SEKNDS}|"
> fi
>
> bash: _SEKNDS + 08: value too great for base (error token is "08")
Others have explained the octal issue, so I won't dwell on it.
As far as the code goes, I'd rather start from scratch.
========================================================================
#!/bin/bash
shopt -s extglob
# Interpret $1 (which must be in MM:SS or HH:MM:SS format) as a duration
# and convert it to a number of seconds, which is returned in variable r.
hms2sec() {
local h m s
case $1 in
*:*:*) h=${1%%:*} h=${h##+(0)}
m=${1#*:}
s=${m#*:} s=${s##+(0)}
m=${m%:*} m=${m##+(0)}
;;
*:*) h=0
m=${1%:*} m=${m##+(0)}
s=${1#*:} s=${s##+(0)}
;;
*) echo "unrecognized input '$1'" >&2; return 1;;
esac
r=$((h*3600 + m*60 + s))
return 0
}
for hms in 19:09 07:08 3:06:07; do
hms2sec "$hms"
echo "$hms -> $r"
done
========================================================================
This is one way to tackle the problem. It's *almost* POSIX compliant;
the bashisms here are the local variables in the function, and the +(0)
extended glob for removing multiple leading zeroes.
If you assume the input can never have multiple leading zeroes to
remove, then you could drop both of those bashisms, and it would work
in POSIX sh (though I'm not sure how POSIX handles empty variables
in arithmetic expansions; you might want to add extra checks for
empty variables and set them to 0).
If you don't want to see an explanation of the function, you can stop
reading here. The rest is just tutorial.
- - - - - - - - -
The function receives one input variable (positional parameter 1) and
takes it apart, and then does arithmetic using the parts.
The first thing it does is examine the format, to see whether it's
HH:MM:SS (with two colons) or MM:SS (with one colon). If it's not
either of those, then it's considered an error, and the function
writes a message to stderr and returns failure (1).
In the HH:MM:SS case, which is the harder part, the extraction is done
in four steps.
1) The number of hours is the input string up until the first colon,
so everything from the first colon to the end of the string is
removed. That's the ${1%%:*} expansion. Then, any leading zeroes
are stripped (the ${h##+(0)} expansion).
2) The MM:SS part of the input string, which is everything after the
first colon, is temporarily stored in the m variable. That's the
m=${1#*:} expansion. I could have used a fourth variable to hold
this value, but I decided to use the existing m variable.
3) The number of seconds is the MM:SS part (which is in m) after the
colon, so it's extracted using the s=${m#*:} expansion. Then,
as before, the leading zeroes are stripped.
4) Finally, the number of minutes is extracted from the MM:SS part
using m=${m%:*} and then the leading zeroes are stripped.
In the MM:SS case (one colon), the extraction is simpler:
1) The number of hours is always 0.
2) The number of minutes is the input string up to the colon,
so it's extracted with m=${1%:*} and then leading zeroes are
stripped.
3) The number of seconds is the input string after the colon,
so it's extracted with s=${1#*:} and then leading zeroes are
stripped.
After the input has been separated into parts, with leading zeroes
stripped to avoid the octal problem, calculating the number of
seconds is straightforward arithmetic. The output value is stored in
the variable r, which is *not* local to the function. This gives
the caller control over whether they want to use their own local r
variable, or to let r be global. Either way, there is no command
substitution, which means there's no forked subshell process.
The entire script is written in pure shell code, and does not fork
any processes.
Reply to: