workpattern 0.3.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/Rakefile CHANGED
@@ -9,3 +9,12 @@ Rake::TestTask.new do |test|
9
9
  test.test_files = Dir["test/test_*.rb"]
10
10
  test.verbose = true
11
11
  end
12
+
13
+ task :console do
14
+ require 'irb'
15
+ require 'irb/completion'
16
+ require 'workpattern' # You know what to do.
17
+ ARGV.clear
18
+ IRB.start
19
+ end
20
+
data/lib/workpattern.rb CHANGED
@@ -3,148 +3,88 @@
3
3
  #
4
4
  # email: barrie@callenb.org
5
5
  #++
6
- $:.unshift(File.dirname(__FILE__)) unless
7
- $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
6
+ $LOAD_PATH.unshift(File.dirname(__FILE__)) unless
7
+ $LOAD_PATH.include?(File.dirname(__FILE__)) || $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
8
8
 
9
9
  require 'rubygems'
10
+ require 'sorted_set' if RUBY_VERSION >= "2.4"
10
11
  require 'date'
11
- require 'workpattern/utility/base.rb'
12
12
  require 'workpattern/clock'
13
- require 'workpattern/hour'
13
+ require 'workpattern/constants'
14
14
  require 'workpattern/day'
15
15
  require 'workpattern/week'
16
16
  require 'workpattern/workpattern'
17
+ require 'workpattern/week_pattern'
17
18
 
18
19
  #
19
20
  # workpattern.rb - date calculation library that takes into account patterns of
20
- # working and resting time and is aimed at supporting scheduling applications such
21
- # as critical path analysis.
21
+ # working and resting time and is aimed at supporting scheduling applications
22
+ # such as critical path analysis.
22
23
  #
23
24
  # Author: Barrie Callender 2011
24
25
  #
25
26
  # Documentation: Barrie Callender <barrie@callenb.org>
26
27
  #
27
28
  module Workpattern
28
-
29
- # Represents a full working hour
30
- # @since 0.2.0
31
- WORKING_HOUR = 2**60-1
32
-
33
- # Represents a full resting hour
34
- # @since 0.2.0
35
- RESTING_HOUR = 0
36
-
37
- # The default workpattern name
38
- # @since 0.2.0
39
- DEFAULT_WORKPATTERN_NAME = 'default'
40
-
41
- # The default base year
42
- # @since 0.2.0
43
- DEFAULT_BASE_YEAR = 2000
44
-
45
- # The default span in years
46
- # @since 0.2.0
47
- DEFAULT_SPAN = 100
48
-
49
- # Hour in terms of days
50
- # @since 0.2.0
51
- HOUR = Rational(1,24)
52
-
53
- # Minute in terms of days
54
- # @since 0.2.0
55
- MINUTE = Rational(1,1440)
56
-
57
- # Earliest or first time in the day
58
- # @since 0.0.1
59
- FIRST_TIME_IN_DAY=Clock.new(0,0)
60
-
61
- # Latest or last time in the day
62
- # @since 0.0.1
63
- LAST_TIME_IN_DAY=Clock.new(23,59)
64
-
65
- # Specifies a working pattern
66
- # @since 0.0.1
67
- WORK = 1
68
-
69
- # Specifies a resting pattern
70
- # @since 0.0.1
71
- REST = 0
72
-
73
- # Represents the days of the week to be used in applying working and resting patterns.
74
- # Values exist for each day of the week as well as for the weekend (Saturday and Sunday),
75
- # the week (Monday to Friday) and all days in the week.
76
- #
77
- # @since 0.0.1
78
- DAYNAMES={:sun => [0],:mon => [1], :tue => [2], :wed => [3], :thu => [4], :fri => [5], :sat => [6],
79
- :weekday => [1,2,3,4,5],
80
- :weekend => [0,6],
81
- :all => [0,1,2,3,4,5,6]}
82
-
83
- # Covenience method to obtain a new <tt>Workpattern</tt>
29
+
30
+ # Covenience method to obtain a new <tt>Workpattern</tt>
84
31
  #
85
32
  # A negative <tt>span</tt> counts back from the <tt>base</tt> year
86
33
  #
87
34
  # @param [String] name Every workpattern has a unique name.
88
35
  # @param [Integer] base Workpattern starts on the 1st January of this year.
89
- # @param [Integer] span Workpattern spans this number of years ending on 31st December.
36
+ # @param [Integer] number of years ending on 31st December.
90
37
  # @return [Workpattern]
38
+ # @raise [NameError] creating a Workpattern with a name that already exists
91
39
  #
92
- # @since 0.2.0
93
- #
94
- def self.new(name=DEFAULT_WORKPATTERN_NAME, base=DEFAULT_BASE_YEAR, span=DEFAULT_SPAN)
95
- return Workpattern.new(name, base,span)
40
+ def self.new(name = DEFAULT_WORKPATTERN_NAME,
41
+ base = DEFAULT_BASE_YEAR,
42
+ span = DEFAULT_SPAN)
43
+ Workpattern.new(name, base, span)
96
44
  end
97
-
98
- # Covenience method to obtain an Array of all the known <tt>Workpattern</tt> objects
45
+
46
+ # Covenience method to obtain an Array of all the known <tt>Workpattern</tt>
47
+ # objects
99
48
  #
100
49
  # @return [Array] all <tt>Workpattern</tt> objects
101
50
  #
102
- # @since 0.2.0
103
- #
104
- def self.to_a()
105
- return Workpattern.to_a
51
+ def self.to_a
52
+ Workpattern.to_a
106
53
  end
107
54
 
108
- # Covenience method to obtain an existing <tt>Workpattern</tt>
55
+ # Covenience method to obtain an existing <tt>Workpattern</tt>
109
56
  #
110
57
  # @param [String] name The name of the Workpattern to retrieve.
111
58
  # @return [Workpattern]
112
59
  #
113
- # @since 0.2.0
114
- #
115
60
  def self.get(name)
116
- return Workpattern.get(name)
61
+ Workpattern.get(name)
117
62
  end
118
-
63
+
119
64
  # Convenience method to delete the named <tt>Workpattern</tt>
120
65
  #
121
66
  # @param [String] name The name of the Workpattern to be deleted.
122
67
  #
123
- # @since 0.2.0
124
- #
125
68
  def self.delete(name)
126
69
  Workpattern.delete(name)
127
70
  end
128
-
71
+
129
72
  # Convenience method to delete all Workpatterns.
130
- #
131
- # @since 0.2.0
132
73
  #
133
74
  def self.clear
134
75
  Workpattern.clear
135
76
  end
136
-
137
- # Convenience method to create a Clock object. This can be used for specifying times
138
- # if you don't want to create a <tt>DateTime</tt> object
139
- #
77
+
78
+ # Convenience method to create a Clock object. This can be used for
79
+ # specifying times if you don't want to create a <tt>DateTime</tt> object
80
+ #
140
81
  # @param [Integer] hour the number of hours.
141
82
  # @param [Integer] min the number of minutes
142
83
  # @return [Clock]
143
84
  # @see Clock
144
85
  #
145
- # @since 0.2.0
146
- #
147
- def self.clock(hour,min)
148
- return Clock.new(hour,min)
86
+ def self.clock(hour, min)
87
+ Clock.new(hour, min)
149
88
  end
89
+
150
90
  end
@@ -7,7 +7,7 @@ module Workpattern
7
7
  # myClock.hour #=> 3
8
8
  # myClock.min #=> 32
9
9
  # myClock.time #=> Time.new(1963,6,10,3,32)
10
- # myClock.to_s #=> 3:32 212
10
+ # myClock.to_s #=> 3:32 212
11
11
  #
12
12
  # aClock=Clock.new(27,80)
13
13
  # aClock.minutes #=> 1700
@@ -19,60 +19,61 @@ module Workpattern
19
19
  # @since 0.2.0
20
20
  #
21
21
  class Clock
22
-
23
- # Initialises an instance of <tt>Clock</tt> using the hours and minutes supplied
24
- # or 0 if they are absent. Although there are 24 hours in a day
22
+ # Initialises an instance of <tt>Clock</tt> using the hours and minutes
23
+ # supplied or 0 if they are absent. Although there are 24 hours in a day
25
24
  # (0-23) and 60 minutes in an hour (0-59), <tt>Clock</tt> calculates
26
25
  # the full hours and remaining minutes of whatever is supplied.
27
26
  #
28
27
  # @param [Integer] hour number of hours
29
28
  # @param [Integer] min number of minutes
30
29
  #
31
- def initialize(hour=0,min=0)
32
- @hour=hour
33
- @min=min
34
- total_minutes = minutes
35
- @hour=total_minutes.div(60)
36
- @min=total_minutes % 60
30
+ def initialize(hour = 0, min = 0)
31
+ @hour = total_minutes(hour, min).div(60)
32
+ @min = total_minutes(hour, min) % 60
37
33
  end
38
-
34
+
39
35
  # Returns the total number of minutes
40
36
  #
41
37
  # @return [Integer] total minutes represented by the Clock object
42
38
  #
43
39
  def minutes
44
- return (@hour*60)+@min
40
+ total_minutes(@hour, @min)
45
41
  end
46
-
42
+
47
43
  # Returns the hour of the clock (0-23)
48
44
  #
49
45
  # @return [Integer] hour of Clock from 0 to 23.
50
46
  #
51
47
  def hour
52
- return @hour % 24
48
+ @hour % 24
53
49
  end
54
-
50
+
55
51
  # Returns the minute of the clock (0-59)
56
52
  #
57
53
  # @return [Integer] minute of Clock from 0 to 59
58
54
  #
59
55
  def min
60
- return @min % 60
56
+ @min % 60
61
57
  end
62
-
58
+
63
59
  # Returns a <tt>Time</tt> object with the correct
64
60
  # <tt>hour</tt> and <tt>min</tt> values. The date
65
61
  # is 10th June 1963.
66
62
  #
67
63
  # @return [DateTime] The time using the date of 10th June 1963 (My Birthday)
68
64
  def time
69
- return DateTime.new(1963,6,10,hour,min)
65
+ DateTime.new(1963, 6, 10, hour, min)
70
66
  end
71
-
72
67
 
73
68
  # @return [String] representation of <tt>Clock</tt> value as 'hh:mn minutes'
74
- def to_s
69
+ def to_s
75
70
  hour.to_s.concat(':').concat(min.to_s).concat(' ').concat(minutes.to_s)
76
71
  end
72
+
73
+ private
74
+
75
+ def total_minutes(hours, mins)
76
+ (hours * 60) + mins
77
+ end
77
78
  end
78
79
  end
@@ -0,0 +1,72 @@
1
+ module Workpattern
2
+
3
+ # default name of a new Workpattern
4
+ DEFAULT_WORKPATTERN_NAME = 'default'.freeze
5
+
6
+ # default base year for a new workpattern
7
+ DEFAULT_BASE_YEAR = 2000
8
+
9
+ # default span of years for a new Workpattern
10
+ DEFAULT_SPAN = 100
11
+
12
+ # 60 seconds in a minute
13
+ MINUTE = 60
14
+
15
+ # 60 minutes in an hour
16
+ HOUR = MINUTE * 60
17
+
18
+ # 24 hours in a day
19
+ HOURS_IN_DAY = 24
20
+
21
+ # Seconds in a day
22
+ DAY = HOUR * HOURS_IN_DAY
23
+
24
+ # 60 minutes in a working hour as binary bit per minute
25
+ WORKING_HOUR = 2**MINUTE - 1
26
+
27
+ # 0 minutes in a working hour as binary bits per minute
28
+ RESTING_HOUR = 0
29
+
30
+ # Earliest or first time in the day
31
+ FIRST_TIME_IN_DAY = Clock.new(0, 0)
32
+
33
+ # Latest or last time in the day
34
+ LAST_TIME_IN_DAY = Clock.new(23, 59)
35
+
36
+ # Flags for calculations
37
+ PREVIOUS_DAY = -1
38
+ SAME_DAY = 0
39
+ NEXT_DAY = 1
40
+
41
+ # Specifies a working pattern
42
+ WORK_TYPE = 1
43
+
44
+ # Specifies a resting pattern
45
+ REST_TYPE = 0
46
+
47
+ # All the days of the week
48
+ SUNDAY=0
49
+ MONDAY=1
50
+ TUESDAY=2
51
+ WEDNESDAY=3
52
+ THURSDAY=4
53
+ FRIDAY=5
54
+ SATURDAY=6
55
+
56
+ # first and last day of week
57
+ FIRST_DAY_OF_WEEK = SUNDAY
58
+ LAST_DAY_OF_WEEK = SATURDAY
59
+
60
+ # Represents the days of the week to be used in applying working
61
+ # and resting patterns.
62
+ # Values exist for each day of the week as well as for the weekend
63
+ # (Saturday and Sunday),
64
+ # the week (Monday to Friday) and all days in the week.
65
+ #
66
+ daynames = { sun: [0], mon: [1], tue: [2], wed: [3],
67
+ thu: [4], fri: [5], sat: [6],
68
+ weekday: [1, 2, 3, 4, 5],
69
+ weekend: [0, 6],
70
+ all: [0, 1, 2, 3, 4, 5, 6] }
71
+ DAYNAMES = daynames.freeze
72
+ end
@@ -1,324 +1,245 @@
1
1
  module Workpattern
2
2
 
3
- # @author Barrie Callender
4
- # @!attribute values
5
- # @return [Array] each hour of the day
6
- # @!attribute hours
7
- # @return [Integer] number of hours in the day
8
- # @!attribute first_hour
9
- # @return [Integer] first working hour in the day
10
- # @!attribute first_min
11
- # @return [Integer] first working minute in first working hour in the day
12
- # @!attribute last_hour
13
- # @return [Integer] last working hour in the day
14
- # @!attribute last_min
15
- # @return [Integer] last working minute in last working hour in the day
16
- # @!attribute total
17
- # @return [Integer] total number of minutes in the day
18
- #
19
- # Represents the 24 hours of a day.
20
- #
21
- # @since 0.2.0
22
- # @todo implement a day with different number of hours in it to support daylight saving
23
- #
24
3
  class Day
25
- include Workpattern::Utility
26
- attr_accessor :values, :hours, :first_hour, :first_min, :last_hour, :last_min, :total
27
4
 
28
- # The new <tt>Day</tt> object can be created as either working or resting.
29
- #
30
- # @param [Integer] type is working (1) or resting (0)
31
- #
32
- def initialize(type=1)
33
- @hours=24
34
- hour=WORKING_HOUR if type==1
35
- hour=RESTING_HOUR if type==0
36
- @values=Array.new(@hours) {|index| hour }
37
-
38
- set_attributes
5
+ attr_accessor :pattern, :hours_per_day, :first_working_minute, :last_working_minute
6
+
7
+ def initialize(hours_per_day = HOURS_IN_DAY, type = WORK_TYPE)
8
+ @hours_per_day = hours_per_day
9
+ @pattern = initial_day(type)
10
+ set_first_and_last_minutes
39
11
  end
40
-
41
- # Creates a duplicate of the current <tt>Day</tt> instance.
42
- #
43
- # @return [Day]
44
- #
45
- def duplicate
46
- duplicate_day = Day.new()
47
- duplicate_values=Array.new(@values.size)
48
- @values.each_index {|index|
49
- duplicate_values[index]=@values[index]
50
- }
51
- duplicate_day.values=duplicate_values
52
- duplicate_day.hours = @hours
53
- duplicate_day.first_hour=@first_hour
54
- duplicate_day.first_min=@first_min
55
- duplicate_day.last_hour=@last_hour
56
- duplicate_day.last_min=@last_min
57
- duplicate_day.total = @total
58
- duplicate_day.refresh
59
- return duplicate_day
12
+
13
+ def set_resting(start_time, finish_time)
14
+ mask = resting_mask(start_time, finish_time)
15
+ @pattern = @pattern & mask
16
+ set_first_and_last_minutes
60
17
  end
61
-
62
- # Recalculates characteristics for this day
63
- #
64
- def refresh
65
- set_attributes
18
+
19
+ def set_working(from_time, to_time)
20
+ @pattern = @pattern | working_mask(from_time, to_time)
21
+ set_first_and_last_minutes
66
22
  end
23
+
24
+ def working_minutes(from_time = FIRST_TIME_IN_DAY, to_time = LAST_TIME_IN_DAY)
25
+ section = @pattern & working_mask(from_time, to_time)
26
+ section.to_s(2).count('1')
27
+ end
28
+
29
+ def working?(hour, minute)
30
+ mask = (2**((hour * 60) + minute))
31
+ result = mask & @pattern
32
+ mask == result
33
+ end
34
+
35
+ def resting?(hour, minute)
36
+ !working?(hour,minute)
37
+ end
38
+
39
+ def calc(a_date, a_duration)
40
+ if a_duration == 0
41
+ return a_date, a_duration, SAME_DAY
42
+ else
43
+ return a_duration > 0 ? add(a_date, a_duration) : subtract(a_date, a_duration)
44
+ end
45
+ end
67
46
 
68
- # Sets all minutes in a date range to be working or resting.
69
- #
70
- # @param [(#hour,#min)] start_time is the start of the range to set
71
- # @param [(#hour, #min)] finish_time is the finish of the range to be set
72
- # @param [Integer] type is either working (1) or resting (0)
73
- #
74
- def workpattern(start_time,finish_time,type)
75
-
76
- if start_time.hour==finish_time.hour
77
- @values[start_time.hour]=@values[start_time.hour].wp_workpattern(start_time.min,finish_time.min,type)
47
+ private
48
+
49
+ def add(a_date, a_duration)
50
+ minutes_left = working_minutes(a_date)
51
+ if a_duration > minutes_left
52
+ return [a_date, a_duration - minutes_left, NEXT_DAY]
53
+ elsif a_duration < minutes_left
54
+ return add_minutes(a_date, a_duration)
78
55
  else
79
- test_hour=start_time.hour
80
- @values[test_hour]=@values[test_hour].wp_workpattern(start_time.min,59,type)
81
-
82
- while ((test_hour+1)<finish_time.hour)
83
- test_hour+=1
84
- @values[test_hour]=@values[test_hour].wp_workpattern(0,59,type)
56
+ if working?(LAST_TIME_IN_DAY.hour, LAST_TIME_IN_DAY.min)
57
+ return [a_date, 0, NEXT_DAY]
58
+ else
59
+ return_date = Time.gm(a_date.year, a_date.month, a_date.day, @last_working_minute.hour, @last_working_minute.min) + 60
60
+ return [ return_date, 0, SAME_DAY]
85
61
  end
86
-
87
- @values[finish_time.hour]=@values[finish_time.hour].wp_workpattern(0,finish_time.min,type)
88
- end
89
- set_attributes
62
+ end
90
63
  end
91
-
92
- # Calculates the result of adding <tt>duration</tt> to
93
- # <tt>time</tt>. The <tt>duration</tt> can be negative in
94
- # which case it subtracts from <tt>time</tt>.
95
- #
96
- # An addition where there are less working minutes left in
97
- # the day than are being added will result in the time
98
- # returned being 00:00 on the following day.
99
- #
100
- # A subtraction where there are less working minutes left in
101
- # the day than are being added will result in the time
102
- # returned being the previous day with the <tt>midnight</tt> flag set to true.
103
- #
104
- # @param [DateTime] time when the calculation starts from
105
- # @param [Integer] duration is the number of minutes to add or subtract if it is negative
106
- # @param [Boolean] midnight is a flag used in subtraction to pretend the time is actually midnight
107
- # @return [DateTime,Integer,Boolean] Calculated time along with any remaining duration and the midnight flag
108
- #
109
- def calc(time,duration,midnight=false)
110
-
111
- if (duration<0)
112
- return subtract(time,duration, midnight)
113
- elsif (duration>0)
114
- return add(time,duration)
64
+
65
+ def add_minutes(a_date, a_duration)
66
+ elapsed_date = a_date + (a_duration * 60) - 60
67
+
68
+ if working_minutes(a_date, elapsed_date) == a_duration
69
+ return [elapsed_date += 60, 0, SAME_DAY]
115
70
  else
116
- return time,duration, false
71
+ begin
72
+ elapsed_date += 60
73
+ end while working_minutes(a_date, elapsed_date) != a_duration
74
+ return [elapsed_date += 60, 0, SAME_DAY]
117
75
  end
118
-
119
76
  end
120
-
121
- # Returns true if the given minute is working and false if it is resting
122
- #
123
- # @param [(#hour, #min)] start is the time in the day to inspect
124
- # @return [Boolean] true if the time is working and false if it is resting
125
- #
126
- def working?(start)
127
- return true if minutes(start.hour,start.min,start.hour,start.min)==1
128
- return false
77
+
78
+ def subtract(a_date, a_duration)
79
+ minutes_left = working_minutes(FIRST_TIME_IN_DAY,a_date - 60)
80
+ abs_duration = a_duration.abs
81
+ if abs_duration > minutes_left
82
+ return [a_date, a_duration + minutes_left, PREVIOUS_DAY]
83
+ elsif abs_duration < minutes_left
84
+ return subtract_minutes(a_date, abs_duration)
85
+ else
86
+ return [Time.gm(a_date.year,a_date.month,a_date.day,@first_working_minute.hour,@first_working_minute.min), 0, SAME_DAY]
87
+ end
129
88
  end
130
-
131
- # Returns the difference in working minutes between two times.
132
- #
133
- # @param [(#hour, #min)] start start time in the range
134
- # @param [(#hour, #min)] finish finish time in the range
135
- # @return [Integer] number of working minutes
136
- #
137
- def diff(start,finish)
138
- start,finish=finish,start if ((start <=> finish))==1
139
- # calculate to end of hour
140
- #
141
- if (start.jd==finish.jd) # same day
142
- duration=minutes(start.hour,start.min,finish.hour, finish.min)
143
- duration -=1 if working?(finish)
144
- start=finish
89
+
90
+ def subtract_minutes(a_date, abs_duration)
91
+ elapsed_date = a_date - (abs_duration * 60)
92
+ if working_minutes(elapsed_date, a_date - 60) == abs_duration
93
+ return [elapsed_date, 0, SAME_DAY]
145
94
  else
146
- duration=minutes(start.hour,start.min,23, 59)
147
- start=start+((23-start.hour)*HOUR) +((60-start.min)*MINUTE)
95
+ a_date -= 60
96
+ begin
97
+ elapsed_date -= 60
98
+ end while working_minutes(elapsed_date, a_date) != abs_duration
99
+ return [elapsed_date, 0, SAME_DAY]
148
100
  end
149
- return duration, start
101
+ end
102
+
103
+ def working_day
104
+ 2**((60 * @hours_per_day) +1) - 1
150
105
  end
151
106
 
152
- # Returns the total number of minutes between two times.
153
- #
154
- # @param [Integer] start_hour first hour in range
155
- # @param [Integer] start_min first minute of first hour in range
156
- # @param [Integer] finish_hour last hour in range
157
- # @param [Integer] finish_min last minute of last hour in range
158
- # @return [Integer] minutes between supplied hours and minutes
159
- #
160
- # @todo can this method and #diff method be combined?
161
- #
162
- def minutes(start_hour,start_min,finish_hour,finish_min)
163
-
164
- if (start_hour > finish_hour) || ((finish_hour==start_hour) && (start_min > finish_min))
165
- start_hour,start_min,finish_hour,finish_min=finish_hour,finish_min,start_hour,finish_min
107
+ def initial_day(type = WORK_TYPE)
108
+
109
+ pattern = 2**((60 * @hours_per_day) + 1)
110
+
111
+ if type == WORK_TYPE
112
+ pattern = pattern - 1
166
113
  end
167
114
 
168
- if (start_hour==finish_hour)
169
- retval=@values[start_hour].wp_minutes(start_min,finish_min)
170
- else
171
-
172
- retval=@values[start_hour].wp_minutes(start_min,59)
173
- while (start_hour+1<finish_hour)
174
- retval+=@values[start_hour+1].wp_total
175
- start_hour+=1
176
- end
177
- retval+=@values[finish_hour].wp_minutes(0,finish_min)
178
- end
179
-
180
- return retval
115
+ pattern
181
116
  end
182
117
 
183
- private
118
+ def working_mask(start_time, finish_time)
184
119
 
185
- # Calculates the attributes that describe the day. Called after changes.
186
- #
187
- def set_attributes
188
- @first_hour=nil
189
- @first_min=nil
190
- @last_hour=nil
191
- @last_min=nil
192
- @total=0
193
- 0.upto(@hours-1) {|index|
194
- @first_hour=index if ((@first_hour.nil?) && (@values[index].wp_total!=0))
195
- @first_min=@values[index].wp_first if ((@first_min.nil?) && (!@values[index].wp_first.nil?))
196
- @last_hour=index if (@values[index].wp_total!=0)
197
- @last_min=@values[index].wp_last if (@values[index].wp_total!=0)
198
- @total+=@values[index].wp_total
199
- }
120
+ start = minutes_in_time(start_time)
121
+ finish = minutes_in_time(finish_time)
122
+
123
+ mask = initial_day
124
+
125
+ mask = mask - ((2**start) - 1)
126
+ mask & ((2**(finish + 1)) -1)
200
127
  end
201
-
202
- # Returns the first working minute as a <tt>DateTime</tt> or <tt>oo:oo</tt>
203
- # when there is no working minutes in the day. Used by the <tt>#subtract</tt> method
204
- #
205
- # @param [DateTime] time day for which the first working time is sought.
206
- # @return [DateTime] the first working time of the day
207
- #
208
- def first_working_minute(time)
209
- if @first_hour.nil?
210
- return time - (HOUR*time.hour) - (MINUTE*time.min)
211
- else
212
- time = time - HOUR * (time.hour - @first_hour)
213
- time = time - MINUTE * (time.min - @first_min )
214
- return time
215
- end
128
+
129
+ def resting_mask(start_time, finish_time)
130
+
131
+ start = minutes_in_time(start_time)
132
+ finish_clock = Clock.new(finish_time.hour, finish_time.min + 1)
133
+
134
+ mask = initial_day(REST_TYPE)
135
+ if minutes_in_time(finish_time) != LAST_TIME_IN_DAY.minutes
136
+ mask = mask | working_mask(finish_clock,LAST_TIME_IN_DAY)
137
+ end
138
+ mask | ((2**start) - 1)
139
+ end
140
+
141
+ def minutes_in_time(a_time)
142
+ (a_time.hour * 60) + a_time.min
216
143
  end
217
-
218
- # Handles the subtraction of a duration from a time in the day.
219
- #
220
- # @param [DateTime] time when the subtraction starts from
221
- # @param [Integer] duration is the number of minutes to subtract from the <tt>time</tt>
222
- # @param [Boolean] midnight is a flag used in subtraction to pretend the time is actually midnight
223
- # @return [DateTime,Integer,Boolean] Calculated time along with any remaining duration and the midnight flag
224
- #
225
- def subtract(time,duration,midnight=false)
226
- if (time.hour==0 && time.min==0)
227
- if midnight
228
- duration+=minutes(23,59,23,59)
229
- time=time+(HOUR*23)+(MINUTE*59)
230
- return calc(time,duration)
144
+
145
+ def last_minute
146
+ if working?(LAST_TIME_IN_DAY.hour, LAST_TIME_IN_DAY.min)
147
+ return LAST_TIME_IN_DAY
148
+ end
149
+
150
+ top = minutes_in_time(LAST_TIME_IN_DAY)
151
+ bottom = minutes_in_time(FIRST_TIME_IN_DAY)
152
+ mark = top / 2
153
+
154
+ not_done = true
155
+ while not_done
156
+
157
+ minutes = working_minutes(minutes_to_time(mark), minutes_to_time(top))
158
+
159
+ if minutes > 1
160
+ bottom = mark
161
+ mark = mark + ((top - bottom) / 2)
162
+
163
+ elsif minutes == 0
164
+ top = mark
165
+ mark = mark - (( top - bottom) / 2)
166
+
167
+ elsif minutes == 1 && is_resting(mark)
168
+ bottom = mark
169
+ mark = mark + ((top - bottom) / 2)
170
+
231
171
  else
232
- return time.prev_day, duration,true
172
+ not_done = false
173
+
233
174
  end
234
- elsif (time.hour==@first_hour && time.min==@first_min)
235
- time=time-(HOUR*@first_hour) - (MINUTE*@first_min)
236
- return time.prev_day, duration, true
237
- elsif (time.min>0)
238
- available_minutes=minutes(0,0,time.hour,time.min-1)
239
- else
240
- available_minutes=minutes(0,0,time.hour-1,59)
241
- end
242
- if ((duration+available_minutes)<0) # not enough minutes in the day
243
- time = midnight_before(time.prev_day)
244
- duration = duration + available_minutes
245
- return time, duration, true
246
- elsif ((duration+available_minutes)==0)
247
- duration=0
248
- time=first_working_minute(time)
249
- else
250
- minutes_this_hour=@values[time.hour].wp_minutes(0,time.min-1)
251
- this_hour=time.hour
252
- until (duration==0)
253
- if (minutes_this_hour<duration.abs)
254
- duration+=minutes_this_hour
255
- time = time - (MINUTE*time.min) - HOUR
256
- this_hour-=1
257
- minutes_this_hour=@values[this_hour].wp_total
258
- else
259
- next_hour=(time.min==0)
260
- time,duration=@values[this_hour].wp_calc(time,duration, next_hour)
261
- end
175
+
176
+ if mark == bottom #& last_mark != mark
177
+ mark = mark + 1
178
+ end
179
+
180
+ if mark == 1 && top == 1
181
+ mark = 0
262
182
  end
263
- end
264
- return time,duration, false
183
+
184
+ end
185
+ minutes_to_time(mark)
186
+
265
187
  end
266
-
267
- # Returns the result of adding <tt>duration</tt> to the given <tt>time</tt>
268
- # When there are not enough minutes in the day it returns the date
269
- # for the start of the following day.
270
- #
271
- # @param [DateTime] time when the calculation starts from
272
- # @param [Integer] duration is the number of minutes to add
273
- # @return [DateTime,Integer] Calculated time along with any remaining duration
274
- #
275
- def add(time,duration)
276
- available_minutes=minutes(time.hour,time.min,@hours-1,59)
277
- if ((duration-available_minutes)>0) # not enough minutes left in the day
278
-
279
- result_date= time.next_day - (HOUR*time.hour) - (MINUTE*time.min)
280
- duration = duration - available_minutes
281
- else
282
- total=@values[time.hour].wp_minutes(time.min,59)
283
- if (total==duration) # this hour satisfies
284
- result_date=time - (MINUTE*time.min) + (MINUTE*@values[time.hour].wp_last) + MINUTE
285
- duration = 0
286
- else
287
- result_date = time
288
- until (duration==0)
289
- if (total<duration)
290
- duration-=total
291
- result_date=result_date + HOUR - (MINUTE*result_date.min)
292
- else
293
- result_date,duration=@values[result_date.hour].wp_calc(result_date,duration)
294
- end
295
- total=@values[result_date.hour].wp_total
296
- end
188
+
189
+ def first_minute
190
+ if working?(FIRST_TIME_IN_DAY.hour, FIRST_TIME_IN_DAY.min)
191
+ return FIRST_TIME_IN_DAY
192
+ end
193
+
194
+ top = minutes_in_time(LAST_TIME_IN_DAY)
195
+ bottom = minutes_in_time(FIRST_TIME_IN_DAY)
196
+ mark = top / 2
197
+
198
+ not_done = true
199
+ while not_done
200
+
201
+ minutes = working_minutes(minutes_to_time(bottom), minutes_to_time(mark))
202
+ if minutes > 1
203
+ top = mark
204
+ mark = mark - ((top - bottom) / 2)
205
+ elsif minutes == 0
206
+ bottom = mark
207
+ mark = mark + (( top - bottom) / 2)
208
+ elsif minutes == 1 && is_resting(mark)
209
+ top = mark
210
+ mark = mark - ((top - bottom) / 2)
211
+ else
212
+ not_done = false
213
+ end
214
+
215
+ if mark == 1 && top == 1
216
+ mark = 0
297
217
  end
298
- end
299
- return result_date,duration, false
300
- end
301
-
302
- # Returns the start of the next hour.
303
- #
304
- # The next hour could be the start of the following day.
305
- #
306
- # @param [DateTime] start is the <tt>DateTime</tt> for which the following hour is required.
307
- # @return [DateTime] the start of the next hour following the <tt>DateTime</tt> supplied
308
- #
309
- def next_hour(start)
310
- return start+HOUR-(start.min*MINUTE)
311
- end
312
-
313
- # Returns the number of working minutes left in the current hour
314
- #
315
- # @param [DateTime] start is the <tt>DateTime</tt> for which the remaining working minutes
316
- # in the hour are required
317
- # @return [Integer] number of remaining working minutes
318
- #
319
- def minutes_left_in_hour(start)
320
- return @values[start.hour].wp_diff(start.min,60)
218
+ end
219
+
220
+ minutes_to_time(mark)
221
+ end
222
+
223
+ def minutes_to_time(minutes)
224
+ Time.gm(1963,6,10,minutes / 60, minutes - (minutes / 60 * 60))
321
225
  end
322
-
226
+
227
+ def is_resting(minutes)
228
+ a_time =(minutes_to_time(minutes))
229
+ resting?(a_time.hour, a_time.min)
230
+ end
231
+
232
+ def set_first_and_last_minutes
233
+ if working_minutes == 0
234
+ @first_working_minute = nil
235
+ @last_working_minute = nil
236
+ else
237
+ @first_working_minute = first_minute
238
+ @last_working_minute = last_minute
239
+ end
240
+ end
241
+
242
+
323
243
  end
244
+
324
245
  end