workpattern 0.3.6 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,15 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0a052375d1d21fb15b3ec113efeafa9f385613b5
4
- data.tar.gz: e5187dbe1b5c5b497553d7d665cb785b02824fdb
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ YmYzOTdhYWIzZTYwZjZmODY3NzBmYmQ5ZWY3MTI0MDcyNWUzNThhYw==
5
+ data.tar.gz: !binary |-
6
+ MjZmZTg4OTViNzJjY2I0NGI1OTNlZjQ2NzYwZGNmZTg1ZDE4Y2JjZA==
5
7
  SHA512:
6
- metadata.gz: 296401d1f2ff0d938fd934050373f776d46cfaf199ae24c3951769d65bad804a511c23c032968179a5fb6e979137148a72dba8a471a36cfda45db5a8cde97aae
7
- data.tar.gz: 6ae2c003e83b250e1a21efe10ad7e979e2bd93df64887791d14769f12a0ce8a273e985be30fb1633915e947e921e7fecd94de0202706bd2e598938f48d616232
8
+ metadata.gz: !binary |-
9
+ ZTIzNDljMTg5NTJmMzgyMGEwZTE2ZmM3OTUzNjVhNzRjYzIzMDAxYTE3ZWY1
10
+ YTJkYTRkZWQ5YjVmZTQ3N2E4YWVhZGNiZTFmNmQ2YzI1Yjc5MjY5MzU1YWRk
11
+ NTNmMDFjODY5ZTNkZDliZjBiYzY5ZDljMzkwYWY1NzA3ZjYyNDk=
12
+ data.tar.gz: !binary |-
13
+ OWY5ZmM4Yzc0MmViNmY5YmZmMTE2YzFhMTVmMTA5Y2M5MzRmMmE0ZWM0YWVh
14
+ MzY3YWFkNjNlYTBkN2JjZjdmYjM3MTRhMDA4NTQ2YjQ1NGZkYmU3ZmQzMDE2
15
+ NjZlZDI5MzVjZjExNTY4YjVlZjMyMTA4ZjQxMDg0YmY2ZjE1NDI=
@@ -2,4 +2,7 @@ language: ruby
2
2
  rvm:
3
3
  - 1.9.3
4
4
  - jruby-19mode # JRuby in 1.9 mode
5
- - 2.0.0
5
+ - 2.0.0
6
+ - 2.1.0
7
+ - jruby-head
8
+ - ruby-head
data/CHANGELOG CHANGED
@@ -1,3 +1,8 @@
1
+ ## Workpattern v0.4.0 ( May 23, 2014) ##
2
+
3
+ * Updated Week class to use bits and removed Day and Hour class as a consequence * Barrie Callender *
4
+ * This resulted in a performance improvement and some new tests
5
+
1
6
  ## Workpattern v0.3.6 (Mar 25, 2014) ##
2
7
 
3
8
  * total minutes of week is zero when short week starting after Sunday (#17) * Barrie Callender *
@@ -10,8 +10,6 @@ require 'rubygems'
10
10
  require 'date'
11
11
  require 'workpattern/utility/base.rb'
12
12
  require 'workpattern/clock'
13
- require 'workpattern/hour'
14
- require 'workpattern/day'
15
13
  require 'workpattern/week'
16
14
  require 'workpattern/workpattern'
17
15
 
@@ -1,5 +1,3 @@
1
1
  module Workpattern
2
- # Version Number for Workpattern gem
3
- # @since 0.0.1
4
- VERSION = '0.3.6'
2
+ VERSION = '0.4.0'
5
3
  end
@@ -1,113 +1,19 @@
1
1
  module Workpattern
2
-
3
- # @author Barrie Callender
4
- # @!attribute values
5
- # @return [Array] each day of the week
6
- # @!attribute days
7
- # @return [Integer] number of days in the week
8
- # @!attribute start
9
- # @return [DateTime] first date in the range
10
- # @!attribute finish
11
- # @return [DateTime] last date in the range
12
- # @!attribute week_total
13
- # @return [Integer] total number of minutes in a week
14
- # @!attribute total
15
- # @return [Integer] total number of minutes in the range
16
- #
17
- # Represents working and resting periods for each day in a week for a specified date range.
18
- #
19
- # @since 0.2.0
20
- #
2
+
21
3
  class Week
22
4
 
23
- attr_accessor :values, :days, :start, :finish, :week_total, :total
24
-
25
- # The new <tt>Week</tt> object can be created as either working or resting.
26
- #
27
- # @param [DateTime] start first date in the range
28
- # @param [DateTime] finish last date in the range
29
- # @param [Integer] type working (1) or resting (0)
30
- # @return [Week] newly initialised Week object
31
- #
32
- def initialize(start,finish,type=1)
33
- hours_in_days_in_week=[24,24,24,24,24,24,24]
34
- @days=hours_in_days_in_week.size
35
- @values=Array.new(7) {|index| Day.new(type)}
5
+ attr_accessor :values, :hours_per_day, :start, :finish, :week_total, :total
6
+
7
+ def initialize(start,finish,type=1,hours_per_day=24)
8
+ @hours_per_day = hours_per_day
36
9
  @start=DateTime.new(start.year,start.month,start.day)
37
10
  @finish=DateTime.new(finish.year,finish.month,finish.day)
38
-
39
- set_attributes
40
- end
41
-
42
- # Duplicates the current <tt>Week</tt> object
43
- #
44
- # @return [Week] a duplicated instance of the current <tt>Week</tt> object
45
- #
46
- def duplicate()
47
- duplicate_week=Week.new(self.start,self.finish)
48
- duplicate_values=Array.new(self.values.size)
49
- self.values.each_index {|index|
50
- duplicate_values[index]=self.values[index].duplicate
51
- }
52
- duplicate_week.values=duplicate_values
53
- duplicate_week.days=self.days
54
- duplicate_week.start=self.start
55
- duplicate_week.finish=self.finish
56
- duplicate_week.week_total=self.week_total
57
- duplicate_week.total=self.total
58
- duplicate_week.refresh
59
- return duplicate_week
60
- end
61
-
62
- # Recalculates the attributes that define a <tt>Week</tt> object.
63
- # This was made public for <tt>#duplicate</tt> to work
64
- #
65
- def refresh
66
- set_attributes
67
- end
68
-
69
- # Changes the date range.
70
- # This method calls <tt>#refresh</tt> to update the attributes.
71
- #
72
- # @param [DateTime] start is the new starting date for the <tt>Week</tt>
73
- # @param [DateTime] finish is the new finish date for the <tt>Week</tt>
74
- #
75
- def adjust(start_date,finish_date)
76
- self.start=DateTime.new(start_date.year,start_date.month,start_date.day)
77
- self.finish=DateTime.new(finish_date.year,finish_date.month,finish_date.day)
78
- refresh
79
- end
80
-
81
- # Sets a range of minutes in a week to be working or resting. The parameters supplied
82
- # to this method determine exactly what should be changed
83
- #
84
- # @param [Hash(DAYNAMES)] days identifies the days to be included in the range
85
- # @param [DateTime] from_time where the time portion is used to specify the first minute to be set
86
- # @param [DateTime] to_time where the time portion is used to specify the last minute to be set
87
- # @param [Integer] type where a 1 sets it to working and a 0 to resting
88
- #
89
- def workpattern(days,from_time,to_time,type)
90
- DAYNAMES[days].each {|day| self.values[day].workpattern(from_time,to_time,type)}
91
- refresh
92
- end
93
-
94
- # Calculates a new date by adding or subtracting a duration in minutes.
95
- #
96
- # @param [DateTime] start original date
97
- # @param [Integer] duration minutes to add or subtract
98
- # @param [Boolean] midnight flag used for subtraction that indicates the start date is midnight
99
- #
100
- def calc(start_date,duration, midnight=false)
101
- return start_date,duration,false if duration==0
102
- return add(start_date,duration) if duration > 0
103
- return subtract(self.start,duration, midnight) if (self.total==0) && (duration <0)
104
- return subtract(start_date,duration, midnight) if duration <0
11
+ @values = Array.new(6)
12
+ 0.upto(6) do |i|
13
+ @values[i] = working_day * type
14
+ end
105
15
  end
106
-
107
- # Comparison Returns an integer (-1, 0, or +1) if week is less than, equal to, or greater than other_week
108
- #
109
- # @param [Week] other_week object to compare to
110
- # @return [Integer] -1,0 or +1 if week is less than, equal to or greater than other_week
16
+
111
17
  def <=>(other_week)
112
18
  if self.start < other_week.start
113
19
  return -1
@@ -117,268 +23,358 @@ module Workpattern
117
23
  return 1
118
24
  end
119
25
  end
120
-
121
- # Returns true if the supplied DateTime is working and false if resting
122
- #
123
- # @param [DateTime] start DateTime to be tested
124
- # @return [Boolean] true if the minute is working otherwise false if it is a resting minute
125
- #
126
- def working?(start_date)
127
- self.values[start_date.wday].working?(start_date)
128
- end
129
-
130
- # Returns the difference in minutes between two DateTime values.
131
- #
132
- # @param [DateTime] start starting DateTime
133
- # @param [DateTime] finish ending DateTime
134
- # @return [Integer, DateTime] number of minutes and start date for rest of calculation.
135
- #
136
- def diff(start_date,finish_date)
137
- start_date,finish_date=finish_date,start_date if ((start_date <=> finish_date))==1
138
- # calculate to end of day
139
- #
140
- if (start_date.jd==finish_date.jd) # same day
141
- duration, start_date=self.values[start_date.wday].diff(start_date,finish_date)
142
- elsif (finish_date.jd<=self.finish.jd) #within this week
143
- duration, start_date=diff_detail(start_date,finish_date,finish_date)
144
- else # after this week
145
- duration, start_date=diff_detail(start_date,finish_date,self.finish)
146
- end
147
- return duration, start_date
148
- end
149
-
26
+
150
27
  def week_total
151
- span_in_days > 6 ? self.values.inject(0) {|sum,x| sum + x.total } : day_indexes.inject(0) {|sum,x| sum + self.values[x].total}
28
+ span_in_days > 6 ? full_week_total_minutes : part_week_total_minutes
152
29
  end
153
30
 
154
31
  def total
155
32
  total_days = span_in_days
156
33
  return week_total if total_days < 8
157
- sum = total_hours(self.start.wday,6)
34
+ sum = sum_of_minutes_in_day_range(self.start.wday, 6)
158
35
  total_days -= (7-self.start.wday)
159
- sum += total_hours(0,self.finish.wday)
36
+ sum += sum_of_minutes_in_day_range(0,self.finish.wday)
160
37
  total_days-=(self.finish.wday+1)
161
38
  sum += week_total * total_days / 7
162
39
  return sum
163
40
  end
164
41
 
165
- private
42
+ def workpattern(days,from_time,to_time,type)
43
+ DAYNAMES[days].each do |day|
44
+ type==1 ? work_on_day(day,from_time,to_time) : rest_on_day(day,from_time,to_time)
45
+ end
46
+ end
166
47
 
167
- def day_indexes
168
- self.start.wday > self.finish.wday ? self.start.wday.upto(6).to_a.concat(0.upto(self.finish.wday).to_a) : self.start.wday.upto(self.finish.wday).to_a
48
+ def duplicate()
49
+ duplicate_week=Week.new(self.start,self.finish)
50
+ 0.upto(6).each do |i| duplicate_week.values[i] = @values[i] end
51
+ return duplicate_week
169
52
  end
170
53
 
171
-
54
+ def calc(start_date,duration, midnight=false)
55
+ return start_date,duration,false if duration==0
56
+ return add(start_date,duration) if duration > 0
57
+ return subtract(self.start,duration, midnight) if (self.total==0) && (duration <0)
58
+ return subtract(start_date,duration, midnight) if duration <0
59
+ end
60
+
61
+ def working?(date)
62
+ return true if bit_pos_time(date) & @values[date.wday] > 0
63
+ false
64
+ end
65
+
66
+ def resting?(date)
67
+ !working?(date)
68
+ end
69
+
70
+ def diff(start_date,finish_date)
71
+ start_date,finish_date=finish_date,start_date if ((start_date <=> finish_date))==1
72
+
73
+ if (start_date.jd==finish_date.jd)
74
+ duration, start_date=diff_in_same_day(start_date, finish_date)
75
+ elsif (finish_date.jd<=self.finish.jd)
76
+ duration, start_date=diff_in_same_weekpattern(start_date,finish_date)
77
+ else
78
+ duration, start_date=diff_beyond_weekpattern(start_date,finish_date)
79
+ end
80
+ return duration, start_date
81
+
82
+ end
83
+
84
+ private
85
+
172
86
  def span_in_days
173
87
  (self.finish-self.start).to_i + 1
174
88
  end
175
89
 
176
- # Recalculates all the attributes for a Week object
177
- #
178
- def set_attributes
179
-
90
+ def full_week_total_minutes
91
+ sum_of_minutes_in_day_range 0, 6
180
92
  end
181
93
 
182
- # Calculates the total number of minutes between two dates
183
- #
184
- # @param [DateTime] start is the first date in the range
185
- # @param [DateTime] finish is the last date in the range
186
- # @return [Integer] total number of minutes between supplied dates
187
- #
188
- def total_hours(start,finish)
189
- total=0
190
- start.upto(finish) {|day|
191
- total+=self.values[day].total
192
- }
94
+ def part_week_total_minutes
95
+
96
+ if self.start.wday <= self.finish.wday
97
+ total = sum_of_minutes_in_day_range(self.start.wday, self.finish.wday)
98
+ else
99
+ total = sum_of_minutes_in_day_range(self.start.wday, 6)
100
+ total += sum_of_minutes_in_day_range(0, self.finish.wday)
101
+ end
193
102
  return total
194
103
  end
195
-
196
- # Adds a duration in minutes to a date.
197
- #
198
- # The Boolean returned is always false.
199
- #
200
- # @param [DateTime] start original date
201
- # @param [Integer] duration minutes to add
202
- # @return [DateTime, Integer, Boolean] the calculated date, remaining minutes and flag used for subtraction
203
- #
204
- def add(start,duration)
205
- # aim to calculate to the end of the day
206
- start,duration = self.values[start.wday].calc(start,duration)
207
- return start,duration,false if (duration==0) || (start.jd > self.finish.jd)
208
- # aim to calculate to the end of the next week day that is the same as @finish
209
- while((duration!=0) && (start.wday!=self.finish.next_day.wday) && (start.jd <= self.finish.jd))
210
- if (duration>self.values[start.wday].total)
211
- duration = duration - self.values[start.wday].total
212
- start=start.next_day
213
- elsif (duration==self.values[start.wday].total)
214
- start=after_last_work(start)
215
- duration = 0
216
- else
217
- start,duration = self.values[start.wday].calc(start,duration)
218
- end
104
+
105
+ def sum_of_minutes_in_day_range(first,last)
106
+ @values[first..last].inject(0) {|sum,item| sum + item.to_s(2).count('1')}
107
+ end
108
+
109
+ def work_on_day(day,from_time,to_time)
110
+ self.values[day] = self.values[day] | time_mask(from_time, to_time)
111
+ end
112
+
113
+ def rest_on_day(day,from_time,to_time)
114
+ mask_of_1s = time_mask(from_time, to_time)
115
+ mask = mask_of_1s ^ working_day & working_day
116
+ self.values[day] = self.values[day] & mask
117
+ end
118
+
119
+ def time_mask(from_time, to_time)
120
+ bit_pos_above_time(to_time) - bit_pos_time(from_time)
121
+ end
122
+
123
+ def bit_pos_above_time(time)
124
+ bit_pos(time.hour, time.min+1)
125
+ end
126
+
127
+ def bit_pos(hour,minute)
128
+ 2**( (hour * 60) + minute )
129
+ end
130
+
131
+ def bit_pos_time(time)
132
+ bit_pos(time.hour,time.min)
133
+ end
134
+
135
+ def add(initial_date,duration)
136
+
137
+ initial_date, duration = add_to_end_of_day(initial_date,duration)
138
+
139
+ while ( duration != 0) && (initial_date.wday != self.finish.next_day.wday) && (initial_date.jd <= self.finish.jd)
140
+ initial_date, duration = add_to_end_of_day(initial_date,duration)
219
141
  end
142
+
143
+ while (duration != 0) && (duration >= self.week_total) && ((initial_date.jd + 6) <= self.finish.jd)
144
+ duration -= self.week_total
145
+ initial_date += 7
146
+ end
147
+
148
+ while (duration != 0) && (initial_date.jd <= self.finish.jd)
149
+ initial_date, duration = add_to_end_of_day(initial_date,duration)
150
+ end
151
+ return initial_date, duration, false
220
152
 
221
- return start,duration,false if (duration==0) || (start.jd > self.finish.jd)
222
-
223
- # while duration accomodates full weeks
224
- while ((duration!=0) && (duration>=self.week_total) && ((start.jd+6) <= self.finish.jd))
225
- duration=duration - self.week_total
226
- start=start+7
153
+ end
154
+
155
+ def add_to_end_of_day(initial_date, duration)
156
+ available_minutes_in_day = minutes_to_end_of_day(initial_date)
157
+
158
+ if available_minutes_in_day < duration
159
+ duration -= available_minutes_in_day
160
+ initial_date = start_of_next_day(initial_date)
161
+ elsif available_minutes_in_day == duration
162
+ duration -= available_minutes_in_day
163
+ initial_date = end_of_this_day(initial_date)
164
+ else
165
+ initial_date = consume_minutes(initial_date,duration)
166
+ duration=0
227
167
  end
168
+ return initial_date, duration
169
+ end
228
170
 
229
- return start,duration,false if (duration==0) || (start.jd > self.finish.jd)
171
+ def minutes_to_end_of_day(date)
172
+ pattern_to_end_of_day(date).to_s(2).count('1')
173
+ end
230
174
 
231
- # while duration accomodates full days
232
- while ((duration!=0) && (start.jd<= self.finish.jd))
233
- if (duration>self.values[start.wday].total)
234
- duration = duration - self.values[start.wday].total
235
- start=start.next_day
236
- else
237
- start,duration = self.values[start.wday].calc(start,duration)
238
- end
239
- end
240
- return start, duration, false
241
-
175
+ def pattern_to_end_of_day(date)
176
+ mask = mask_to_end_of_day(date)
177
+ (self.values[date.wday] & mask)
178
+ end
179
+
180
+ def mask_to_end_of_day(date)
181
+ bit_pos(self.hours_per_day,0) - bit_pos(date.hour, date.min)
182
+ end
183
+
184
+ def working_day
185
+ 2**(60*self.hours_per_day)-1
186
+ end
187
+
188
+ def start_of_next_day(date)
189
+ date.next_day - (HOUR * date.hour) - (MINUTE * date.minute)
190
+ end
191
+
192
+ def start_of_previous_day(date)
193
+ start_of_next_day(date).prev_day.prev_day
194
+ end
195
+
196
+ def start_of_today(date)
197
+ start_of_next_day(date.prev_day)
198
+ end
199
+
200
+ def end_of_this_day(date)
201
+ position = pattern_to_end_of_day(date).to_s(2).size
202
+ return adjust_date(date,position)
203
+ end
204
+
205
+ def adjust_date(date,adjustment)
206
+ date - (HOUR * date.hour) - (MINUTE * date.min) + (MINUTE * adjustment)
207
+ end
208
+
209
+ def diff_minutes_to_end_of_day(start_date)
210
+ mask = ((2**(60*self.hours_per_day + 1)) - (2**(start_date.hour*60 + start_date.min))).to_i
211
+ return (self.values[start.wday] & mask).to_s(2).count('1')
212
+ end
213
+
214
+ def mask_to_start_of_day(date)
215
+ bit_pos(date.hour, date.min) - bit_pos(0,0)
242
216
  end
243
217
 
244
- # Subtracts a duration in minutes from a date
245
- #
246
- # @param [DateTime] start original date
247
- # @param [Integer] duration minutes to subtract - always a negative
248
- # @param [Boolean] midnight flag indicates the start date is midnight when true
249
- # @return [DateTime, Integer, Boolean] the calculated date, remaining number of minutes and
250
- # true if the time is midnight on the date
251
- #
252
- def subtract(start,duration,midnight=false)
253
-
254
- # Handle subtraction from start of day
255
- if midnight
256
- start,duration=minute_b4_midnight(start,duration)
257
- midnight=false
258
- end
218
+ def pattern_to_start_of_day(date)
219
+ mask = mask_to_start_of_day(date)
220
+ (self.values[date.wday] & mask)
221
+ end
259
222
 
260
- # aim to calculate to the start of the day
261
- start,duration, midnight = self.values[start.wday].calc(start,duration)
223
+ def minutes_to_start_of_day(date)
224
+ pattern_to_start_of_day(date).to_s(2).count('1')
225
+ end
226
+
227
+ def consume_minutes(date,duration)
228
+
229
+ minutes=pattern_to_end_of_day(date).to_s(2).reverse! if duration > 0
230
+ minutes=pattern_to_start_of_day(date).to_s(2) if duration < 0
262
231
 
263
- if midnight && (start.jd >= self.start.jd)
264
- start,duration=minute_b4_midnight(start,duration)
265
- return subtract(start,duration, false)
266
- elsif midnight
267
- return start,duration,midnight
268
- elsif (duration==0) || (start.jd ==self.start.jd)
269
- return start,duration, midnight
270
- end
232
+ top=minutes.size
233
+ bottom=1
234
+ mark = top / 2
271
235
 
272
- # aim to calculate to the start of the previous week day that is the same as @start
273
- while((duration!=0) && (start.wday!=self.start.wday) && (start.jd >= self.start.jd))
236
+ while minutes[0,mark].count('1') != duration.abs
237
+ last_mark = mark
238
+ if minutes[0,mark].count('1') < duration.abs
239
+
240
+ bottom = mark
241
+ mark = (top-mark) / 2 + mark
242
+ mark = top if last_mark == mark
274
243
 
275
- if (duration.abs>=self.values[start.wday].total)
276
- duration = duration + self.values[start.wday].total
277
- start=start.prev_day
278
244
  else
279
- start,duration=minute_b4_midnight(start,duration)
280
- start,duration = self.values[start.wday].calc(start,duration)
281
- end
245
+
246
+ top = mark
247
+ mark = (mark-bottom) / 2 + bottom
248
+ mark = bottom if last_mark = mark
249
+
250
+ end
282
251
  end
283
252
 
284
- return start,duration if (duration==0) || (start.jd ==self.start.jd)
253
+ mark = minutes_addition_adjustment(minutes, mark) if duration > 0
254
+ mark = minutes_subtraction_adjustment(minutes,mark) if duration < 0
255
+
256
+ return adjust_date(date, mark) if duration > 0
257
+ return start_of_today(date) + (MINUTE * mark) if duration < 0
258
+
259
+ end
260
+
261
+ def minutes_subtraction_adjustment(minutes,mark)
262
+ i = mark - 1
263
+
264
+ while minutes[i]=='0'
265
+ i-=1
266
+ end
267
+
268
+ minutes.size - (i + 1)
269
+ end
270
+
271
+ def minutes_addition_adjustment(minutes,mark)
272
+ minutes=minutes[0,mark]
285
273
 
286
- #while duration accomodates full weeks
287
- while ((duration!=0) && (duration.abs>=self.week_total) && ((start.jd-6) >= self.start.jd))
288
- duration=duration + self.week_total
289
- start=start-7
274
+ while minutes[minutes.size-1]=='0'
275
+ minutes.chop!
290
276
  end
291
277
 
292
- return start,duration if (duration==0) || (start.jd ==self.start.jd)
278
+ minutes.size
279
+ end
280
+
281
+ def subtract_to_start_of_day(initial_date, duration, midnight)
282
+
283
+ initial_date,duration, midnight = handle_midnight(initial_date, duration) if midnight
284
+ available_minutes_in_day = minutes_to_start_of_day(initial_date)
293
285
 
294
- #while duration accomodates full days
295
- while ((duration!=0) && (start.jd>= self.start.jd))
296
- if (duration.abs>=self.values[start.wday].total)
297
- duration = duration + self.values[start.wday].total
298
- start=start.prev_day
286
+ if duration != 0
287
+ if available_minutes_in_day < duration.abs
288
+ duration += available_minutes_in_day
289
+ initial_date = start_of_previous_day(initial_date)
290
+ midnight = true
299
291
  else
300
- start,duration=minute_b4_midnight(start,duration)
301
- start,duration = self.values[start.wday].calc(start,duration)
292
+ initial_date = consume_minutes(initial_date,duration)
293
+ duration = 0
294
+ midnight = false
302
295
  end
303
- end
304
-
305
- return start, duration , midnight
306
-
307
- end
308
-
309
- # Supports calculating from midnight by updating the given duration depending on whether the
310
- # last minute in the day is resting or working. It then sets the time to this minute.
311
- #
312
- # @param [DateTime] start is the date whose midnight is to be used as the start date
313
- # @param [Integer] duration is the number of minutes to subtract
314
- # @return [DateTime, Integer] the date with a time of 23:59 and remaining duration
315
- # adjusted according to whether 23:59 is resting or not
316
- #
317
- def minute_b4_midnight(start,duration)
318
- start -= start.hour * HOUR
319
- start -= start.min * MINUTE
320
- duration += self.values[start.wday].minutes(23,59,23,59)
321
- start = start.next_day - MINUTE
322
- return start,duration
323
- end
324
-
325
- # Calculates the date and time after the last working minute of the current date
326
- #
327
- # @param [DateTime] start is the current date
328
- # @return [DateTime] the new date
329
- #
330
- def after_last_work(start_date)
331
- if self.values[start_date.wday].last_hour.nil?
332
- return start_date.next_day
333
- else
334
- start_date = start_date + HOUR * (self.values[start_date.wday].last_hour - start_date.hour)
335
- start_date = start_date + MINUTE * (self.values[start_date.wday].last_min - start_date.min + 1)
336
- return start_date
337
- end
296
+ end
297
+ return initial_date, duration, midnight
338
298
  end
339
-
340
- # Calculates the difference between two dates that exist in this Week object.
341
- #
342
- # @param [DateTime] start first date
343
- # @param [DateTime] finish last date
344
- # @param [DateTime] finish_on the range to be used in this Week object.
345
- # @return [DateTime, Integer] new date for rest of calculation and total number of minutes calculated thus far.
346
- #
347
- def diff_detail(start_date,finish_date,finish_on_date)
348
-
349
- duration, start_date=diff_in_day(start_date, finish_date)
350
- return duration,start_date if start_date > finish_on_date
299
+
300
+
301
+ def handle_midnight(initial_date,duration)
302
+ if working?(start_of_next_day(initial_date) - MINUTE)
303
+ duration += 1
304
+ end
351
305
 
352
- #rest of week to finish day
353
- while (start_date.wday<finish_date.wday) do
354
- duration+=day_total(start_date)
355
- start_date=start_date.next_day
306
+ initial_date -= (HOUR * initial_date.hour)
307
+ initial_date -= (MINUTE * initial_date.min)
308
+ initial_date = initial_date.next_day - MINUTE
309
+
310
+ return initial_date, duration, false
311
+ end
312
+
313
+
314
+ def subtract(initial_date, duration, midnight)
315
+ initial_date,duration, midnight = handle_midnight(initial_date, duration) if midnight
316
+
317
+ initial_date, duration, midnight = subtract_to_start_of_day(initial_date, duration, midnight)
318
+
319
+ while ( duration != 0) && (initial_date.wday != self.start.prev_day.wday) && (initial_date.jd >= self.start.jd)
320
+ initial_date, duration, midnight = subtract_to_start_of_day(initial_date,duration, midnight)
356
321
  end
357
322
 
358
- #weeks
359
- while (start_date.jd+7<finish_on_date.jd) do
360
- duration+=self.week_total
361
- start_date+=7
323
+ while (duration != 0) && (duration >= self.week_total) && ((initial_date.jd - 6) >= self.start.jd)
324
+ duration += self.week_total
325
+ initial_date -= 7
362
326
  end
363
327
 
364
- #days
365
- while (start_date.jd < finish_on_date.jd) do
366
- duration+=day_total(start_date)
367
- start_date=start_date.next_day
328
+ while (duration != 0) && (initial_date.jd >= self.start.jd)
329
+ initial_date, duration, midnight = subtract_to_start_of_day(initial_date,duration, midnight)
368
330
  end
369
331
 
370
- #day
371
- day_duration, start_date=diff_in_day(start_date, finish_date)
372
- duration+=day_duration
373
- return duration, start_date
332
+ return initial_date, duration, midnight
333
+
334
+ end
335
+
336
+ def diff_in_same_weekpattern(start_date, finish_date)
337
+ duration, start_date = diff_to_tomorrow(start_date)
338
+ while true
339
+ break if (start_date.wday == (self.finish.wday + 1))
340
+ break if (start_date.jd == self.finish.jd)
341
+ break if (start_date.jd == finish_date.jd)
342
+ duration += minutes_to_end_of_day(start_date)
343
+ start_date = start_of_next_day(start_date)
344
+ end
345
+
346
+ while true
347
+ break if ((start_date + 7) > finish_date)
348
+ break if ((start_date + 6).jd > self.finish.jd)
349
+ duration += week_total
350
+ start_date += 7
351
+ end
352
+
353
+ while true
354
+ break if (start_date.jd >= self.finish.jd)
355
+ break if (start_date.jd >= finish_date.jd)
356
+ duration += minutes_to_end_of_day(start_date)
357
+ start_date = start_of_next_day(start_date)
358
+ end
359
+
360
+ interim_duration, start_date = diff_in_same_day(start_date, finish_date) if (start_date < self.finish)
361
+ duration += interim_duration unless interim_duration.nil?
362
+ return duration, start_date
374
363
  end
375
364
 
376
- def diff_in_day(start_date,finish_date)
377
- return self.values[start_date.wday].diff(start_date,finish_date)
365
+ def diff_beyond_weekpattern(start_date,finish_date)
366
+ duration, start_date = diff_in_same_weekpattern(start_date, finish_date)
367
+ return duration, start_date
368
+ end
369
+
370
+ def diff_to_tomorrow(start_date)
371
+ mask = bit_pos(self.hours_per_day, 0) - bit_pos(start_date.hour, start_date.min)
372
+ return (self.values[start_date.wday] & mask).to_s(2).count('1'), start_of_next_day(start_date)
378
373
  end
379
374
 
380
- def day_total(start_date)
381
- return self.values[start_date.wday].total
375
+ def diff_in_same_day(start_date, finish_date)
376
+ mask = bit_pos(finish_date.hour, finish_date.min) - bit_pos(start_date.hour, start_date.min)
377
+ return (self.values[start_date.wday] & mask).to_s(2).count('1'), finish_date
382
378
  end
383
379
 
384
380
  end