Big Ben goes BONG

Bash, 71, 70, 69 bytes

EDITS:

  • Optimized the sleep interval computation a bit, -1 byte;
  • Replaced backticks with xargs, -1 byte (Thanks @jrtc27 !).

Golfed

sleep `date +3600-%s%3600|bc`;yes BONG|sed $(date +%I)q|xargs;exec $0

Explained

#Compute the number of seconds left in the current hour and wait.
sleep `date +3600-%s%3600|bc`; 

#Print "hour" (1-12) worth of "BONG" strings, one per line,
#use xargs to merge them into a single space-separated string.
yes BONG|sed $(date +%I)q|xargs

#Re-execute itself (in the same process).
exec $0

Test Version

Works with minutes instead of hours

sleep `date +60-%s%60|bc`;yes BONG|sed $(date +%I)q|xargs;exec $0

*><>, 48 47 44 bytes

>s?um?uhc%:?!c21.O
v$o" GNOB"oooo$1-:?!
\aofS

Try it here! (or this version which does it every minute for testing)

This outputs a number of BONGs separated by spaces based on what hour it is (and 13 is 1, etc). There are no trailing space after the final BONG, and there is a trailing newline.

Technically this doesn't run forever (but it basically does). It leaks ~28 bytes per day (ignoring interpreter overhead ...). It would take ~105062 years for it to leak 1GiB.

Approximating for interpreter overhead, the stack is just a slice of float64s on the Golang interpreter. So I simply did 28*8 to come up with 224 bytes per day. I divided this number by the number of bytes in a gibibyte (1073741824) then 365 to approximate that it would take 13132.85 years to leak 1 GiB.

Note about the interpreters

The online interpreter is in Javascript. The page must be open and visible for it to check the time it currently is and output the BONGs. The Golang interpreter has no such limitation.

Explanation

Main loop:

s?um?u          O     if seconds == 0 && minutes == 0:
      hc%               push hours % 12 (bongs) to the stack
         :?!c           if bongs is 0, push 12 to the stack
             21.        jump to "output bongs" coords (2, 1)


Output bongs:

Begins at `"` because of the jump.

   " GNOB"            push "BONG " to the stack
          oooo        output "BONG"
              $1-     subtract 1 from bongs
v                :?!  if bongs == 0, move to "newline and sleep"
 $o                   output " "


Newline and sleep:

\                     mirror the IP to the right
 ao                   output a newline
   fS                 sleep for 1.6s
\                     mirror to main loop

JavaScript (ES6), 99 93 bytes

f=_=>setTimeout('alert("BONG ".repeat(new Date/36e5%12|0||12).trim(f()))',36e5-new Date%36e5)

This uses UTC time (which lines up with the actual thing). Due to the untestable nature of the code, you can try the following which does the same thing, but every 60 seconds:

f=_=>setTimeout('alert("BONG ".repeat(new Date/36e5%12|0||12).trim(f()))',6e4-new Date%6e4)

f()

Here's a bonus 99-byte version which uses local time:

f=_=>setTimeout('alert("BONG ".repeat(new Date().getHours()%12||12).trim(f()))',36e5-new Date%36e5)