workpattern 0.5.0 → 0.6.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.
@@ -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