Home > Blockchain >  Foreach hour:min:sec.ms
Foreach hour:min:sec.ms

Time:09-30

I would like to know how can I parse an foreach specifying hours, minutes, seconds and milliseconds. Something like this:

DURATION=00:14:17.82 // (00 hours, 14 minutes, 17 seconds and 82ms)
for ((i=1;i<=DURATION;i  )); do
    // output the hour, minute, second and ms here
done

I want to iterate every milisecond, second, minute, hour! Like this:

00:14:17.82
00:14:17.83
00:14:17.84
00:14:17.85
...
00:14:18.01
00:14:18.02
...
01:00:10.15
01:00:10.16
01:00:10.17
...

I just know creating this for seconds and just seconds! But how can I make for hours, minutes, seconds and ms? Thank you so much!

CodePudding user response:

NOTE: The following ideas assume a max DURATION of 23:59:59:99 or 24:00:00.00; upper ranges can certainly be increased but keep in mind the extra overhead (cpu and/or memory) for brace expansions, especially if generating huge ranges that dwarf DURATION (ie, generating large volumes of timestamps that you'll never process).


If you're going to manually enter the value for DURATION then you could manually enter the following brace expansion series:

for i in {00..00}:{00..14}:{00..17}.{00..82}
do
    echo $i
done

NOTE: This relatively small set of of brace expansions was almost instantaneous and took up ~6 MB (RAM) on my system.

This generates:

00:00:00.00
00:00:00.01
00:00:00.02
00:00:00.03
00:00:00.04
... snip ...
00:14:17.78
00:14:17.79
00:14:17.80
00:14:17.81
00:14:17.82

Alternatively:

DURATION=00:14:17.82

for i in {00..23}:{00..59}:{00..59}.{00..99}
do
    [[ "${i}" > "${DURATION}" ]] && break
    # do other stuff
done

NOTE:

  • on my system this took about about 10 seconds and 1.6 GB (RAM) to expand the series of brace expansions before the loop was processed
  • this particular set of brace expansions creates 8.64 million timestamps but ...
  • we break out of the loop after processing ~ 86K timestamps (or ~1% of the timestamps generated by the brace expansions) so ...
  • we wasted a lot of cpu cycles and RAM generating timestamps that we never got around to using

Another idea is to write a 4-deep set of for loops and break out once we hit 00:14:17.82, eg:

DURATION='00:14:17.82'

for a in {00..23}; do
    for b in {00..59}; do
        for c in {00..59}; do
            for d in {00..99}; do
                [[ "${a}:${b}:${c}.${d}" > "${DURATION}" ]] && break 4
                # do other stuff here
            done
        done
    done
done

NOTE: While each brace expansion is fast (and uses a miniscule amount of RAM), the nested/repeated expansions is going to eat up a lot of time the longer it takes to get to ${DURATION}; however, unlike the previous ideas this one will at least let you start processing your timestamp values immediately, with intermittent (brief) delays as a new brace expansion is performed.


Of course we could just skip the brace expansion overhead and use 'normal' for loops, eg:

DURATION='00:14:17.82'

for ((a=0; a<=23; a  )); do
    for ((b=0; b<=59; b  )); do
       for ((c=0; c<=59; c  )); do
           for ((d=0; d<=99; d  )); do
                printf -v ts "d:d:d.d" ${a} ${b} ${c} ${d}
                [[ "${ts}" > "${DURATION}" ]] && break 4
                # do other stuff here
           done
       done
    done
done

NOTES:

  • this eliminates the cpu/time/RAM overhead of the brace expansions
  • assumes you have a newer version of bash that supports printf -v

And for the masochists:

DURATION='00:14:17.82'

for ((i=0; i<8640000; i  )); do

      hr=$(( ( i / 100 / 60 / 60 ) %  24 ))
     min=$(( ( i / 100 / 60 )      %  60 ))
     sec=$(( ( i / 100 )           %  60 ))
    msec=$((   i                   % 100 ))

    printf -v ts "d:d:d.d" ${hr} ${min} ${sec} ${msec}
    [[ "${ts}" > "${DURATION}" ]] && break
    # do other stuff
done

CodePudding user response:

Just iterate over milliseconds and convert to the representation you want upon use. First, we write conversion functions:

millis_to_human() {
   local h m s ms
   # calculate all those hours, minutes, seconds...
   ((
      ms = $1,
      s = ms / 100,
      ms %= 100,
      m = s / 60,
      s %= 60,
      h = m / 60,
      h %= 60,
      1
   ))
   printf "d:d:d.d\n" "$h" "$m" "$s" "$ms"
}
human_to_millis() {
   # Match with regex
   [[ $1 =~ ([0-9]*):([0-9]*):([0-9]*).([0-9]*) ]]
   # Using 10# so that the number is always in base 10, even with leading 0.
   echo $(( ( (
      10#${BASH_REMATCH[1]} * 60  
      10#${BASH_REMATCH[2]} ) * 60  
      10#${BASH_REMATCH[3]} ) * 100  
      10#${BASH_REMATCH[4]} ))
}

Then just:

duration='00:14:17.82'
duration_ms=$(human_to_millis "$duration")
for ((i = 1; i <= duration_ms;   i)); do
   millis_to_human "$i"
done
  •  Tags:  
  • bash
  • Related