From:	Robert Conn [mailto:bob.conn / btinternet.com]
> I haven't had time to do any "extra credit" stuff, or
> even test this fully, but here goes -

For what it's worth, here's the test code I've written to test submissions. I'm not 100% sure that it's correctly testing things yet, but I think it is :)

The interesting bit for me has been trying to identify any solution that ignores requirement #2, such as solutions that always show the correct time, or that are always a bit fast or slow. The statistics are my best attempt at that so far.

To use, if you have defined your class name as "FuzzyTime", just include this file after your class definition. (Beware long incorrectly wrapped lines ahead.)

require 'test/unit'

$DEBUG = false

class Time
  def short
    strftime("%H:%M")
  end
  def to_fuzzy
    s = short
    s[4] = "~"
    s
  end
end

class FuzzyTimeTester < Test::Unit::TestCase
  def test_running
    runs, num_ahead, num_behind = 0, 0, 0
    20.times{
      t = Time.at( Time.new + rand( 3600 * 300 ) )
      advance_seconds = rand( 200 ) + 17
      end_time = t + 60 * 60 * 24
      ft = FuzzyTime.new( t )
      last_value = nil
      while( t < end_time )
        assert_equal t, ft.actual
        t0 = Time.at( t - 60 * 5 )
        t2 = Time.at( t + 60 * 5 )
        legal_values = [ t0, t, t2 ].map{ |x| x.to_fuzzy }.uniq
      
        if last_value
          y,mon,day = t.year, t.mon, t.day
          h,m = last_value.scan(/\d+/).map{ |s| s.to_i }
          m *= 10
          if (m -= 10) < 0
            m %= 60
            if (h -= 1) < 0
              h %= 24
            end
          end
          illegal_old_value = Time.local( y,mon,day,h,m ).to_fuzzy
          legal_values -= [ illegal_old_value ]
          if $DEBUG
            puts "Now: #{t.short}; legal: #{legal_values.inspect} (was #{last_value}, can't be #{illegal_old_value})"
          end
        end
    
        s = ft.to_s
        assert legal_values.include?( s ), "#{s} not in #{legal_values.inspect}"
      
        a = t.to_fuzzy
        if s != a
          ahour, amin = a.scan( /\d+/ ).map{ |x| x.to_i }
          fhour, fmin = s.scan( /\d+/ ).map{ |x| x.to_i }
          if fmin > amin || fhour > ahour || ( fhour == 0 && ahour == 23 )
            num_ahead += 1
          else
            num_behind +=1
          end
        end
        runs += 1     
        last_value = s
        ft.advance( advance_seconds )
        t += advance_seconds
      end
      puts "Variation: %.2f%% ahead, %.2f%% behind" % [ 100.0 * num_ahead / runs, 100.0 * num_behind / runs ]
    }
  end
end