--envbJBWh7q8WU6mo
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline

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 default)
    - Display time : Fuzzed time 'floored' to a granularity (10 min default)

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  he maximum of the last display time or (actual - fuzz factor minutes)
        - upper bound  ctual + 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!

enjoy,

-jeremy

--

 Jeremy Hinegardner                              jeremy / hinegardner.org 


--envbJBWh7q8WU6mo
Content-Type: text/plain; charset=us-ascii
Content-Disposition: attachment; filename="fuzzytime.rb"

#----------------------------------------------------------------------
# Ruby Quiz #99 - Fuzzy Time
#
# Jeremy Hinegardner
#----------------------------------------------------------------------
class FuzzyTime

    HOUR_24_FORMAT   %H:%M"
    HOUR_12_FORMAT   %I:%M"

    HOUR_FORMAT_OPTIONS  ash.new(HOUR_24_FORMAT)
    HOUR_FORMAT_OPTIONS[HOUR_12_FORMAT]      OUR_12_FORMAT
    HOUR_FORMAT_OPTIONS[:twelve_hour]        OUR_12_FORMAT
    HOUR_FORMAT_OPTIONS[HOUR_24_FORMAT]      OUR_24_FORMAT
    HOUR_FORMAT_OPTIONS[:twentyfour_hour]    OUR_24_FORMAT
    
    GRANULARITIES  :one_minute, :ten_minute, :one_hour]

    attr_accessor :time_format
    attr_accessor :fuzz_factor
    attr_accessor :display_granularity

    attr_reader   :actual
    attr_reader   :fuzzed
    attr_reader   :display

    def initialize(time  ime.now)
        @actual               ime.dup
        @time_format          OUR_24_FORMAT
        @fuzz_factor           * 60
        @display_granularity  ten_minute

        # initialize a base history that is minimal to give maximum
        # range for calculating a fuzzy time
        @fuzzed          actual - @fuzz_factor
        @fuzz_history     { :actual @actual, :fuzzed @fuzzed, :display calculate_display_time } ]
        calculate_fuzz_time
    end

    def advance(seconds)
        @actual + econds
        calculate_fuzz_time
    end

    def update
        @actual  ime.now
        calculate_fuzz_time
    end

    # return a string representation of the display time which is the time
    # with up to the @disiplay_granularity replaced by '~'.  when
    # :one_minute is the granularity, nothing needs to be done
    def to_s
        s  display.strftime(time_format)
        case @display_granularity
        when :ten_minute
            s.chop!
            s << "~"
        when :one_hour
            s.chop!
            s.chop!
            s << "~~"
        end
        return s
    end

    # allow the time format to be set to 24 hour or 12 hour time
    def time_formatormat)
        @time_format  OUR_FORMAT_OPTIONS[format]
    end

    # all the fuzz factor to be set in a range of minutes, no limit
    def fuzz_factorinutes)
        @fuzz_factor  0 * minutes.abs
    end

    def fuzz_factor
        (@fuzz_factor / 60).to_i
    end

    def display_granularityranularity)
        if GRANULARITIES.include?(granularity.to_sym) 
            @display_granularity  ranularity
            # need to recalculate this when the granularity is updated 
            calculate_display_time
        else
            raise ArgumentError, "display_granularity must be one of #{GRANULARITIES.join(",")}"
        end
        @display_granularity
    end
    
    private

    #
    # the display time is the fuzzy time converted to a "floor" time
    # with a granularity base upon the @display_granularity.  For
    # example, if the fuzzed time is 13:43 then the display time would
    # be:
    #
    #   granularity     display
    #   
    #   :one_minute     13:43
    #   :ten_minute     13:40
    #   :one_hour       13:00
    #
    def calculate_display_time
        case @display_granularity
        when :one_minute 
            min  fuzzed.min
        when :ten_minute
            min  @fuzzed.min / 10) * 10
        when :one_hour
            min  
        end

        @display  ime.mktime( @fuzzed.year, @fuzzed.month, @fuzzed.day, @fuzzed.hour, min, 0, 0)
    end

    #
    # calculate the new fuzzy time.
    #
    # Since :
    #   1) the displayed time must appear to be continually increasing
    #   2) we must always be within the fuzz factor of the actual time
    #
    # Therefore:
    #   the lower bound of the fuzzy range is the maximum of the displayed
    #   time or the lower bond of the fuzz factor around the actual time.
    #
    def calculate_fuzz_time
        last_display     fuzz_history.last[:display]
        min_fuzz_factor  actual - @fuzz_factor
        lower_bound  ast_display > min_fuzz_factor ? last_display : min_fuzz_factor

        upper_bound  actual + @fuzz_factor

        range        pper_bound - lower_bound
        @fuzzed      ower_bound + rand(range + 1)
        calculate_display_time
        @fuzz_history << { :actual @actual, :fuzzed @fuzzed, :display @display } 
    end

end

--envbJBWh7q8WU6mo--