workpattern 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,144 @@
1
+ module Workpattern
2
+ class WeekPattern
3
+ def initialize(work_pattern)
4
+ @work_pattern = work_pattern
5
+ end
6
+
7
+ def work_pattern
8
+ @work_pattern
9
+ end
10
+
11
+ def weeks
12
+ work_pattern.weeks
13
+ end
14
+
15
+ def from
16
+ work_pattern.from
17
+ end
18
+
19
+ def to
20
+ work_pattern.to
21
+ end
22
+ # Applys a working or resting pattern to the <tt>Workpattern</tt> object.
23
+ #
24
+ # The #resting and #working methods are convenience methods that call
25
+ # this with the appropriate <tt>:work_type</tt> already set.
26
+ #
27
+ # @param [Hash] opts the options used to apply a workpattern
28
+ # @option opts [Date] :start The first date to apply the pattern. Defaults
29
+ # to the <tt>start</tt> attribute.
30
+ # @option opts [Date] :finish The last date to apply the pattern. Defaults
31
+ # to the <tt>finish</tt> attribute.
32
+ # @option opts [DAYNAMES] :days The specific day or days the pattern will
33
+ # apply to.It defaults to <tt>:all</tt>
34
+ # @option opts [(#hour, #min)] :start_time The first time in the selected
35
+ # days to apply the pattern. Defaults to <tt>00:00</tt>.
36
+ # @option opts [(#hour, #min)] :finish_time The last time in the selected
37
+ # days to apply the pattern. Defaults to <tt>23:59</tt>.
38
+ # @option opts [(WORK_TYPE || REST_TYPE)] :work_type Either working or resting.
39
+ # Defaults to working.
40
+ # @see #working
41
+ # @see #resting
42
+ #
43
+ def workpattern(opts = {}, persist = nil)
44
+ args = all_workpattern_options(opts)
45
+
46
+ persist.store(name: @name, workpattern: args) if !persist.nil?
47
+
48
+ args = standardise_args(args)
49
+
50
+ upd_start = work_pattern.to_utc(args[:start])
51
+ upd_finish = work_pattern.to_utc(args[:finish])
52
+
53
+ while upd_start <= upd_finish
54
+
55
+ current_wp = work_pattern.find_weekpattern(upd_start)
56
+
57
+ if current_wp.start == upd_start
58
+ if current_wp.finish > upd_finish
59
+ clone_wp = fetch_updatable_week_pattern(current_wp,
60
+ upd_finish + DAY,
61
+ current_wp.finish,
62
+ upd_start,
63
+ upd_finish)
64
+ update_and_store_week_pattern(clone_wp, args)
65
+ upd_start = upd_finish + DAY
66
+ else # (current_wp.finish == upd_finish)
67
+ current_wp.workpattern(args[:days], args[:from_time],
68
+ args[:to_time], args[:work_type])
69
+ upd_start = current_wp.finish + DAY
70
+ end
71
+ else
72
+ clone_wp = fetch_updatable_week_pattern(current_wp, current_wp.start,
73
+ upd_start - DAY, upd_start)
74
+ if clone_wp.finish > upd_finish
75
+ after_wp = fetch_updatable_week_pattern(clone_wp,
76
+ upd_start,
77
+ upd_finish,
78
+ upd_finish + DAY)
79
+ weeks << after_wp
80
+ end
81
+ update_and_store_week_pattern(clone_wp, args)
82
+ upd_start = clone_wp.finish + DAY
83
+ end
84
+ end
85
+ end
86
+
87
+ private
88
+
89
+ def all_workpattern_options(opts)
90
+
91
+ args = { start: from, finish: to, days: :all,
92
+ from_time: FIRST_TIME_IN_DAY, to_time: LAST_TIME_IN_DAY,
93
+ work_type: WORK_TYPE }
94
+
95
+ args.merge! opts
96
+ end
97
+
98
+ def standardise_args(args)
99
+
100
+ args[:start] = dmy_date(args[:start])
101
+ args[:finish] = dmy_date(args[:finish])
102
+
103
+ args
104
+ end
105
+
106
+ # Clones the supplied Week Pattern then changes the dates on it
107
+ # The newly cloned Week pattern dates are also changed and it is
108
+ # returned by this method
109
+ #
110
+ def fetch_updatable_week_pattern(keep_week, keep_start, keep_finish,
111
+ change_start, change_finish = nil)
112
+ change_week = keep_week.duplicate
113
+ adjust_date_range(keep_week, keep_start, keep_finish)
114
+ if change_finish.nil?
115
+ adjust_date_range(change_week, change_start, change_week.finish)
116
+ else
117
+ adjust_date_range(change_week, change_start, change_finish)
118
+ end
119
+ change_week
120
+ end
121
+
122
+ def update_and_store_week_pattern(week_pattern, args)
123
+ week_pattern.workpattern(args[:days], args[:from_time],
124
+ args[:to_time], args[:work_type])
125
+ weeks << week_pattern
126
+ end
127
+
128
+ def adjust_date_range(week_pattern, start_date, finish_date)
129
+ week_pattern.start = start_date
130
+ week_pattern.finish = finish_date
131
+ end
132
+
133
+ # Strips off hours, minutes, seconds etc from a supplied <tt>Date</tt> or
134
+ # <tt>DateTime</tt>
135
+ #
136
+ # @param [DateTime] date
137
+ # @return [DateTime] with zero hours, minutes, seconds and so forth.
138
+ #
139
+ def dmy_date(date)
140
+ Time.gm(date.year, date.month, date.day)
141
+ end
142
+
143
+ end
144
+ end
@@ -10,14 +10,19 @@ module Workpattern
10
10
  # referenced by calling applications when
11
11
  # using this gem.
12
12
  #
13
- # @since 0.2.0
14
- #
15
13
  class Workpattern
16
- include Base
17
14
 
18
15
  # Holds collection of <tt>Workpattern</tt> objects
19
16
  @@workpatterns = {}
20
17
 
18
+ def self.workpatterns
19
+ @@workpatterns
20
+ end
21
+
22
+ def workpatterns
23
+ @@workpatterns
24
+ end
25
+
21
26
  # @!attribute [r] name
22
27
  # Name given to the <tt>Workpattern</tt>
23
28
  # @!attribute [r] base
@@ -43,6 +48,25 @@ module Workpattern
43
48
  @@persist ||= nil
44
49
  end
45
50
 
51
+ # Holds local timezone info
52
+ @@tz = nil
53
+
54
+ # Converts a date like object into utc
55
+ #
56
+ def to_utc(date)
57
+ date.to_time.utc
58
+ end
59
+ # Converts a date like object into local time
60
+ #
61
+ def to_local(date)
62
+ date.to_time.getgm
63
+ end
64
+
65
+ # Retrieves the local timezone
66
+ def timezone
67
+ @@tz || @@tz = TZInfo::Timezone.get(Time.now.zone)
68
+ end
69
+
46
70
  # The new <tt>Workpattern</tt> object is created with all working minutes.
47
71
  #
48
72
  # @param [String] name Every workpattern has a unique name
@@ -52,7 +76,7 @@ module Workpattern
52
76
  # @raise [NameError] if the given name already exists
53
77
  #
54
78
  def initialize(name = DEFAULT_NAME, base = DEFAULT_BASE_YEAR, span = DEFAULT_SPAN)
55
- if @@workpatterns.key?(name)
79
+ if workpatterns.key?(name)
56
80
  raise(NameError, "Workpattern '#{name}' already exists and can't be created again")
57
81
  end
58
82
  offset = span < 0 ? span.abs - 1 : 0
@@ -60,25 +84,30 @@ module Workpattern
60
84
  @name = name
61
85
  @base = base
62
86
  @span = span
63
- @from = Time.gm(base.abs - offset)
64
- @to = Time.gm(from.year + span.abs - 1, 12, 31, 23, 59)
87
+ @from = Time.gm(@base.abs - offset)
88
+ @to = Time.gm(@from.year + @span.abs - 1, 12, 31, 23, 59)
65
89
  @weeks = SortedSet.new
66
- @weeks << Week.new(from, to, 1)
90
+ @weeks << Week.new(@from, @to)
91
+
92
+ workpatterns[@name] = self
93
+ @week_pattern = WeekPattern.new(self)
94
+ end
67
95
 
68
- @@workpatterns[name] = self
96
+ def week_pattern
97
+ @week_pattern
69
98
  end
70
99
 
71
100
  # Deletes all <tt>Workpattern</tt> objects
72
101
  #
73
102
  def self.clear
74
- @@workpatterns.clear
103
+ workpatterns.clear
75
104
  end
76
105
 
77
106
  # Returns an Array containing all the <tt>Workpattern</tt> objects
78
107
  # @return [Array] all <tt>Workpattern</tt> objects
79
108
  #
80
109
  def self.to_a
81
- @@workpatterns.to_a
110
+ workpatterns.to_a
82
111
  end
83
112
 
84
113
  # Returns the specific named <tt>Workpattern</tt>
@@ -87,7 +116,7 @@ module Workpattern
87
116
  # exist
88
117
  #
89
118
  def self.get(name)
90
- return @@workpatterns[name] if @@workpatterns.key?(name)
119
+ return workpatterns[name] if workpatterns.key?(name)
91
120
  raise(NameError, "Workpattern '#{name}' doesn't exist so can't be retrieved")
92
121
  end
93
122
 
@@ -97,7 +126,7 @@ module Workpattern
97
126
  # if it doesn't
98
127
  #
99
128
  def self.delete(name)
100
- @@workpatterns.delete(name).nil? ? false : true
129
+ workpatterns.delete(name).nil? ? false : true
101
130
  end
102
131
 
103
132
  # Applys a working or resting pattern to the <tt>Workpattern</tt> object.
@@ -116,58 +145,16 @@ module Workpattern
116
145
  # days to apply the pattern. Defaults to <tt>00:00</tt>.
117
146
  # @option opts [(#hour, #min)] :finish_time The last time in the selected
118
147
  # days to apply the pattern. Defaults to <tt>23:59</tt>.
119
- # @option opts [(WORK || REST)] :work_type Either working or resting.
148
+ # @option opts [(WORK_TYPE || REST_TYPE)] :work_type Either working or resting.
120
149
  # Defaults to working.
121
150
  # @see #working
122
151
  # @see #resting
123
152
  #
124
153
  def workpattern(opts = {})
125
- args = { start: from, finish: to, days: :all,
126
- from_time: FIRST_TIME_IN_DAY, to_time: LAST_TIME_IN_DAY,
127
- work_type: WORK }
128
-
129
- args.merge! opts
130
-
131
- @@persist.store(name: name, workpattern: args) if self.class.persistence?
132
-
133
- args[:start] = dmy_date(args[:start])
134
- args[:finish] = dmy_date(args[:finish])
135
- args[:from_time] = hhmn_date(args[:from_time])
136
- args[:to_time] = hhmn_date(args[:to_time])
137
-
138
- upd_start = to_utc(args[:start])
139
- upd_finish = to_utc(args[:finish])
140
- while upd_start <= upd_finish
141
-
142
- current_wp = find_weekpattern(upd_start)
143
-
144
- if current_wp.start == upd_start
145
- if current_wp.finish > upd_finish
146
- clone_wp = clone_and_adjust_current_wp(current_wp,
147
- upd_finish + DAY,
148
- current_wp.finish,
149
- upd_start,
150
- upd_finish)
151
- set_workpattern_and_store(clone_wp, args)
152
- upd_start = upd_finish + DAY
153
- else # (current_wp.finish == upd_finish)
154
- current_wp.workpattern(args[:days], args[:from_time],
155
- args[:to_time], args[:work_type])
156
- upd_start = current_wp.finish + DAY
157
- end
158
- else
159
- clone_wp = clone_and_adjust_current_wp(current_wp, current_wp.start,
160
- upd_start - DAY, upd_start)
161
- if clone_wp.finish > upd_finish
162
- after_wp = clone_and_adjust_current_wp(clone_wp,
163
- upd_start,
164
- upd_finish,
165
- upd_finish + DAY)
166
- weeks << after_wp
167
- end
168
- set_workpattern_and_store(clone_wp, args)
169
- upd_start = clone_wp.finish + DAY
170
- end
154
+ if self.class.persistence?
155
+ week_pattern.workpattern(opts, @@persistence)
156
+ else
157
+ week_pattern.workpattern(opts)
171
158
  end
172
159
  end
173
160
 
@@ -177,7 +164,7 @@ module Workpattern
177
164
  # @see #workpattern
178
165
  #
179
166
  def resting(args = {})
180
- args[:work_type] = REST
167
+ args[:work_type] = REST_TYPE
181
168
  workpattern(args)
182
169
  end
183
170
 
@@ -187,7 +174,7 @@ module Workpattern
187
174
  # @see #workpattern
188
175
  #
189
176
  def working(args = {})
190
- args[:work_type] = WORK
177
+ args[:work_type] = WORK_TYPE
191
178
  workpattern(args)
192
179
  end
193
180
 
@@ -203,20 +190,26 @@ module Workpattern
203
190
  #
204
191
  def calc(start, duration)
205
192
  return start if duration == 0
206
- midnight = false
193
+ a_day = SAME_DAY
207
194
 
208
195
  utc_start = to_utc(start)
196
+
209
197
  while duration != 0
210
- week = find_weekpattern(utc_start)
211
- if (week.start == utc_start) && (duration < 0) && !midnight
212
- utc_start = utc_start.prev_day
213
- week = find_weekpattern(utc_start)
214
- midnight = true
215
- end
216
198
 
217
- utc_start, duration, midnight = week.calc(utc_start, duration, midnight)
199
+ if a_day == PREVIOUS_DAY
200
+ utc_start -= DAY
201
+ a_day = SAME_DAY
202
+ utc_start = Time.gm(utc_start.year, utc_start.month, utc_start.day,LAST_TIME_IN_DAY.hour, LAST_TIME_IN_DAY.min)
203
+ week = find_weekpattern(utc_start)
204
+
205
+ if week.working?(utc_start)
206
+ duration += 1
207
+ end
208
+ else
209
+ week = find_weekpattern(utc_start)
210
+ end
211
+ utc_start, duration, a_day = week.calc(utc_start, duration, a_day)
218
212
  end
219
-
220
213
  to_local(utc_start)
221
214
  end
222
215
 
@@ -239,18 +232,17 @@ module Workpattern
239
232
  def diff(start, finish)
240
233
  utc_start = to_utc(start)
241
234
  utc_finish = to_utc(finish)
242
- utc_start, utc_finish = utc_finish, utc_start if finish < start
243
- duration = 0
235
+ utc_start, utc_finish = utc_finish, utc_start if utc_finish < utc_start
236
+ minutes = 0
237
+
244
238
  while utc_start != utc_finish
245
239
  week = find_weekpattern(utc_start)
246
- result_duration, utc_start = week.diff(utc_start, utc_finish)
247
- duration += result_duration
240
+ r_minutes, utc_start = week.diff(utc_start, utc_finish)
241
+ minutes += r_minutes
248
242
  end
249
- duration
243
+ minutes
250
244
  end
251
245
 
252
- private
253
-
254
246
  # Retrieve the correct <tt>Week</tt> pattern for the supplied date.
255
247
  #
256
248
  # If the supplied <tt>date</tt> is outside the span of the
@@ -264,61 +256,17 @@ module Workpattern
264
256
  def find_weekpattern(date)
265
257
  # find the pattern that fits the date
266
258
  #
267
- if date < from
268
- result = Week.new(Time.at(0), from - MINUTE, 1)
259
+ if date < @from
260
+ result = Week.new(Time.at(0), @from - MINUTE, WORK_TYPE)
269
261
  elsif date > to
270
- result = Week.new(to + MINUTE, Time.new(9999), 1)
262
+ result = Week.new(@to + MINUTE, Time.new(9999), WORK_TYPE)
271
263
  else
272
264
 
273
265
  date = Time.gm(date.year, date.month, date.day)
274
266
 
275
- result = weeks.find { |week| week.start <= date && week.finish >= date }
267
+ result = @weeks.find { |week| week.start <= date && week.finish >= date }
276
268
  end
277
269
  result
278
270
  end
279
-
280
- # Strips off hours, minutes, seconds etc from a supplied <tt>Date</tt> or
281
- # <tt>DateTime</tt>
282
- #
283
- # @param [DateTime] date
284
- # @return [DateTime] with zero hours, minutes, seconds and so forth.
285
- #
286
- def dmy_date(date)
287
- Time.gm(date.year, date.month, date.day)
288
- end
289
-
290
- # Extract the time into a <tt>Clock</tt> object
291
- #
292
- # @param [DateTime] date
293
- # @return [Clock]
294
- def hhmn_date(date)
295
- Clock.new(date.hour, date.min)
296
- end
297
-
298
- # Handles cloning of Week Pattern including date adjustments
299
- #
300
- def clone_and_adjust_current_wp(current_wp, current_start, current_finish,
301
- clone_start, clone_finish = nil)
302
- clone_wp = current_wp.duplicate
303
- adjust_date_range(current_wp, current_start, current_finish)
304
- if clone_finish.nil?
305
- adjust_date_range(clone_wp, clone_start, clone_wp.finish)
306
- else
307
- adjust_date_range(clone_wp, clone_start, clone_finish)
308
- end
309
- clone_wp
310
- end
311
-
312
- def set_workpattern_and_store(new_wp, args)
313
- new_wp.workpattern(args[:days], args[:from_time],
314
- args[:to_time], args[:work_type])
315
- weeks << new_wp
316
- end
317
-
318
- def adjust_date_range(week_pattern, start_date, finish_date)
319
- week_pattern.start = start_date
320
- week_pattern.finish = finish_date
321
- end
322
-
323
271
  end
324
272
  end