Hi,

Here's my entry.  I haven't peeked at any of the other responses yet,  
but I imagine most people have gone about this in roughly the same  
way.  Any comments are welcome, I'm a relative nuby, and have only  
done toy projects so far.

The place where there's probably the most latitude for variation is  
in the algorithm for deciding how the offset between the reported  
time and the actual time varies.  The first thing I tried was to  
figure out, whenever the time was updated, what the range of possible  
offsets was (given the current actual and reported times and need to  
make sure the reported time doesn't retrogress) and then select a new  
offset uniformly within that range.  The problem there is that, if  
you update more often than every five minutes, the clock will tend to  
advance 4-5 minutes ahead of the actual time and stay there.  To  
address that, I now select an offset uniformly within the range of  
allowable errors (+5 minutes to -5 minutes) and, if the new offset  
would cause the time to retrogress, I just leave the reported time  
unchanged.  The offset will still tend to be positive, however, and  
more positive the shorter the interval between updates.  I'm  
interested to see if anyone came up with a good way to get a balanced  
distribution of errors over a long run.

Cheers,

Tom

Here are my sources for the FuzzyTime module and a driver program.   
To run the driver, just say

     ruby fuzzyclock.rb

You can set the number of digits to fuzz out with the option '-f  
<ndigits>'.
You can set the "wobble" (maximum allowable error) with '-w <minutes>'.
You can have it display the actual time and cuurrent error with '-d'.
You can have it run in an accelerated test mode with the option '-t'.

- - - - - - - - - - - - - - - - - - - - fuzzytime.rb  - - - - - - - -  
- - - - - - - - - - -
#!/usr/bin/env ruby
#  Fuzzy time module
#  Author: Tom Pollard <pollard / earthlink.net>
#
class FuzzyTime
   attr_reader :actual, :current, :updated
   attr_accessor :am_pm, :fuzz, :wobble, :method

   # Return a new FuzzyTime clock, intialized to the given time.
   # If no intial time is specified, the current time is used.
   # The default fuzz is 1 digit; the default wobble is 5 minutes;
   # times are represented in 12-hour style by default.
   #
   def initialize ( actual=nil )
     @actual = actual || Time.new()   # the actual time (Time)
     @current = nil                   # the time that we report (Time)
     @updated = Time.new              # when @current was last  
updated (Time)
     @wobble = 5                      # the maximum error in @current  
(minutes)
     @fuzz = 1                        # the number of digits to fuzz  
out (int)
     @method = 2                      # the update algorithm to use  
(int)
     @am_pm = true                    # report 12-hour time? (boolean)
     @current = @actual + offset
   end

   # Advance the actual time by the given number of seconds
   # (The reported time may or may not change.)
   def advance ( delta=0 )
     @actual += delta
     @current = @actual + offset
     @updated = Time.new
     self
   end

   # Advance the actual time to account for the time since it was  
last changed.
   # (The reported time may or may not change.)
   def update
     advance( (Time.new - @updated).to_i )
   end

   # Calculate a new offset (in minutes) between the actual and  
reported times.
   # (This is called whenever the actual time changes.)
   def offset
     max_range = 2*@wobble + 1
     min_offset = @current.to_i/60 - @actual.to_i/60
     if @current.nil?  || min_offset < -@wobble
       range = max_range
     else
       range = @wobble - min_offset + 1
     end
     range = max_range if range > max_range

     if range == 0
       delta = 0
     else
       if @method == 1
         # pick a new offset within the legal range of offsets.
         delta = @wobble - rand(range)
       else
         # pick a new offset within the range of allowable errors.
         # if it would require the time to regress, don't change the  
reported time.
         delta = @wobble - rand(max_range)
         delta = min_offset if delta < min_offset
       end
     end
     return 60 * delta
   end

   # Report the difference (in minutes) between the reported and  
actual times.
   def error
     (current - actual).to_i/60
   end

   # Return a string representation of the fuzzy time.
   # The number of digits obscured by tildes is controlled by the  
'fuzz' attribute.
   # Whether the time is in 12- or 24-hour style is controlled by  
'am_pm'.
   def to_s
     if @am_pm
       display = @current.strftime("%I:%M")
     else
       display = @current.strftime("%H:%M")
     end
     @fuzz.times { display.sub!(/\d(\D*)$/, '~\1') } if @fuzz > 0
     display
   end
end
- - - - - - - - - - - - - - - - - - - - fuzzyclock.rb - - - - - - - -  
- - - - - - - - - - -
#!/usr/bin/env ruby
#
#  Report the current time on the console, using the FuzzyTime module.
#
require 'fuzzytime.rb'
require 'getoptlong.rb'

opts = GetoptLong.new(
   [ '--fuzz', '-f', GetoptLong::REQUIRED_ARGUMENT],
   [ '--wobble', '-w', GetoptLong::REQUIRED_ARGUMENT],
   [ '--24hour', '-m', GetoptLong::NO_ARGUMENT],
   [ '--test', '-t', GetoptLong::NO_ARGUMENT],
   [ '--debug', '-d', GetoptLong::NO_ARGUMENT]
)

ft = FuzzyTime.new
test = false
debug = false

opts.each do |opt, arg|
   ft.fuzz = arg.to_i   if opt == '--fuzz'
   ft.wobble = arg.to_i if opt == '--wobble'
   ft.am_pm = false     if opt == '--24hour'
   debug = true         if opt == '--debug'
   test = true         if opt == '--test'
end

puts "Fuzzy Clock"
while true
   printf "\r%s", ft.to_s
   printf "   (%s %3d)", ft.actual.strftime("%H:%M"), ft.error if debug
   STDOUT.flush
   if test
     sleep 1
     ft.advance 60
   else
     sleep 60
     ft.update
   end
end
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  
- - - - - - - - - - -