module Workpattern # @author Barrie Callender # @!attribute values # @return [Array] each day of the week # @!attribute days # @return [Fixnum] number of days in the week # @!attribute start # @return [DateTime] first date in the range # @!attribute finish # @return [DateTime] last date in the range # @!attribute week_total # @return [Fixnum] total number of minutes in a week # @!attribute total # @return [Fixnum] total number of minutes in the range # # Represents working and resting periods for each day in a week for a specified date range. # class Week attr_accessor :values, :days, :start, :finish, :week_total, :total # Initialises an instance of class Week # @param [DateTime] start first date in the range # @param [DateTime] finish last date in the range # @param [Fixnum] type working (1) or resting (0) # @return [Week] newly initialised Week object # def initialize(start,finish,type=1) hours_in_days_in_week=[24,24,24,24,24,24,24] @days=hours_in_days_in_week.size @values=Array.new(7) {|index| Day.new(type)} @start=DateTime.new(start.year,start.month,start.day) @finish=DateTime.new(finish.year,finish.month,finish.day) set_attributes end # Duplicates the current Week object # @return [Week] a duplicated instance of the current Week object # def duplicate() duplicate_week=Week.new(@start,@finish) duplicate_values=Array.new(@values.size) @values.each_index {|index| duplicate_values[index]=@values[index].duplicate } duplicate_week.values=duplicate_values duplicate_week.days=@days duplicate_week.start=@start duplicate_week.finish=@finish duplicate_week.week_total=@week_total duplicate_week.total=@total duplicate_week.refresh return duplicate_week end # Recalculates the attributes that defines a Week object. # This was made public for #duplicate to work # def refresh set_attributes end # Changes the date range. # This method calls #refresh to update the attributes. # def adjust(start,finish) @start=DateTime.new(start.year,start.month,start.day) @finish=DateTime.new(finish.year,finish.month,finish.day) refresh end # Sets a range of minutes in a week to be working or resting. The parameters supplied # to this method determine exactly what should be changed # @param [Hash] days identifies the days to be included in the range # @param [DateTime] from_time where the time portion is used to specify the first minute to be set # @param [DateTime] to_time where the time portion is used to specify the last minute to be set # @param [Fixnum] type where a 1 sets it to working and a 0 to resting def workpattern(days,from_time,to_time,type) DAYNAMES[days].each {|day| @values[day].workpattern(from_time,to_time,type)} refresh end # Calculates a new date by adding or subtracting a duration in minutes. # @param [DateTime] start original date # @param [Fixnum] duration minutes to add or subtract # @param [Boolean] midnight flag used for subtraction that indicates the start date is midnight # def calc(start,duration, midnight=false) return start,duration,false if duration==0 return add(start,duration) if duration > 0 return subtract(@start,duration, midnight) if (@total==0) && (duration <0) return subtract(start,duration, midnight) if duration <0 end # Comparison — Returns an integer (-1, 0, or +1) if week is less than, equal to, or greater than other_week # @param [Week] other_week object to compare to # @return [Integer] -1,0 or +1 if week is less than, equal to or greater than other_week def <=>(obj) if @start < obj.start return -1 elsif @start == obj.start return 0 else return 1 end end # Returns true if the supplied DateTime is working and false if resting # @param [DateTime] start DateTime to be tested # @return [Boolean] true if the minute is working otherwise false if it is a resting minute # def working?(start) @values[start.wday].working?(start) end # Returns the difference in minutes between two DateTime values. # @param [DateTime] start starting DateTime # @param [DateTime] finish ending DateTime # @return [Fixnum] number of minutes # @return [DateTime] start date for rest of calculation. The calculation is complete when this is the same as the finish date # def diff(start,finish) start,finish=finish,start if ((start <=> finish))==1 # calculate to end of day # if (start.jd==finish.jd) # same day duration, start=@values[start.wday].diff(start,finish) elsif (finish.jd<=@finish.jd) #within this week duration, start=diff_detail(start,finish,finish) else # after this week duration, start=diff_detail(start,finish,@finish) end return duration, start end private # Recalculates all the attributes for a Week object # def set_attributes @total=0 @week_total=0 days=(@finish-@start).to_i + 1 #/60/60/24+1 if (7-@start.wday) < days and days < 8 @total+=total_hours(@start.wday,@finish.wday) @week_total=@total else @total+=total_hours(@start.wday,6) days -= (7-@start.wday) @total+=total_hours(0,@finish.wday) days-=(@finish.wday+1) @week_total=@total if days==0 week_total=total_hours(0,6) @total+=week_total * days / 7 @week_total=week_total if days != 0 end end # Calculates the total number of minutes between two dates # @param [DateTime] start is the first date in the range # @param [DateTime] finish is the last date in the range # @return [Fixnum] total number of minutes between supplied dates # def total_hours(start,finish) total=0 start.upto(finish) {|day| total+=@values[day].total } return total end # Adds a duration in minutes to a date # @param [DateTime] start original date # @param [Fixnum] duration minutes to add # @param [Boolean] midnight flag used for subtraction that indicates the start date is midnight # @return [DateTime] the calculated date # @return [Fixnum] the number of minutes still to be added # @return [Boolean] Always false, this is the flag used for subtraction # def add(start,duration) # aim to calculate to the end of the day start,duration = @values[start.wday].calc(start,duration) return start,duration,false if (duration==0) || (start.jd > @finish.jd) # aim to calculate to the end of the next week day that is the same as @finish while((duration!=0) && (start.wday!=@finish.next_day.wday) && (start.jd <= @finish.jd)) if (duration>@values[start.wday].total) duration = duration - @values[start.wday].total start=start.next_day elsif (duration==@values[start.wday].total) start=after_last_work(start) duration = 0 else start,duration = @values[start.wday].calc(start,duration) end end return start,duration,false if (duration==0) || (start.jd > @finish.jd) # while duration accomodates full weeks while ((duration!=0) && (duration>=@week_total) && ((start.jd+6) <= @finish.jd)) duration=duration - @week_total start=start+7 end return start,duration,false if (duration==0) || (start.jd > @finish.jd) # while duration accomodates full days while ((duration!=0) && (start.jd<= @finish.jd)) if (duration>@values[start.wday].total) duration = duration - @values[start.wday].total start=start.next_day else start,duration = @values[start.wday].calc(start,duration) end end return start, duration, false end # Subtracts a duration in minutes from a date # @param [DateTime] start original date # @param [Fixnum] duration minutes to subtract - always a negative # @param [Boolean] midnight flag indicates the start date is midnight when true # @return [DateTime] the calculated date # @return [Fixnum] the number of minutes still to be subtracted # @return [Boolean] When set to true indicates the time is midnight on the given date # def subtract(start,duration,midnight=false) # Handle subtraction from start of day if midnight start,duration=minute_b4_midnight(start,duration) midnight=false end # aim to calculate to the start of the day start,duration, midnight = @values[start.wday].calc(start,duration) if midnight && (start.jd >= @start.jd) start,duration=minute_b4_midnight(start,duration) return subtract(start,duration, false) elsif midnight return start,duration,midnight elsif (duration==0) || (start.jd ==@start.jd) return start,duration, midnight end # aim to calculate to the start of the previous week day that is the same as @start while((duration!=0) && (start.wday!=@start.wday) && (start.jd >= @start.jd)) if (duration.abs>=@values[start.wday].total) duration = duration + @values[start.wday].total start=start.prev_day else start,duration=minute_b4_midnight(start,duration) start,duration = @values[start.wday].calc(start,duration) end end return start,duration if (duration==0) || (start.jd ==@start.jd) #while duration accomodates full weeks while ((duration!=0) && (duration.abs>=@week_total) && ((start.jd-6) >= @start.jd)) duration=duration + @week_total start=start-7 end return start,duration if (duration==0) || (start.jd ==@start.jd) #while duration accomodates full days while ((duration!=0) && (start.jd>= @start.jd)) if (duration.abs>=@values[start.wday].total) duration = duration + @values[start.wday].total start=start.prev_day else start,duration=minute_b4_midnight(start,duration) start,duration = @values[start.wday].calc(start,duration) end end return start, duration , midnight end # Supports calculating from midnight by updating the given duration depending on whether the # last minute in the day is resting or working. It then sets the time to this minute. # @param [DateTime] start is the date whose midnight is to be used as the start date # @param [Fixnum] duration is the number of minutes to subtract # @return [DateTime] the date with a time of 23:59 # @return [Fixnum] the duration adjusted according to whether 23:59 is resting or not # def minute_b4_midnight(start,duration) start -= start.hour * HOUR start -= start.min * MINUTE duration += @values[start.wday].minutes(23,59,23,59) start = start.next_day - MINUTE return start,duration end # Calculates the date and time after the last working minute of the current date # @param [DateTime] start is the current date # @return [DateTime] the new date # def after_last_work(start) if @values[start.wday].last_hour.nil? return start.next_day else start = start + HOUR * (@values[start.wday].last_hour - start.hour) start = start + MINUTE * (@values[start.wday].last_min - start.min + 1) return start end end # Calculates the difference between two dates that exist in this Week object. # @param [DateTime] start first date # @param [DateTime] finish last date # @param [DateTime] finish_on the range to be used in this Week object. # @return [DateTime] new date for rest of calculation otherwise same as finish if completed calculation # @return [Fixnum] total number of minutes calculated thus far. # def diff_detail(start,finish,finish_on) duration, start=@values[start.wday].diff(start,finish) #rest of week to finish day while (start.wday