OSX bash command line countdown timer

I found here on superuser the following nice countdown script that I could include in my .bash_profile:

function countdown(){  
   date1=$((`date +%s` + $1));   
   while [ "$date1" -ne `date +%s` ]; do   
     echo -ne "$(date -u --date @$(($date1 - `date +%s`)) +%H:%M:%S)\r";  
     sleep 0.1  
  done  
}

It doesn't work on OSX, i understand, because of the differences in the date command. Another user commented as such but the problem remained unresolved in that question.

The same problem (I think) is also discussed and solved here but I cannot work out how to modify the countdown script to utilise this solution.


Personally, I'd refactor that a bit for readability (still relying on GNU date)

function countdown(){  
    local now=$(date +%s)
    local end=$((now + $1))
    while (( now < end )); do   
        printf "%s\r" "$(date -u -d @$((end - now)) +%T)"  
        sleep 0.25  
        now=$(date +%s)
    done  
    echo
}

Mapping the date calls to OSX's BSD date (man page here)

It looks like the GNU date invocation

date -u -d @$((end - now)) +%T

translates to this BSD date invocation

date -u -j -f %s $((end - now)) +%T

but that's untested.


I tried @glenn's answer on macOS Mojave in Bash and Zsh, and it worked well. The last echo in his code near the last line might not be needed:

function countdown() {  
    local now=$(date +%s)
    local end=$((now + $1))
    while (( now < end )); do   
        printf "%s\r" "$(date -u -j -f %s $((end - now)) +%T)"  
        sleep 0.25  
        now=$(date +%s)
    done  
}

and to count down:

countdown 30                       # for 30 seconds
countdown $((10*60))               # 10 minutes
countdown $((1*60*60 + 30*60))     # 1 hour 30 minutes

I am not so familiar with shell scripts, so perhaps somebody could add a display for number of days if the countdown is more than a day.

To give an audio bell at the end of the countdown, add an extra line near the end:

function countdown() {  
    local now=$(date +%s)
    local end=$((now + $1))
    while (( now < end )); do   
        printf "%s\r" "$(date -u -j -f %s $((end - now)) +%T)"  
        sleep 0.25  
        now=$(date +%s)
    done  
    echo -en "\a"
}