Fuzzy Time (#99)

The three rules of Ruby Q.:

  1. Please do not post any solutions or spoiler discussion for this quiz
    48 hours have passed from the time on this message.

  2. Support Ruby Q. by submitting ideas as often as you can:


  1. Enjoy!

Suggestion: A [QUIZ] in the subject of emails about the problem helps
on Ruby T. follow the discussion. Please reply to the original quiz
if you can.


by Gavin K.

The Background

Last night I was having trouble falling asleep, and was staring at my
alarm clock (as I do so often under that circumstance). Something on the
table was occluding the minutes digit, however, so all I could tell is
that it
was “10 4”. (Oddly, my alarm clock has no “:” between the hours and

“How nice!” I thought. “An imposed vagueness to keep me from obsessing
exactly what time it is! Should I really be worried about the exact
Shouldn’t I be more relaxed? Shouldn’t a 10-minute precision in life be
to keep me roughly on time, without obsessing on exacting promptitude?”

I realized that if I kept staring at the clock (as I did), and I were to
it changing from “10 4” to “10 5”, that I would, at that moment, know
what time it is.

“Bah” I thought, “so much for that idea.”

And then I thought some more. I thought of bad ideas: analog watches
where the
hand erratically swings forward and backward, digital clocks that showed
times near the correct time. And then I dreamed of the watch I wanted to

The Challenge

Requirement #1: Write a Ruby program that shows the current time, but
only the
‘tens’ part of the minutes. For example, if the time is 10:37, then the
might output “10:3~”

Requirement #2: The time shown by the clock must randomly vary +/- 5
from reality. For example, if the time is actually 10:37, the program
output “10:3~” or “10:4~” (but not “10:2~” or “10:5~”).

Requirement #3: The time on the clock should continuously increase. If
the time
shows “10:4~” it must continue to show “10:4~” until it shows “10:5~”.
(It can’t
show “10:4~”, then “10:3~” for a bit and then come back to “10:4~”.)

Putting the three requirements together, the left column below shows the
time and the next three columns show the possible outputs from three
runs of the same program:

10:35    10:3~    10:4~    10:3~
10:36    10:3~    10:4~    10:3~
10:37    10:3~    10:4~    10:3~
10:38    10:3~    10:4~    10:3~
10:39    10:4~    10:4~    10:3~
10:40    10:4~    10:4~    10:3~
10:41    10:4~    10:4~    10:3~
10:42    10:4~    10:4~    10:3~
10:43    10:4~    10:4~    10:3~
10:44    10:4~    10:4~    10:3~
10:45    10:4~    10:4~    10:4~
10:46    10:4~    10:4~    10:5~
10:47    10:4~    10:4~    10:5~
10:48    10:4~    10:4~    10:5~
10:49    10:4~    10:4~    10:5~
10:50    10:4~    10:4~    10:5~
10:51    10:4~    10:4~    10:5~
10:52    10:5~    10:4~    10:5~
10:53    10:5~    10:4~    10:5~
10:54    10:5~    10:4~    10:5~
10:55    10:5~    10:5~    10:5~
10:56    10:5~    10:5~    11:0~
10:57    10:5~    10:5~    11:0~
10:58    10:5~    10:5~    11:0~
10:59    10:5~    10:5~    11:0~
11:00    10:5~    10:5~    11:0~
11:01    10:5~    10:5~    11:0~

Testing your Output

You should supply a FuzzyTime class that supports the following:

ft = FuzzyTime.new                      # Start at the current time
ft = FuzzyTime.new(Time.at(1161104503)) # Start at a specific time

p ft.to_s                               # to_s format
#=> "10:5~"

p ft.actual, ft.actual.class            # Reports real time as Time
#=> Tue Oct 17 11:01:36 -0600 2006
#=> Time

ft.advance( 60 * 10 )                   # Manually advance time
puts ft                                 # by a specified number of
#=> 11:0~                               # seconds.

sleep( 60 * 10 )

ft.update              # Automatically update the time based on the
puts ft                # time that has passed since the last call
#=> 11:1~              # to #initialize, #advance or #update

Your class and output will be tested with code like the following:

def test_output
  # Initialize with a well-known time
  ft = FuzzyTime.new( Time.at( ... ) )

  60.times do
    @legal  = ...       # Find the array of legal output strings
    @output = ft.to_s

    assert_block "#@output not one of #@legal.inspect" do
      @legal.include?( @output )

    sleep( rand( 30 ) )

  60.times do
    @legal  = ...       # Find the array of legal output strings
    @output = ft.to_s

    assert_block "#@output not one of #@legal.inspect" do
      @legal.include?( @output )

    ft.advance( rand( 30 ) )

Extra Credit

* Provide a self-running application that shows the time somehow.
(ASCII in the terminal, some GUI window, etc.)

* Allow your class to be customized to display 12- or 24-hour time.

* Allow your class to be customized to change how close to reality
it must display. (+/- 3 minutes, +/- 12 minutes, etc.)

* Allow your class to be customized to change how roughly it displays
the time (e.g. 1 minute, 10 minute, 1 hour intervals).

* Ensure that the transition from one digit to the next occurs
randomly across the range of -5 to +5. (So, while the digit might
change 5 minutes before or 5 minutes after the real transition, on
average the change should occur around the transition itself.)
You might need to assume that your update/advance method will be
called with a certain regularity (e.g. once per second, once every
7 seconds, once every 30 seconds, etc.)

* Come up with your own technique of displaying time that
(a) is always 'close' to right, but (b) never allows a
watchful person to ever know exactly what time it is.

Things to Keep in Mind

* You need to be able to handle the transition across hour/day
boundaries. (10:5~ might change to 11:0~ when the real time is still
10:58, or might continue to display 10:5~ when the real time is
11:04. On a 24-hour click, you also need to be able to wrap from
23:5~ to 00:0~)

* For testing purposes of the real-time #update advancing, you might
find it easier to work with minutes and seconds instead of hours and

* Requirement #3 is, well, a requirement. Repeated #update/#to_s
calls to a FuzzyTime instance should never show an earlier time
(unless 24 hours occurred between #update calls ;).

On Fri, 27 Oct 2006, Ruby Q. wrote:

Requirement #3: The time on the clock should continuously increase. If the time
shows “10:4~” it must continue to show “10:4~” until it shows “10:5~”. (It can’t
show “10:4~”, then “10:3~” for a bit and then come back to “10:4~”.)

it seems like #2 and #3 contradict one another. imagine it’s 10:45 and,
through randomness, you choose to vary the clock by +5, therefore
10:5~, you will not be able to change the output again until 10:55, and
only because the upper bould will have rolled over into the next hour.
here it
is in table form

lower actual upper selection

10:40 10:45 10:50 10:50/5~
10:41 10:46 10:51 10:51/5~
10:42 10:47 10:52 10:52/5~
10:43 10:48 10:53 10:53/5~
10:44 10:49 10:54 10:54/5~
10:45 10:50 10:55 10:55/5~
10:46 10:51 10:56 10:56/5~
10:47 10:52 10:57 10:57/5~
10:48 10:53 10:58 10:58/5~
10:49 10:54 10:59 10:59/5~
10:50 10:55 11:00 11:00/0~

an initial high selection eats into subsequent ranges - in otherwords a
selection of t+ means that #2 will not by able to hold: the variance
will be
required to b smaller for the next choice: a high selection eats the
range of the next choice…

it’s possibly even worse when we not near the top of an hour and start
of with
maximum variance:

lower actual upper selection

10:20 10:25 10:30 10:30/3~
10:21 10:26 10:31 10:31/3~
10:22 10:27 10:32 10:32/3~
10:23 10:28 10:33 10:33/3~
10:24 10:29 10:34 10:34/3~
10:25 10:30 10:35 10:35/3~
10:26 10:31 10:36 10:36/3~
10:27 10:32 10:37 10:37/3~
10:28 10:33 10:38 10:38/3~
10:29 10:34 10:39 10:39/3~
10:30 10:35 10:40 10:30/3~
10:31 10:36 10:41 10:31/3~
10:32 10:37 10:42 10:32/3~
10:33 10:38 10:43 10:33/3~
10:34 10:39 10:44 10:34/3~
10:35 10:40 10:45 10:35/3~
10:36 10:41 10:46 10:36/3~
10:37 10:42 10:47 10:37/3~
10:38 10:43 10:48 10:38/3~
10:39 10:44 10:49 10:39/3~
10:40 10:45 10:50 10:50/5~

so the combined effect means that it’s acceptable to display the same
for twenty straight minutes - is that really the a desired potential


On 10/27/06, [email protected] [email protected] wrote:

it seems like #2 and #3 contradict one another.

Indeed. I take it that the challenge of the quiz is to come up with a
way to balance out the two goals. The purpose for requirement 2 is
that you don’t want the user to be able to deduce the actual time
based on when you switch from one output to the next. The problem
with doing that as a simple random selection is that you get wildly
differing ranges.

+/- 5 minutes from reality.

Is problematic, as you point out Ara. The example

For example, if the time is actually 10:37, the program might
output “10:3~” or “10:4~” (but not “10:2~” or “10:5~”).

however, is much more lenient. You don’t have to round down, you just
can’t pick a number from a different section. (10:3~ is not +/-5 of
10:37 obviously).

I gather that there would be some pretty simple ways you can dampen
the latter effect while still preventing the user from easily guessing
the actual time.

#!/usr/bin/env ruby

quiz99.rb: Implementation of RubyQuiz #99 - Fuzzy Time

Lou S. [email protected]

Sunday, October 29th, around 11 o-clock (smirks).

I decided to approximate time to the quarter hour. When people on the


bother you for the time, that’s probably what they are expecting.

It’s been

my experience that they give you nasty looks when you make them do any

amount of trivial math =)

This one was a lot of fun. Thanks again to James and Gavin for

another great



class Time

A string representation for times on the quarter hour

def to_quarter_s
case min
when 0
“#{hour_12} o-clock”
when 15
“quarter past #{hour_12}”
when 30
“half past #{hour_12}”
when 45
n = self + 3600
“quarter 'till #{n.hour_12}”

Let the Time library to the hard work for getting 12 time.

def hour_12
(strftime “%I”).sub(/\A0/,‘’)

class FuzzyTime

Quarter hours fall every 900 seconds

TimeScale = 60 * 15

def initialize(time = Time.now)
@real = @time = time
@emitted = nil

Use a simple linear scaling to even out the intervals. This seems

to work

out okay after a little testing, but it could probably be improved

quite a


One variable that effects this is the sampling rate. We’re

approximating to

the nearest quarter hour; however, if you call to_s say once a

second, you

have a greater chance of bumping up to the next interval than if you

increase the time between calls to one minute.

def to_s
pick = rand(TimeScale)
threshold = TimeScale - @time.to_i % TimeScale - 1
p [pick, threshold, last_valid, next_valid] if $DEBUG

@emitted = if ([email protected]? && @emitted > last_valid) \
           || pick >= threshold



def inspect
t_obj = if @emitted.nil?

def actual

def last_valid
Time.at(@time.to_i / TimeScale * TimeScale)

def next_valid
last_valid + TimeScale

def advance(offset)
@real = Time.now
@time += offset

def update
now = Time.now
delta = now - @real
@time += delta
@real = now


Err, I forgot what the incantation is on windows. I think it is

‘cls’, but

I’ll leave it as an exercise to the reader. winks

ClearString = clear
def clear
print ClearString

def get_sample_rate
default = 60
return default unless ARGV[0]
return ARGV[0].to_i
return default

if caller.empty?
ft = FuzzyTime.new

while true do
puts ft
sleep get_sample_rate

On Oct 29, 2006, at 10:28 AM, Marcel W. wrote:

This is also my first greeting to the Ruby community… Hello!

Then let me be one of the first to welcome you to Ruby and the Ruby

James Edward G. II

Thanks for this week’s interesting problem. My solution is below and
I look forward to any feedback and seeing other techniques used.

There are two files pasted below; the second is the unit test, which
takes about 5-10 minutes to run to completion. My first unit test
ever in any language :slight_smile:

This is also my first greeting to the Ruby community… Hello!


#!/usr/bin/env ruby

Marcel W. <wardies ^a-t^ gmaildotcom>

Sunday, 29 October 2006

Solution for Ruby Q. number 99



class FuzzyTime
attr_reader :actual, :timed_observation_period

If the time passed is nil, then keep track of the time now.

def initialize(tm=nil, range_secs=600, disp_accuracy_secs=range_secs,
fmt="%H:%M", obs_period=nil)
@actual = @last_update = @next_diff = tm || Time.now
@realtime = tm.nil?
@maxrange = range_secs
@display_accuracy = @best_accuracy = disp_accuracy_secs
@tformat = fmt
@last_observed = @max_disptime = Time.at(0)
@timed_observation_period = obs_period

def to_s
@last_update = Time.now
@actual = @last_update if @realtime

check_observation_period unless @timed_observation_period.nil?

# We only calculate a new offset each time the last offset times out.
if @next_diff <= @actual
  # Calculate a new time offset
  @diff = rand(@maxrange) - @maxrange/2
  # Decide when to calculate the next time offset
  @next_diff = @actual + rand(@maxrange)
@last_observed = @actual

# Don't display a time less than the time already displayed
@max_disptime = [@max_disptime, @actual + @diff].max

# Take care to preserve any specific locale (time zone / dst) 

# stored in @actual - for example, we cannot use
disptime = @max_disptime.strftime(@tformat)

# Lop off characters from the right of the display string until the
# remaining string matches one of the extreme values; then fuzz out 

# rightmost digits
(0…disptime.size).to_a.reverse.each do
[@display_accuracy.div(2), - @display_accuracy.div(2)].map{
(@max_disptime + offs).strftime(@tformat)
}.each do
return disptime[0,w] + disptime[w…-1].tr(“0123456789”, “~”) if

disptime[0,w] == testtime[0,w]

def advance(secs)
if @realtime
@actual = Time.now + secs
# Once a real-time FuzzyTime is advanced, it can never again be
# real-time.
@realtime = false
@actual += secs
@last_update = Time.now

def update
diff = Time.now - @last_update
@actual += diff
@last_update += diff
# By calling update, you are effectively saying “set a fixed time”
# so we must disable the real-time flag.
@realtime = false

def accuracy
“+/- #{@maxrange/2}s”

def dump
“actual: #{@actual.strftime(”%Y-%m-%d %H:%M:%S")}, "
“diff: #{@diff}, "
“next_diff: #{@next_diff.strftime(”%Y-%m-%d %H:%M:%S”)}, "
“accuracy: #{@display_accuracy}”

def check_observation_period
# Is the clock being displayed too often?

# Although this method seems to work, it may be a bit simplistic.
# Proper statistical / mathematical analysis and a proper 

# of the human ability to count seconds may be necessary to
# whether this still gives away too much info for the average

patience = @actual - @last_observed

if patience < @timed_observation_period / 2
  # Worsen display accuracy according to how impatient the observer 

@display_accuracy += (2 * @best_accuracy *
(@timed_observation_period - patience)) /
elsif patience < @timed_observation_period
# Immediately punish impatience by enforcing a minumum accuracy
# twice as bad as the best possible.
# Don’t give too much away but allow the accuracy to get slowly
# if the observer is a bit more patient and waits over half the
# observation period
@display_accuracy = [
2 * @best_accuracy,
@display_accuracy - ((@best_accuracy * patience) /
# The observer has waited long enough.
# Reset to the best possible accuracy.
@display_accuracy = @best_accuracy

def wardies_clock

Get us a real-time clock by initializing Time with first


Make the seconds harder to guess by expanding the range to +/- 15s


keeping the default display accuracy to +/- 5 secs. The user will


to wait 30s between observations to see the clock with best

ft = FuzzyTime.new(nil, 30, 10, “%H:%M:%S”, 30)

This simpler instantiation does not check the observation period and

shows “HH:M~”. (This is the default when no parameters are provided)

#ft = FuzzyTime.new(nil, 600, 600, “%H:%M”)

puts “** Wardies Clock\n”
puts "\n Observing more often than every "
“#{ft.timed_observation_period} seconds reduces accuracy”
unless ft.timed_observation_period.nil?
puts “**\n\n”

loop do
puts “\n\nTime Now: #{ft.to_s} (#{ft.accuracy})\n\n”
"-- Press Enter to observe the clock again or "
“q then Enter to quit --\n\n”

# Flush the output text so that we can scan for character input.

break if STDIN.getc == ?q


def clocks_go_back_in_uk

Clocks go back in the UK on Sun Oct 29. (+0100 => +0000)

Start at Sun Oct 29 01:58:38 +0100 2006

ft = FuzzyTime.new(Time.at(Time.at(1162083518)))

In the UK locale, we see time advancing as follows:



01:0~ (clocks gone back one hour)




60.times do
puts ft.to_s

def full_date_example

Accuracy can be set very high to fuzz out hours, days, etc.

E.g. accuracy of 2419200 (28 days) fuzzes out the day of the month

Note the fuzz factoring does not work so well with hours and

non-30-day months because these are not divisble exactly by 10.

tm = FuzzyTime.new(nil, 2419200, 2419200, “%Y-%m-%d %H:%M:%S”)
300.times do
puts “#{tm.to_s} (#{tm.dump})”
# advance by about 23 days
#sleep 0.2

Note, all the examples given in the quiz are for time zone -0600.

If you are in a different timezone, you should see other values.

def quiz_example
ft = FuzzyTime.new # Start at the current time
ft = FuzzyTime.new(Time.at(1161104503)) # Start at a specific time

p ft.to_s # to_s format

p ft.actual, ft.actual.class # Reports real time as Time
#=> Tue Oct 17 11:01:36 -0600 2006
#=> Time

ft.advance( 60 * 10 ) # Manually advance time
puts ft # by a specified number of
#=> 11:0~ # seconds.

sleep( 60 * 10 )

ft.update # Automatically update the time based on the
puts ft # time that has passed since the last call
#=> 11:1~ # to #initialize, #advance or #update

if FILE == $0



require ‘test/unit’
require ‘fuzzy_time’

class FuzzyTime_Test < Test::Unit::TestCase
#def setup

#def teardown

def test_advance

# Initialize with a known UTC time (Tue Jun 10 03:14:52 UTC 1975)
ft = FuzzyTime.new(Time.at(171602092).getgm, 60, 60, "%H:%M:%S")

# Add 6 hours 45 minutes 30 secs to give us
# (Tue Jun 10 09:59:22 UTC 1975)
ft.advance(3600*6 + 60*45 + 30)
@last_output = ""

60.times do
  # Initial displayed time sourced from between 09:58:52 and 

# Time will be advanced by between 0 and 600 seconds.
# So final displayed time source ranges from 10:08:52 to 10:09:52

  # The array of legal output strings:
  @legal  = ["09:58:~~", "09:59:~~", "10:00:~~", "10:01:~~",
    "10:02:~~", "10:03:~~", "10:04:~~", "10:05:~~",
    "10:06:~~", "10:07:~~", "10:08:~~", "10:09:~~"]

  @output = ft.to_s

  assert_block "#@output not one of #{@legal.inspect}" do
    @legal.include?( @output )

  assert_block  "#@output must be greater than or equal to " \
    "last value, #@last_output" \
    @output >= @last_output
  @last_output = @output

  ft.advance( rand( 11 ) )


def test_advance_rollover
# Initialize with a known UTC time (Fri Dec 31 23:58:25 UTC 1999)
# Test rollover at midnight
# Note, we have an accuracy of +/- 5 secs now and enabled the
# observations timer
ft = FuzzyTime.new(Time.at(946684705).getgm, 10, 10, “%H:%M:%S”, 10)

30.times do
  # Initial displayed time sourced from between 23:58:20 and 

# Time will be advanced by between 0 and 150 seconds.
# So final displayed time source ranges from 00:00:50 to 00:01:00

  # Note, if we watch too often over a short period of time,
  # our displayed accuracy will decrease.  Then we will lose
  # the 10's digit of the seconds and occasionally the 1's minute.

  # The array of legal output strings:
  @legal = ["23:58:1~", "23:58:2~", "23:58:3~",
    "23:58:4~", "23:58:5~", "23:58:6~",
    "23:58:~~", "23:59:~~", "23:5~:~~",
    "23:59:0~", "23:59:1~", "23:59:2~",
    "23:59:3~", "23:59:4~", "23:59:5~",
    "00:00:0~", "00:00:1~", "00:00:2~",
    "00:00:3~", "00:00:4~", "00:00:5~", "00:00:~~",
    "00:01:0~", "00:01:~~", "00:0~:~~"]

  @output = ft.to_s

  assert_block "#@output not one of #{@legal.inspect}" do
    @legal.include?( @output )

  # We cannot easily check that the current output is greater or 

equal to
# the last because with timed observations, a valid output
sequence is:
# 23:59:0~
# 23:59:~~ (looking too often, accuracy has been reduced)
# 23:59:0~ (waited long enough before observing for accuracy to

  ft.advance( rand(6) )


def test_update
# NOTE - this test takes 5-10 minutes to complete

# Initialize with a known UTC time (Tue Jun 10 03:14:52 UTC 1975)
ft = FuzzyTime.new(Time.at(171602092).getgm, 60, 60, "%H:%M:%S")
@last_output = ""

60.times do
  # Initial displayed time sourced from between 03:14:22 and 

# Duration of loop will be between 0 and ~600 seconds.
# So final displayed time source ranges from 03:14:22 to 03:25:22

  # The array of legal output strings:
  @legal  = ["03:14:~~", "03:15:~~", "03:16:~~", "03:17:~~",
    "03:18:~~", "03:19:~~", "03:20:~~", "03:21:~~",
    "03:22:~~", "03:23:~~", "03:24:~~", "03:25:~~"]

  @output = ft.to_s

  assert_block "#@output not one of #{@legal.inspect}" do
    @legal.include?( @output )

  assert_block  "#@output must be greater than or equal to " \
    "last value, #@last_output" \
    @output >= @last_output
  @last_output = @output

  sleep( rand( 11 ) ) # wait between 0..10 secs


Hi all

Here’s is my script. It was fun working at it, just please no more
time based quiz when there’s the switching of daylight time saving :stuck_out_tongue:
The scripts defines two class one to manage how to move within a given
range and one for actual FuzzyTime implementation.
At the end of the email there’s a really ugly extra file you may want
to use to play with the FuzzyTime class.


class WanderingWalker
SIGN = [1, -1]
CHECK_LIMIT = %w(min max)
DEFAULT_GENERATOR = lambda { |limit| rand(limit) * SIGN[rand(2)] }
attr_reader :position, :limit, :target
alias :level :position
def initialize(limit, &block)
@limit = limit
@generator = block_given? ? block : DEFAULT_GENERATOR
@position = 0
def walk(steps)
generate_target while @position == @target
new_pos = [(@position + direction*steps), @target].send
@position = new_pos
def distance
@target - @position
def direction
if distance == 0 : 1 else distance/distance.abs end
def generate_target
@target = @generator.call limit

class FuzzyTime
FORMATS = %w(%H:%M %I:%M)
attr_reader :fuzziness
#two params, both optional time and an hash
#time: a time object used as the starting point
#hash params
#:hidden_digits - hidden_digits number, from right
#:precision - maximum distance from real time in seconds
def initialize(time=Time.new, opts={})
@internaltime = time
@last_call = @printed_time = Time.new
@precision = opts[:precision] || 300
@fuzziness = WanderingWalker.new(@precision)
@format = FORMATS[0]
@hidden_digits = opts[:hidden_digits] || 1
@sub_args = case @hidden_digits
when 0 : [//, ‘’]
when 1 : [/\d$/, ‘~’]
when 2 : [/\d{2}$/, ‘’]
when 3 : [/\d:\d{2}$/, '~:
raise “nothing to see!”
def advance(secs)
def update
tic Time.new - @last_call
def actual
#switch 12 / 24h format
def toggle_format
@format = FORMATS[FORMATS.index(@format) == 0 ? 1 : 0]
def to_s
@printed_time = [@printed_time, (@internaltime +
def tic(secs=1)
@internaltime += secs
@last_call = Time.new
@fuzziness.walk secs

Ad here’s the super ugly runner script

require ‘fuzzy_time’

accepts a FuzzyTime object as argument + an hash of options

#possible options

:time_warp if true instead of working as a clock just prints out

#in an instant all the clock output, if false, works as a clock

:step_size how often the clock will be updated in seconds

:print_actual prints the actual time

:print_fuzziness, prints the error size

:toggle switch at every step from 24 to 12 hour format

:duration how many step_size the clock should run

class FuzzyExec
def initialize(ft = FuzzyTime.new ,opts = {})
opt_def = {:duration => 100, :step_size => 60, :print_actual =>
@opts = opt_def.update opts
@ft = ft
def show_fuzzy_clock
@opts[:duration].times do
@ft.toggle_format if @opts[:toggle]
out = []
out << @ft.to_s
out << @ft.actual.strftime(’%H:%M’) if @opts[:print_actual]
out << @ft.fuzziness.level if @opts[:print_fuzziness]
out << Time.new.strftime(’%H:%M’) if @opts[:print_current]
puts out.join ’ ’
if @opts[:time_warp]
sleep @opts[:step_size]

Here’s my effort. I didn’t get around to testing it, but the concept
seems sound. I decided to interpret the goal a little more
conservatively and instead of just making sure the displayed time
never goes forward, I decided to make sure the offset never goes
backwards. To do this, I made the max delta from the current offset
no more than the amount of time that passed since the last offset

class FuzzyTime

def initialize(*args)
now = Time.new
@internal_time = args[0] || now
@time_offset = now - @internal_time
@fuzzy_secs = 0
@last_calc = Time.new

def actual

def update
@internal_time = Time.new + @time_offset

def advance(amount)
@time_offset += amount.to_i

def calc_offset
# Choose a new offset that’s between +/- 5 mins. If it has been
# less than 5 mins since the last offset calc, choose that time as
# a max delta (this makes sure time is always going forward)

 time_from_last_calc = (Time.new - @last_calc).to_i

 if time_from_last_calc > 0
     max_delta = [MAX_OFFSET, time_from_last_calc].min

     delta = rand((2*max_delta) + 1) - max_delta
   end until (delta + @fuzzy_secs).abs < MAX_OFFSET
   @fuzzy_secs += delta

   puts "Fuzzy secs now: #{@fuzzy_secs}"

   @last_calc = Time.new


def get_time
fuzzy_hour = @internal_time.hour
fuzzy_min = @internal_time.min
fuzzy_sec = @internal_time.sec + calc_offset

 if fuzzy_sec > 60
   fuzzy_sec -= 60
   fuzzy_min += 1

 if fuzzy_sec < 0
   fuzzy_sec += 60
   fuzzy_min -= 1

 if fuzzy_min > 60
   fuzzy_min -= 60
   fuzzy_hour = (fuzzy_hour + 1) % 24

 if fuzzy_min < 0
   fuzzy_min += 60
   fuzzy_hour = (fuzzy_hour + 23) % 24

 [fuzzy_hour, fuzzy_min, fuzzy_sec]


def to_s
fuzzy_hour, fuzzy_min, fuzzy_sec = get_time
“#{fuzzy_hour}:#{fuzzy_min / 10}~”
# “#{fuzzy_hour}:#{fuzzy_min / 10}~ (#{fuzzy_hour}:#{”%02d" %
fuzzy_min}:#{"%02d" % fuzzy_sec})"


if $0 == FILE
t = FuzzyTime.new
10.times do
puts t, Time.new
sleep 10


I’ve posted an old version the second line of initialize should be
changed from

@last_call = @printed_time = Time.new


@last_call = @printed_time = time



Ahh, it feels good to do a ruby quiz again :-). I’ve ignored the other
solutions so far so someone else may have taken the same approach.

I took the approach that there are actual 3 times involved, so as to
hopefully avoid as little as possible the range limiting that Ara was
talking about. And it just made it easier to think about the problem.

Essentially, the time displayed to the user is all that must be
increase. So long as that is continually moving forward, the times
behind the scene don’t really matter. That for me results in 3
different times.

- Actual time
- Fuzzed time  : actual time fuzzed within the fuzzy range (+- 5 min 

- Display time : Fuzzed time ‘floored’ to a granularity (10 min

As a result, my approach has the following algorithm:
- Calculate the actual time (update/advance)
- Calculate a fuzzy range for the new fuzzed time which has
- lower bound = the maximum of the last display time or (actual

  • fuzz factor minutes)
    - upper bound = actual + range
    • randomly pick a new time in that range for the fuzzed time
    • calculate the new display time off of the fuzzed time

As a result, the Fuzzed time can increase/decrease with respect to
itself, but will always be greater than the displayed time. But overall
the fuzzed time will continue to increase. And the display time is
always increasing.

I also threw in some Extra Credits: 24hour/12hour option, fuzz factor
(changing the ± range) and display granularity(one minute, ten minute
on hour).

Great Quiz!



Here is my script. This is my first entry to Ruby Q…

I wanted this FuzzyTime to support random access so you can do this:

ft = FuzzyTime.new; puts ft
=> 17:5~
ft = FuzzyTime.set(Time.now+4.hours); puts ft
=> 22:0~
ft = FuzzyTime.set(Time.now+10.minutes); puts ft
=> 18:1~
ft = FuzzyTime.set(Time.now); puts ft
=> 17:5~

Also, I added a ‘rewind’ method for symmetry with ‘advance’ (although
you cannot rewind to before the time FuzzyTime was initialized with).
Now ‘update’ adds to the internal time based on how long since you last
called one of ‘initialize’, ‘advance’, ‘rewind’, ‘update’ or ‘set’.

Other than ensuring that the transition occurs randomly for each ten
minute block, I didn’t implement any of the extra credit options.

-Dan Lucraft

handy stuff…


class FuzzyTime
def initialize(time=Time.now)
# we record the start time as it’s used to reset the random number
# every time we fuzzify a time
@start_time = time

time_mins = (time - time.beginning_of_day).to_i/60
hours = time_mins/60
mins = time_mins-(hours*60)
tens = mins/10
@time_of_nearest_ten_before_start = time_mins - mins + (tens*10) -5

@internal_time = time
@last_called = time


def actual

def advance(secs)
@internal_time += secs
@last_called = Time.now

def rewind(secs)
@internal_time -= secs
@last_called = Time.now

def update
@internal_time += Time.now - @last_called
@last_called = Time.now

def set(time=Time.now)
@internal_time = time
@last_called = Time.now

this fuzzifies the current internal time.

def to_s
# run through random numbers for the number of 10 minute blocks
since the
# start time, and keep the last one (switch_val)
time_mins = ((@internal_time -
distance = time_mins - @time_of_nearest_ten_before_start
switch_val = nil
((distance/10)+1).to_i.times do
switch_val = rand(10)

# the kept random number is where in the 10 minute interval around
# the current time that the fuzzy time switches from low to high
# E.g.
# current time 9:12
# interval around current time: [9:05, 9:15]
# a switch_val of 3 means that we go from 9:0~ to 9:1~ at 9:08
# NB we always get the same switch_val for this ten-minute block,
# since we are resetting the generator and counting the correct 

# of random numbers in. This is what allows random access.
# marginal_mins is 7 in this case. (9:12-9:05)
marginal_mins = distance - (10*(distance/10))
if marginal_mins >= switch_val and marginal_mins >= 5
near_time(@internal_time, :below)
elsif marginal_mins >= switch_val and marginal_mins < 5
near_time(@internal_time, :above)
elsif marginal_mins < switch_val and marginal_mins >= 5
near_time(@internal_time-10.minutes, :below)
elsif marginal_mins < switch_val and marginal_mins < 5
near_time(@internal_time, :below)

def near_time(time, type)
time = (time - time.beginning_of_day).to_i
hour = time/3600
fuzzy_min = ((time - (hour*3600))/60)/10
fuzzy_min += 1 if type == :above
(fuzzy_min = 0 and hour += 1) if fuzzy_min == 6
hour = 0 if hour == 24
if hour >= 10

def check_valid
if @internal_time < @start_time
raise Exception, “Rewound past start of FuzzyTime.”

if FILE == $0
ft = FuzzyTime.new
puts ft
step = ARGV[0].to_i || 60
sleep step
ft.advance 60
puts ft

Hi all,

This is my first submission to the list and I’m new to Ruby too. I’m
finding the quiz excellent for learning. Comments and advice would
really be appreciated.

I haven’t had time to do any “extra credit” stuff, or even test this
fully, but here goes -

class FuzzyTime

attr_reader :actual, :display

def initialize(*start_time)
@current_systime = Time.new
@actual = start_time[0] || Time.new
@last_displayed = @actual

def to_s
# Decide whether to go forward or back 5 mins
if rand(2) == 1
@display = @actual + (5 * 60)
@display = @actual - (5 * 60)

 # If the time we are going to display is before what was last

displayed, don’t do it
if @display < @last_displayed
@display = @last_displayed

 @last_displayed = @display

 "#{"%02d" % @display.hour}:#{("%02d" % @display.min.to_s)[0..0]}~"


Advance the actual time by a number of seconds, reset the system

time record so that

update will work

def advance(secs)
@actual += secs
@current_systime = Time.new

Work out the relative time difference since the last initialize,

advance or update

and apply this to the actual time

def update
diff = Time.new - @current_systime
@actual += diff.to_i
@current_systime = Time.new


Thanks everyone



attached is my FuzzyTime.rb solution and a corresponding Unit Test
file. Thanks for the quiz, I look forward to any feedback. I didn’t
have time to do the extra credit tasks :frowning:

On 10/27/06, Ruby Q. [email protected] wrote:

I realized that if I kept staring at the clock (as I did), and I were to observe
shows “10:4~” it must continue to show “10:4~” until it shows “10:5~”. (It can’t
10:39 10:4~ 10:4~ 10:3~
10:50 10:4~ 10:4~ 10:5~
11:01 10:5~ 10:5~ 11:0~
#=> “10:5~”

      60.times do

    it must display. (+/- 3 minutes, +/- 12 minutes, etc.)
    7 seconds, once every 30 seconds, etc.)
    10:58, or might continue to display 10:5~ when the real time is


“There is no theory of evolution, just a list of creatures Chuck
Norris allows to live.”

Hi all,

I’m new to Ruby and this is the first quiz I’m doing. Not sure if I’m
missing something, but this seems pretty easy to me. Here’s what I

class FuzzyTime

if 24 then show in 24 hour format, else 12 hour format

attr_accessor :mode

def initialize(startAt = Time.now, variance = 5*60)
@time = Time.at(startAt)
@offset = Time.now - @time
@variance = variance
@mintime = Time.at(@time.to_i - @variance).to_i
@mode = 24

def to_s
t = @time.to_i - @variance + rand(@variance * 2)
@mintime = @mintime > t ? @mintime : t
now = Time.at(@mintime)
sprintf(‘%02d:%d~ %s’,
@mode == 24 ? now.hour : now.hour % 12,
now.min / 10,
@mode != 24 ? now.hour / 12 == 1 ? ‘pm’ : ‘am’ : ‘’

def update
@time = Time.now + @offset

def actual

def advance(amt)

def +(amt)
@time = @time + amt

def -(amt)
@time = @time + (-amt)
# reset the minimum displayed time
@mintime = Time.at(@time.to_i - @variance).to_i

if FILE == $0 then
t = FuzzyTime.new
t.mode = 24

30.times {
t += 60
puts “#{t.to_s} (#{t.actual.strftime(‘%H:%M’)})”

Any comments are welcome.


2006/10/30, Caleb P. [email protected]:

On Oct 29, 2006, at 6:34 PM, Robert Conn wrote:

Hi all,


This is my first submission to the list and I’m new to Ruby too.


Comments and advice would really be appreciated.

My comment is that I wish I wrote Ruby that pretty when I was new. :slight_smile:

I’ll give a couple super minor suggestions…

def initialize(*start_time)
@current_systime = Time.new
@actual = start_time[0] || Time.new
@last_displayed = @actual

Why take an Array of arguments but only use the first one? I think
you should drop the slurping operator:

def initialize(start_time)
@current_systime = Time.new
@actual = start_time || Time.new
@last_displayed = @actual

# Decide whether to go forward or back 5 mins
if rand(2) == 1
  @display = @actual + (5 * 60)
  @display = @actual - (5 * 60)

You could shorten that up a bit, though I’m not sure this is as

@display = @actual.send(%w[+ -][rand(2)], 5 * 60)

"#{"%02d" % @display.hour}:#{("%02d" % @display.min.to_s)[0..0]}~"

Perhaps the this is more straight forward:

@display.strftime("%H:%M").sub(/\d$/, “~”)

Hope that gives you some new ideas.

James Edward G. II

Thanks for the test and the quiz. Writing a solution wasn’t very hard,
but coming up with a good way to keep the time from drifting ahead and
had good ‘fuzziness’, quantifying what made a good fuzzy time and then
testing different approaches was the interesting part.

An approach I took to test the fuzziness of my FuzzyTime class was
track the number of minutes the fuzzy time remained the same, advancing
one minute at a time, and then calculate the mean and variance of that
set of numbers, with the idea that a good fuzzy time would have a mean
change interval close to 10 minutes but with a large variance. That
would also snag solutions that always displayed the correct time plus
solutions that were always ahead or behind by a fixed amount: there
would be no variance to the intervals. (It would also be good to see
different results each time it’s run) A unit test for that is after my

In my approach, when a time is asked for, it computes a time that is
+/- 5 minutes from the actual time, keeping a low water mark that is
the last displayed time or 5 mintes from the actual time, whatever is
greater. There is a strong bias not to advance the time, correcting for
the tendancy for the time to drift ahead. This is what my solution got
running your test:

Variation: 10.37% ahead, 20.75% behind
Variation: 13.73% ahead, 8.39% behind
Variation: 13.46% ahead, 10.64% behind
Variation: 13.16% ahead, 10.72% behind
Variation: 13.27% ahead, 11.64% behind
Variation: 13.96% ahead, 9.06% behind
Variation: 13.76% ahead, 9.87% behind
Variation: 13.43% ahead, 10.57% behind
Variation: 13.69% ahead, 8.77% behind
Variation: 13.54% ahead, 9.18% behind
Variation: 13.18% ahead, 9.73% behind
Variation: 13.11% ahead, 10.15% behind
Variation: 13.00% ahead, 10.67% behind
Variation: 13.00% ahead, 10.30% behind
Variation: 12.92% ahead, 10.57% behind
Variation: 12.64% ahead, 10.80% behind
Variation: 12.64% ahead, 11.07% behind
Variation: 12.52% ahead, 10.94% behind
Variation: 12.39% ahead, 11.28% behind
Variation: 12.33% ahead, 11.49% behind

And means and variances:

Mean interval: 9.02446115288221
Variance: 3.07336219244858

As for the solution:

---- FuzzyTime.rb

Fuzzy Time class that reports a time that is always within a given

slop factor to the real time. The user specifies the amount of

fuzzyiness to the time and the display obscures the minutes field.

This class works by maintaining the actual time it represents, and

calculating a new fuzzy time whenever it is asked to display the

fuzzy time. It keeps a low water mark so an earlier time is never


class FuzzyTime

Initialize this object with a known time and a number of minutes

to randomly vary

time -> initial time

fuzzy_minutes -> number of minutes to randomly vary time

def initialize(time=Time.now, fuzzy_minutes=5)
@fuzzy_factor = fuzzy_minutes
@current_time = time

Print the fuzzy time in a format obscuring the last number of the


def to_s
ft = fuzzy_time
s = ft.strftime("%H:%M")
s[4] = ‘~’

Manually advance time by a certain number of seconds. Seconds

cannot be negative

seconds -> number of seconds to advance. Will throw exception

if negative

def advance(seconds)
raise “advance: seconds cannot be negative” if seconds < 0
@current_time = @current_time + seconds

Update the current time with the number of seconds that has

elapsed since the last time this method was called

def update
@current_time = @current_time + update_interval

Reports real time as Time

def actual


sets the current low water mark. This is so the fuzzy time never

goes backwards

def set_low_water_mark
@low_water_mark = @current_time - (@fuzzy_factor*60)

Updates the last time initialize, advance or update was called

def set_updated
@last_updated = Time.now

Gets the number of seconds since the last update

def update_interval
Time.now - @last_updated

Sets fuzzy time to be +/- the fuzzy factor from the current time,

while ensuring that we never return an earlier time than the one

returned last time we were called.

def fuzzy_time
fuzzy_seconds = @fuzzy_factor * 60

# Raise the low watermark if it is lower than allowed, if we
# advanced by a huge degree, etc.
@low_water_mark =
  [@low_water_mark, @current_time - fuzzy_seconds].max

# Compute a new random time, and set it to be the fuzzy time if
# it is higher than the low water mark. The algorithm is biased
# to return a negative time. This is to compensate for the low
# water mark. We want the time to be behind as much as it is
# ahead. At least 60 percent of the time the time will not
# advance here.
random_time = @current_time +
  (rand(fuzzy_seconds*5) - fuzzy_seconds*4)
fuzzy_time = [@low_water_mark, random_time].max

# Update the low water mark if necessary
@low_water_mark = [@low_water_mark, fuzzy_time].max



The unit test to calculate means and variances:

----- FuzzyTimeTest.rb

require ‘FuzzyTime’

class FuzzyTimeTest < Test::Unit::TestCase

Advances the fuzzy time by one minute repeatedly, keeping track

of how many minutes it took to go to the next time on the clock.

Calculates mean and variance to see if the intervals center

around 10 minutes and how much they vary from the mean. We want

the average to hover close to 10 minutes and the variance to be

as large as possible.

def test_intervals
ft = FuzzyTime.new

last_time = nil
fuzzy_interval = 0
intervals = Array.new
100000.times do
  ft.advance 60
  fuzzy_time = ft.to_s

  if last_time.nil? || last_time == fuzzy_time
    fuzzy_interval += 1
    intervals.push fuzzy_interval
    fuzzy_interval = 0
  last_time = fuzzy_time

average = intervals.inject {|sum,val| sum + val }.to_f /
puts "Mean interval: #{average}"

variance = intervals.inject {|sum, val| sum+(val-average).abs}.to_f

puts “Variance: #{variance}”


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.



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
You can set the “wobble” (maximum allowable error) with ‘-w ’.
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 P. [email protected]

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
@fuzz = 1 # the number of digits to fuzz
out (int)
@method = 2 # the update algorithm to use
@am_pm = true # report 12-hour time? (boolean)
@current = @actual + offset

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

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 )

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
range = @wobble - min_offset + 1
range = max_range if range > max_range

 if range == 0
   delta = 0
   if @method == 1
     # pick a new offset within the legal range of offsets.
     delta = @wobble - rand(range)
     # 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
return 60 * delta

Report the difference (in minutes) between the reported and

actual times.
def error
(current - actual).to_i/60

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

def to_s
if @am_pm
display = @current.strftime(“%I:%M”)
display = @current.strftime(“%H:%M”)
@fuzz.times { display.sub!(/\d(\D*)$/, ‘~\1’) } if @fuzz > 0

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

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

Robert Conn wrote:

As a sidenote, I assume Ruby doesn’t support overloaded methods? I
initially tried to implement initialize with 2 methods, one with no
parameters, one with the time parameter but it didn’t work.

No: any time you find
def something

it redefines the previous meaning. But you can use optional arguments

def func(a,b = :special)
if b == :special
# code for one arg
# code for two

I agree it is quite cumbersome, but I think when you get more used to
Ruby, you won’t need overloaded methods anymore.


On Oct 30, 2006, at 12:43 PM, Daniel L. wrote:

This is my first entry to Ruby Q…


James Edward G. II


Thanks for the comments. This -

@display.strftime("%H:%M").sub(/\d$/, “~”)

is certainly more elegant than mine.

Gavin - I was able to test the code with your test class. I don’t
claim to fully understand it yet though. It’s good to finally do
some test driven development.

As a sidenote, I assume Ruby doesn’t support overloaded methods? I
initially tried to implement initialize with 2 methods, one with no
parameters, one with the time parameter but it didn’t work.