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
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - - -