workpattern 0.3.6 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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