avarteqgmbh/gadgeto

View on GitHub
lib/gadgeto/time_of_day.rb

Summary

Maintainability
A
45 mins
Test Coverage
module Gadgeto
  # A +TimeOfDay+ represents the time of day.
  #
  # Limitation: 24 hours only.
  class TimeOfDay

    class InvalidTimeOfDayFormat < ::Exception # :nodoc:
    end

    class TimeOfDayOutOfRange < ::Exception # :nodoc:
    end

    ONE_HOUR_IN_MINUTES = 60 # :nodoc:
    HOURS_ONE_DAY = 24 # :nodoc:

    attr_reader :hour, :minute

    # Creates a new TimeOfDay object.
    #
    #    TimeOfDay.new("08:00") #=> 08:00
    #    TimeOfDay.new("x8:00") #=> raises InvalidTimeOfDayFormat
    #
    def initialize(time_of_day)
      match = /^([0-2]?\d):([0-5]\d)$/.match(time_of_day)
      raise InvalidTimeOfDayFormat if match.nil?
      @hour = match[1].to_i
      @minute = match[2].to_i
      raise InvalidTimeOfDayFormat if (@hour > HOURS_ONE_DAY) || (@hour == HOURS_ONE_DAY && @minute > 0)
    end

    # Returns the hour component.
    #
    #   t = TimeOfDay.new("08:00") #=> 08:00
    #   t.hour                     #=> 8
    def hour
      @hour
    end

    # Returns the hour component.
    #
    #   t = TimeOfDay.new("08:03") #=> 08:00
    #   t.minute                   #=> 3
    def minute
      @minute
    end

    # Adds the given +minutes+ to +self+.
    #
    #    t = TimeOfDay.new("08:00") #=> 08:00
    #    t.add_minutes(60)          #=> 09:00
    #
    #    t = TimeOfDay.new("23:50") #=> 08:00
    #    t.add_minutes(20)          #=> 00:10
    def add_minutes(minutes)
      new_minutes = @minute + minutes
      hours_to_add = (@minute + minutes) / 60
      new_hour = @hour + hours_to_add

      @hour = new_hour % HOURS_ONE_DAY
      @minute = new_minutes % ONE_HOUR_IN_MINUTES

      self
    end

    # Returns string representing time of day.
    #
    #    TimeOfDay.new("08:00").to_s #=> "08:00"
    def to_s
      "#{hour.to_s.rjust(2, '0')}:#{minute.to_s.rjust(2, '0')}"
    end

    # Returns time of day in minutes.
    #
    #    TimeOfDay.new("02:07") #=> 127
    def to_i
      hour * 60 + minute
    end

    # Returns +true+ if +self+ precedes +other+.
    #
    #    TimeOfDay.new("08:00") < TimeOfDay.new("09:00:) #=> true
    def <(other)
      return true if self.hour < other.hour
      return false if self.hour > other.hour

      self.minute < other.minute
    end

    # Returns +true+ if +self+ follows +other+.
    #
    #    TimeOfDay.new("09:00") < TimeOfDay.new("08:00:) #=> true
    def >(other)
      return true if self.hour > other.hour
      return false if self.hour < other.hour

      self.minute > other.minute
    end

    # Returns true if +self+ is equal to +other+.
    #
    #   TimeOfDay.new("08:00") == TimeOfDay.new("08:00") #=> true
    def ==(other)
      self.hour == other.hour && self.minute == other.minute
    end

    # Returns difference in minutes between +self+ and +other+.
    #
    #    TimeOfDay.new("08:00").minutes_till(TimeOfDay.new("09:00"))                     #=> 60
    #    TimeOfDay.new("23:00").minutes_till(TimeOfDay.new("01:00"))                     #=> 120
    #
    #    TimeOfDay.new("23:00").minutes_till(TimeOfDay.new("01:00"), :overflow => false) #=> raises TimeOfDayOutOfRange
    def minutes_till(other, options = {})
      options[:overflow] = true if options[:overflow].nil?

      if options[:overflow]
        if self > other
          (self.minutes_till(TimeOfDay.new("24:00"))) + other.to_i
        else
          other.to_i - self.to_i
        end
      else
        raise TimeOfDayOutOfRange if self > other
        other.to_i - self.to_i
      end
    end

    # Returns +true+ if +time_of_day+ represents time of day.
    #
    #    TimeOfDay.valid?("08:00") #=> true
    def self.valid?(time_of_day)
      begin
        val = TimeOfDay.new(time_of_day)
        true
      rescue InvalidTimeOfDayFormat, TimeOfDayOutOfRange
        false
      end
    end

    alias :inspect :to_s

  end
end