I0_ol
I0_ol

Reputation: 1109

Display date and time like a digital clock in Bash

The command date +'%a %b %e %Y%n%I:%M %p' displays the date and time in the format:

Thu Sep 22 2016
08:02 PM

I was trying to think of a way to have the date and time update as the time changes. The only thing I can think of would be to do something like this:

#!/bin/bash

while true
do 
    date +'%a %b %e %Y%n%I:%M %p'   
done | awk '!seen[$0]++'

This produces output like:

Thu Sep 22 2016
08:02 PM
08:03 PM
08:04 PM

Is there a way to display the time change by overwriting the old time so that 08:02 changes to 08:03 on the same line, essentially making the date and time display like a regular digital clock?

Upvotes: 3

Views: 10279

Answers (4)

Dreded
Dreded

Reputation: 11

I wanted a 24h clock centered vertically and horizontally with the date on a second line that re-centered if the window size or text size changed

this is in /usr/bin/clock as in my opinion it is starting to get a bit large for a addition to bashrc but its only one function so no reason you couldn't put the clock() function in bashrc

output(centered to screen):

                                   19:28:13
                             Tuesday April 9 2024

code:

#!/usr/bin/env bash

clock() {
    local lastDim=("0" "0")
    local lines=()

    display_center() {
        local termDim=("$(tput lines)" "$(tput cols)")
        if [ "$((${termDim[0]} + ${termDim[1]}))" != "$((${lastDim[0]} + ${lastDim[1]}))" ]; then
            clear
            lastDim=(${termDim[@]})
        fi
        tput cup $((${termDim[0]} / 2 - $((${#lines[@]} / 2))))
        for ((i = 0; i < ${#lines[@]}; i++)); do
            printf '%*s\n' $(((${#lines[i]} + ${termDim[1]}) / 2)) "${lines[i]}"
            tput el
        done
    }

    local LOOPDELAY
    trap 'tput cnorm; trap - INT ERR' ERR
    trap 'tput cnorm; trap - INT ERR; return' INT
    tput civis
    while :; do
        lines=("$(date +%0H:%0M:%0S)")
        lines+=("$(date +"%A %B %-d %Y")")
        display_center
        LOOPDELAY=$(expr 1200000000 - $(command -p date '+%N'))
        LOOPDELAY=$(expr ${LOOPDELAY} / 100000000)
        sleep "$(expr ${LOOPDELAY} / 10).$(expr ${LOOPDELAY} % 10)"
    done
}
clock

Upvotes: 0

dsimic
dsimic

Reputation: 390

I also wanted to have a simple, single-line live digital clock displayed and automatically updated in my bash(1) shell when I need it. Here's the definition of the clock() function I implemented, which I use in my ~/.bashrc file:

if command -v 'tput' > /dev/null 2>&1; then
        function clock() {
                trap 'tput cnorm; trap - INT ERR' ERR
                trap 'tput cnorm; trap - INT ERR; return' INT
                tput civis
                while :; do
                        echo -e 'cr\nel' | tput -S
                        echo -n "$(date) "
                        sleep 1
                done
        }
fi

This function uses the tput(1) utility and the terminfo(5) terminal capability database to hide the cursor (civis or cursor_invisible capability), to move the cursor to the start of the line (cr or carriage_return capability), and to clear the line to its end (el or clr_eol capability) before updating its contents. It also uses tput(1) to make the cursor visible again (cnorm or cursor_normal capability) before returning back to the shell.

As visible in the shell code above, this function uses a bit of the shell's trap trickery to achieve the desired behavior, but it all works fine with no issues or side effects. You can verify that no temporary trap leftovers are present by executing trap -p before and after this function is executed and interrupted by pressing Ctrl + C. This function depends on the tput(1) utility, which is usually part of the ncurses(3X) package that isn't always installed, so the function doesn't get defined at all in case the tput(1) utility isn't present.

Of course, you can also define a date alias to customize the default output format for the date command, or you can modify the clock() function to additionally specify the format string for the date command. For example, here's the definition of an alias that I use in my ~/.bashrc file:

alias date="date '+%A  %B %-d, %Y  %H:%M:%S %Z (%:z)'"

Here's another version of the clock() function that avoids the use of a couple of bashisms present in the original version of the function, and instead uses more portable constructs as suggested in the comments below:

if command -v 'tput' > /dev/null 2>&1; then
        function clock() {
                trap 'tput cnorm; trap - INT ERR' ERR
                trap 'tput cnorm; trap - INT ERR; return' INT
                tput civis
                while :; do
                        printf 'cr\nel\n' | tput -S
                        printf '%s' "$(date) "
                        sleep 1
                done
        }
fi

However, as also discussed in the comments below, this more portable version of the function most probably still requires bash(1) because the required ERR trap seems not to be available in the POSIX sh(1P).


Below are the improved versions of the clock() function, which no longer suffer from the occasional flickering of the displayed digital clock, and eliminate the occasional skipping of an elapsed second.

The flickering was pronounced especially on slower systems, such as ARM-based devices, loaded with other resource-hungry processes. It was caused by clearing the line to its end (el or clr_eol capability) before updating its contents, which is actually completely redundant, because all we need is to clear the line to its end after updating its contents, to cover the cases in which the updated contents has fewer characters than the replaced contents.

Simply overwriting the contents, which in most cases actually isn't much different than the replaced contents, avoids the flickering, and the subsequent execution of tput el handles the clearing of the rest of the line.

These improved versions also eliminate the occasional (actually, pretty much periodic) skipping of displaying an elapsed second while the digital clock is updated. Previously, using the fixed one-second update delay interval caused displaying an elapsed second to be skipped periodically because the delays created by executing various commands added up over time, causing the overall update delay to occasionally become longer than one second.

In addition to eliminating the skipped seconds, these improved versions also produce much more fluent updating of the digital clock, by performing the updates always around ss.200000000, on each elapsed second, where 200000000 is the nanosecond portion of the actual time.

Here's the improved fluent, flicker-free and skip-free version of the clock() function that uses some bashisms:

if command -v 'tput' > /dev/null 2>&1; then
        function clock() {
                local LOOPDELAY
                trap 'tput cnorm; trap - INT ERR' ERR
                trap 'tput cnorm; trap - INT ERR; return' INT
                tput civis
                while :; do
                        tput cr
                        echo -n "$(date) "
                        tput el
                        LOOPDELAY=$(expr 1200000000 - $(command -p date '+%N'))
                        LOOPDELAY=$(expr ${LOOPDELAY} / 100000000)
                        sleep "$(expr ${LOOPDELAY} / 10).$(expr ${LOOPDELAY} % 10)"
                done
        }
fi

Here's also the improved fluent, flicker-free and skip-free version of the clock() function that uses fewer bashisms:

if command -v 'tput' > /dev/null 2>&1; then
        function clock() {
                local LOOPDELAY
                trap 'tput cnorm; trap - INT ERR' ERR
                trap 'tput cnorm; trap - INT ERR; return' INT
                tput civis
                while :; do
                        tput cr
                        printf '%s' "$(date) "
                        tput el
                        LOOPDELAY=$(expr 1200000000 - $(command -p date '+%N'))
                        LOOPDELAY=$(expr ${LOOPDELAY} / 100000000)
                        sleep "$(expr ${LOOPDELAY} / 10).$(expr ${LOOPDELAY} % 10)"
                done
        }
fi

However, as discussed in the comments below, this more portable version of the function most probably still requires bash(1) because the required ERR trap seems not to be available in the POSIX sh(1P).

Obviously, the improved versions above rely on the sleep(1) utility being capable of accepting fractional delay values, which most of its implementations and versions seem to support properly.


All versions of the clock() function have been updated to trap properly SIGINT as well, and to clear this additional trap handler, which was required to handle Ctrl + C keypresses properly in all cases.

Upvotes: 1

redneb
redneb

Reputation: 23850

You can use tput to move the cursor around:

date +'%a %b %e %Y%n%I:%M %p'
while sleep 1
do
    tput cuu 2
    date +'%a %b %e %Y%n%I:%M %p'
done

Upvotes: 2

Charles Duffy
Charles Duffy

Reputation: 295363

To update the first line only when the day changes, and the second line every second:

while :; do
  # store start date in variable
  printf -v start_day '%(%a %b %e %Y)T'
  day=$start_day

  clear
  echo "$day"
  while sleep 1 && [[ $start_day = $day ]]; do
      printf -v day '%(%a %b %e %Y)T'
      printf '\r%(%I:%M %p)T'
  done
done

Using printf %()T restricts compatibility to recent bash 4.x, but substantially improves performance (avoiding the need to start an external program every time we want to update the time).

Upvotes: 3

Related Questions