#!/usr/bin/env ruby
################################################################################
# quiz99.rb: Implementation of RubyQuiz #99 - Fuzzy Time
#
# Lou Scoras <louis.j.scoras / gmail.com>
# Sunday, October 29th, around 11 o-clock (*smirks*).
#
# I decided to approximate time to the quarter hour. When people on the street
# 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
# quiz.
#
################################################################################
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}"
end
end
# Let the Time library to the hard work for getting 12 time.
def hour_12
(strftime "%I").sub(/\A0/,'')
end
end
class FuzzyTime
# Quarter hours fall every 900 seconds
TimeScale = 60 * 15
def initialize(time = Time.now)
@real = @time = time
@emitted = nil
end
# 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
# bit.
#
# 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 (!@emitted.nil? && @emitted > last_valid) \
|| pick >= threshold
next_valid
else
last_valid
end
@emitted.to_quarter_s
end
def inspect
t_obj = if @emitted.nil?
self.class.new(@time).next_valid
else
@emitted
end
%!FuzzyTime["#{t_obj.to_quarter_s}"]!
end
def actual
@time
end
def last_valid
Time.at(@time.to_i / TimeScale * TimeScale)
end
def next_valid
last_valid + TimeScale
end
def advance(offset)
@real = Time.now
@time += offset
offset
end
def update
now = Time.now
delta = now - @real
@time += delta
@real = now
end
end
# 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
end
def get_sample_rate
default = 60
return default unless ARGV[0]
begin
return ARGV[0].to_i
rescue
return default
end
end
if caller.empty?
ft = FuzzyTime.new
while true do
clear
puts ft
sleep get_sample_rate
ft.update
end
end