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

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
# displayed.
#
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
    set_low_water_mark
    set_updated
  end

  # Print the fuzzy time in a format obscuring the last number of the
  # time.
  def to_s
    ft = fuzzy_time
    s = ft.strftime("%H:%M")
    s[4] = '~'
    s
  end

  # 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
    set_updated
  end

  # 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
    set_updated
  end

  # Reports real time as Time
  def actual
    @current_time
  end

private
  # 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)
  end

  # Updates the last time initialize, advance or update was called
  def set_updated
    @last_updated = Time.now
  end

  # Gets the number of seconds since the last update
  def update_interval
    Time.now - @last_updated
  end

  # 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

    fuzzy_time
  end
end

-----

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
      else
        intervals.push fuzzy_interval
        fuzzy_interval = 0
      end
      last_time = fuzzy_time
    end

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

    variance = intervals.inject {|sum, val| sum+(val-average).abs}.to_f
/
      intervals.length
    puts "Variance: #{variance}"
  end
end