module Workpattern
require 'set'
# Represents the working and resting periods across a number of whole years. The #base year
# is the first year and the #span is the number of years including that year that is covered.
# The #Workpattern is given a unique name so it can be easily identified amongst other Workpatterns.
#
class Workpattern
# Holds collection of Workpattern objects
@@workpatterns = Hash.new()
attr_accessor :name, :base, :span, :from, :to, :weeks
def initialize(name=DEFAULT_NAME,base_year=DEFAULT_BASE_YEAR,span=DEFAULT_SPAN)
raise(NameError, "Workpattern '#{name}' already exists and can't be created again") if @@workpatterns.key?(name)
if span < 0
offset = span.abs - 1
else
offset = 0
end
@name = name
@base = base_year
@span = span
@from = DateTime.new(base_year.abs - offset)
@to = DateTime.new(@from.year + span.abs - 1,12,31,23,59)
@weeks = SortedSet.new
@weeks << Week.new(@from,@to,1)
@@workpatterns[name]=self
end
def self.clear
@@workpatterns.clear
end
def self.to_a
@@workpatterns.to_a
end
def self.get(name)
return @@workpatterns[name] if @@workpatterns.key?(name)
raise(NameError, "Workpattern '#{name}' doesn't exist so can't be retrieved")
end
def self.delete(name)
if @@workpatterns.delete(name).nil?
return false
else
return true
end
end
# Sets a work or resting pattern in the _Workpattern_.
#
# Can also use resting and working methods leaving off the
# :work_type
#
# === Parameters
#
# * :start - The first date to apply the pattern. Defaults
# to the _Workpattern_ start.
# * :finish - The last date to apply the pattern. Defaults to
# the _Workpattern_ finish.
# * :days - The specific day or days the pattern will apply to. This
# references _Workpattern::DAYNAMES_. It defailts to :all which is
# everyday. Valid values are :sun, :mon, :tue, :wed, :thu, :fri, :sat,
# :weekend, :weekday and :all
# * :start_time - The first time in the selected days to apply the pattern.
# Must implement #hour and #min to get the Hours and Minutes for the time. It will default to
# the first time in the day 00:00.
# * :finish_time - The last time in the selected days to apply the pattern.
# Must implement #hour and #min to get the Hours and Minutes for the time. It will default to
# to the last time in the day 23:59.
# * :work_type - type of pattern is either working (1 or Workpattern::WORK) or
# resting (0 or Workpattern::REST). Alternatively make use of the working
# or resting methods that will set this value for you
#
def workpattern(args={})
#
upd_start = args[:start] || @from
upd_start = dmy_date(upd_start)
args[:start] = upd_start
upd_finish = args[:finish] || @to
upd_finish = dmy_date(upd_finish)
args[:finish] = upd_finish
#args[:days] = args[:days] || :all
days= args[:days] || :all
from_time = args[:from_time] || FIRST_TIME_IN_DAY
from_time = hhmn_date(from_time)
#args[:from_time] = upd_from_time
to_time = args[:to_time] || LAST_TIME_IN_DAY
to_time = hhmn_date(to_time)
#args[:to_time] = upd_to_time
args[:work_type] = args[:work_type] || WORK
type= args[:work_type] || WORK
while (upd_start <= upd_finish)
current_wp=find_weekpattern(upd_start)
if (current_wp.start == upd_start)
if (current_wp.finish > upd_finish)
clone_wp=current_wp.duplicate
current_wp.adjust(upd_finish+1,current_wp.finish)
clone_wp.adjust(upd_start,upd_finish)
clone_wp.workpattern(days,from_time,to_time,type)
@weeks<< clone_wp
upd_start=upd_finish+1
else # (current_wp.finish == upd_finish)
current_wp.workpattern(days,from_time,to_time,type)
upd_start=current_wp.finish + 1
end
else
clone_wp=current_wp.duplicate
current_wp.adjust(current_wp.start,upd_start-1)
clone_wp.adjust(upd_start,clone_wp.finish)
if (clone_wp.finish <= upd_finish)
clone_wp.workpattern(days,from_time,to_time,type)
@weeks<< clone_wp
upd_start=clone_wp.finish+1
else
after_wp=clone_wp.duplicate
after_wp.adjust(upd_finish+1,after_wp.finish)
@weeks<< after_wp
clone_wp.adjust(upd_start,upd_finish)
clone_wp.workpattern(days,from_time,to_time,type)
@weeks<< clone_wp
upd_start=clone_wp.finish+1
end
end
end
end
# Identical to the workpattern method apart from it always creates
# resting patterns so there is no need to set the :work_type argument
#
def resting(args={})
args[:work_type]=REST
workpattern(args)
end
# Identical to the workpattern method apart from it always creates
# working patterns so there is no need to set the :work_type argument
#
def working(args={})
args[:work_type]=WORK
workpattern(args)
end
# :call-seq: calc(start,duration) => DateTime
# Calculates the resulting date when #duration is added to #start date using the #Workpattern.
# Duration is always in whole minutes and can be a negative number, in which case it subtracts
# the minutes from the date.
#
def calc(start,duration)
return start if duration==0
midnight=false
while (duration !=0)
week=find_weekpattern(start)
if (week.start==start) && (duration<0) && (!midnight)
start=start.prev_day
week=find_weekpattern(start)
midnight=true
end
start,duration,midnight=week.calc(start,duration,midnight)
end
return start
end
# :call-seq: working?(start) => Boolean
# Returns true if the given minute is working and false if it isn't
#
def working?(start)
return find_weekpattern(start).working?(start)
end
# :call-seq: diff(start,finish) => Duration
# Returns number of minutes between two dates
#
def diff(start,finish)
start,finish=finish,start if finish@to
result = Week.new(@to+MINUTE,DateTime.new(9999),1)
else
date = DateTime.new(date.year,date.month,date.day)
result=@weeks.find {|week| week.start <= date and week.finish >= date}
end
return result
end
def dmy_date(date)
return DateTime.new(date.year,date.month,date.day)
end
def hhmn_date(date)
return DateTime.new(2000,1,1,date.hour,date.min)
end
end
end