aldesantis/artic

View on GitHub
lib/artic/occupation.rb

Summary

Maintainability
A
0 mins
Test Coverage
# frozen_string_literal: true

module Artic
  # An occupation represents a slot of time in a given date where you are not available.
  #
  # @author Alessandro Desantis
  class Occupation
    attr_reader :date, :time_range

    # Initializes the occupation.
    #
    # @param date [Date] the date of the occupation
    # @param time_range [TimeRange|Range] the time range of the occupation
    def initialize(date, time_range)
      @date = date
      @time_range = TimeRange.build(time_range)
    end

    # Converts the occupation to a range of +Time+ objects.
    #
    # @return [Range]
    #
    # @see TimeRange#with_date
    def to_range
      time_range.with_date(date)
    end

    # Returns whether the occupation overlaps the availability (i.e. whether there's at least one
    # moment in time shared by both the availability and the occupation).
    #
    # @param availability [Availability]
    #
    # @return [Boolean]
    def overlaps?(availability)
      return false unless availability.for_date?(date)

      availability_range = availability.time_range.with_date(date)
      (availability_range.min <= to_range.max) && (availability_range.max >= to_range.min)
    end

    # Returns whether the occupation covers the availability (i.e. whether all moments of the
    # availability are also part of the occupation).
    #
    # @param availability [Availability]
    #
    # @return [Boolean]
    def covers?(availability)
      return false unless availability.for_date?(date)

      availability_range = availability.time_range.with_date(date)
      to_range.min <= availability_range.min && to_range.max >= availability_range.max
    end

    # Determines whether this occupation and the one passed as an argument represent the same
    # day/time range combination, by checking for equality of both the date and the time
    # range.
    #
    # @param other [Occupation]
    #
    # @return [Boolean]
    def ==(other)
      date == other.date && time_range == other.time_range
    end

    # Compares this occupation with another one by comparing their ranges' start.
    #
    # @param other [Occupation]
    #
    # @return [Fixnum] -1 if this occupation should come before the other one, 0 if it should be
    #   in the same position, 1 if it should come after the other one
    def <=>(other)
      to_range.min <=> other.to_range.min
    end

    # Reduces the time range of the given availability or bisects it into two availabilities,
    # depending on where the occupation falls within the availability.
    #
    # If the occupation completely covers the availability, returns an empty collection.
    #
    # If the occupation does not overlap the availability at all, returns a collection with
    # the original availability.
    #
    # @param availability [Availability]
    #
    # @return Collection::AvailabilityCollection a collection of availabilities where the time
    #   slot assigned to the occupation has been removed
    #
    # @example
    #   occupation = Artic::Occupation.new(Date.today, '08:00'..'14:00')
    #   availability = Artic::Availability.new(Date.today, '09:00'..'18:00')
    #
    #   occupation.bisect(availability)
    #   # => [
    #   #   #<Artic::Availability 14:00..18:00>
    #   # ]
    #
    # @example
    #   occupation = Artic::Occupation.new(Date.today, '12:00'..'14:00')
    #   availability = Artic::Availability.new(Date.today, '09:00'..'18:00')
    #
    #   occupation.bisect(availability)
    #   # => [
    #   #   #<Artic::Availability 09:00..12:00>,
    #   #   #<Artic::Availability 14:00..18:00>
    #   # ]
    #
    # @example
    #   occupation = Artic::Occupation.new(Date.today, '16:00'..'20:00')
    #   availability = Artic::Availability.new(Date.today, '09:00'..'18:00')
    #
    #   occupation.bisect(availability)
    #   # => [
    #   #   #<Artic::Availability 09:00..16:00>
    #   # ]
    def bisect(availability)
      return Collection::AvailabilityCollection.new if covers?(availability)
      return Collection::AvailabilityCollection.new([availability]) unless overlaps?(availability)

      bisected_ranges = time_range.bisect(availability.time_range)

      availabilities = bisected_ranges.map do |bisected_range|
        Availability.new(date, bisected_range)
      end

      Collection::AvailabilityCollection.new availabilities
    end
  end
end