0

What timedate value is always greater than any timedate?

In a script, I want to provide an argument to variable duration so that the loop will run forever until I kill the process:

# `duration` has a value in seconds
end=$(($(date +%s) + duration))  

while true; do
    # ...
    [ $(date +%s) -ge $end ] && break
    # ...
done
7
  • 1
    Why not just [ "$end" != never ] && [ "$(date +%s)" -ge "$end" ]? Mar 30 at 12:58
  • 1
    In bash, see also printf -v now '%(%s)T' -1 to avoid the external date command invocation. Mar 30 at 12:59
  • 1
    And the $SECONDS variable copied from ksh that tracks the elapsed time since the shell started. Mar 30 at 13:00
  • 2
    Or the $EPOCHSECONDS variable in newer versions (copied from zsh) Mar 30 at 13:01
  • 4
    "run forever until YOU kill the process"... Isn't 100 years in the future far enough for that? How old are you now? And besides, if you really mean "forever" then you don't need to put a date at all -- just while : ;do ... ;done. I mean, it's redundant to put in a condition which you want to ensure will always fail -- there's no point to that. Might as well say [ 0 -ge 1 ] && break.
    – Pourko
    Mar 30 at 23:26
10

I'd change it to:

SECONDS=0

while true; do
    # ...
    [ "$duration" = forever ] || [ "$SECONDS" -lt "$duration" ] || break
    # ...
done

And set duration=forever without having to worry as to what the maximum number supported by [ on the system is.

$SECONDS is automatically incremented every second. That feature comes from ksh and is also available in zsh and bash. Beware however that $SECONDS in bash is incremented every time the full seconds of wall clock time change, so for instance, if SECONDS=0 is run at 12:00:00.999, it will be incremented to 1 at 12:00:01.000, so only one millisecond later.

If switching to zsh (which no longer has that bug) is an option, you can change it to:

typeset -F SECONDS=0
while true; do
    # ...
    (( SECONDS < duration )) || break
    # ...
done

And use duration=inf for the loop to run forever. That also allows fractional durations.

0
4

Stéphane’s approach is better, but if you really want to compare against some impossible-to-reach number, you could specify Bash’s maximum value in comparisons; on current Linux systems (both 32- and 64-bit), that’s:

end=9223372036854775807

This value can’t even be reached with current versions of GNU date, because they limit the year to 1900 + (231 - 1), so the maximum result of date +%s is 67768036191719999 (23:59:59 on December 31, 2147485547, UTC-12).

11
  • 1
    FWIW, TZ=UTC0 faketime "$((1900 + 1<<31 - 1))-12-31 23:59:59" date +'%s %c' outputs 67768036191676799 Wed 31 Dec -2147481749 23:59:59 UTC for me. Mar 30 at 15:35
  • Oh right, TZ is significant, so the maximum is 12 hours behind UTC. (I’m looking for the maximum which can be reached by having the clock move forwards.) Mar 30 at 15:44
  • 1
    GNU date returns date: time ‘67768036191676800’ is out of range regardless of the timezone for me, maximum seems to be 67768036191676799. Note that you can have UTC-23:59:59 for instance (larger offsets are allowed on some systems IIRC) Mar 30 at 15:49
  • TZ=Etc/GMT+12 date --date=@67768036191719999 works for me... Mar 30 at 16:00
  • Thank you, Stephen!
    – Mary
    Mar 30 at 20:45
1

It isn't difficult to find the maximum value valid for date. It is only 64 bits to test, anyways.

#!/bin/bash

start=${1:-50}
maxint=$(( (1<<63)-1 ))
usedate="date"

for ((n=start;n>=0;n--)); do
    limit=$((1<<n))
    if [[ $limit -lt 0 ]] || [[ $limit -gt $maxint ]] ; then limit=$maxint; fi

    if [[ $usedate == "date" ]]; then
    if endtime=$(date -d @"$((end | limit))" +'<<%s>>'); then
        enderror=""
    else
        enderror="error reported"
    fi
    else
    enderror=$( printf -v endtime '%(%s)T' "$((end | limit))" >/dev/null);
    fi
    
    if [ -z "$enderror" ]; then 
    end=$((end | limit))
    fi
    printf "end=%d %x\n" "$end" "$end"

done

The limit looks fine up to bit 54, where the result is:

$ ./script 54
end=36028797018963967 7fffffffffffff

But as soon as you try 55, the result gets longer to explain (probably a year limit of date).

$ ./script 55
end=67768036191691199 f0c2ab7c54e1bf

Testing printf (already defined in the script, change usedate) gives no limit all up to 63 bits (end=9223372036854775807 7fffffffffffffff).

You can test your date to find out the exact limit on your system.

0

Interestingly, on my system the date can go back over 2 billion years! :-)

To find the valid ranges, I wrote this little script:

#!/bin/bash
printdate () { TZ=UTC0 date -d@$1 ;}
limits () { # GOOD BAD
  local good=$1 bad=$2 mid
  while (( ( good > bad ? good - bad : bad - good ) >1 )) ;do
    (( mid = (good - bad) / 2  + bad )) # "(good+bad)/2" could overflow 
    printdate $mid >/dev/null 2>&1 && good=$mid || bad=$mid
  done
echo $good
printdate $good
}

((MAX=(1<<63)-1))
limits 0 $MAX
limits 0 -$MAX
Results:

# limits 0 $MAX
67768036191676799
Wed Dec 31 23:59:59 UTC 2147485547

# limits 0 -$MAX
-67768040609740800
Thu Jan  1 00:00:00 UTC -2147481748

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.