workpattern 0.4.0 → 0.5.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.
@@ -1,19 +1,23 @@
1
1
  module Workpattern
2
2
  require 'set'
3
-
4
- # Represents the working and resting periods across a given number of whole years. Each <tt>Workpattern</tt>
5
- # has a unique name so it can be easily identified amongst all the other <tt>Workpattern</tt> objects.
3
+ require 'tzinfo'
4
+
5
+ # Represents the working and resting periods across a given number of whole
6
+ # years. Each <tt>Workpattern</tt>has a unique name so it can be easily
7
+ # identified amongst all the other <tt>Workpattern</tt> objects.
6
8
  #
7
- # This and the <tt>Clock</tt> class are the only two that should be referenced by calling applications when
9
+ # This and the <tt>Clock</tt> class are the only two that should be
10
+ # referenced by calling applications when
8
11
  # using this gem.
9
12
  #
10
13
  # @since 0.2.0
11
14
  #
12
15
  class Workpattern
13
-
16
+ include Base
17
+
14
18
  # Holds collection of <tt>Workpattern</tt> objects
15
- @@workpatterns = Hash.new()
16
-
19
+ @@workpatterns = {}
20
+
17
21
  # @!attribute [r] name
18
22
  # Name given to the <tt>Workpattern</tt>
19
23
  # @!attribute [r] base
@@ -28,74 +32,77 @@ module Workpattern
28
32
  # The <tt>Week</tt> objects that make up this workpattern
29
33
  #
30
34
  attr_reader :name, :base, :span, :from, :to, :weeks
31
-
35
+
32
36
  # Class for handling persistence in user's own way
33
37
  #
34
38
  def self.persistence_class=(klass)
35
- @@persistence = klass
39
+ @@persist = klass
36
40
  end
37
41
 
38
42
  def self.persistence?
39
- @@persistence ||= nil
43
+ @@persist ||= nil
40
44
  end
41
45
 
42
46
  # The new <tt>Workpattern</tt> object is created with all working minutes.
43
47
  #
44
48
  # @param [String] name Every workpattern has a unique name
45
49
  # @param [Integer] base Workpattern starts on the 1st January of this year.
46
- # @param [Integer] span Workpattern spans this number of years ending on 31st December.
50
+ # @param [Integer] span Workpattern spans this number of years ending on
51
+ # 31st December.
47
52
  # @raise [NameError] if the given name already exists
48
53
  #
49
- def initialize(name=DEFAULT_NAME,base=DEFAULT_BASE_YEAR,span=DEFAULT_SPAN)
50
-
51
- raise(NameError, "Workpattern '#{name}' already exists and can't be created again") if @@workpatterns.key?(name)
52
-
53
- span < 0 ? offset = span.abs - 1 : offset = 0
54
+ def initialize(name = DEFAULT_NAME, base = DEFAULT_BASE_YEAR, span = DEFAULT_SPAN)
55
+ if @@workpatterns.key?(name)
56
+ raise(NameError, "Workpattern '#{name}' already exists and can't be created again")
57
+ end
58
+ offset = span < 0 ? span.abs - 1 : 0
54
59
 
55
60
  @name = name
56
61
  @base = base
57
62
  @span = span
58
- @from = DateTime.new(base.abs - offset)
59
- @to = DateTime.new(from.year + span.abs - 1,12,31,23,59)
63
+ @from = Time.gm(base.abs - offset)
64
+ @to = Time.gm(from.year + span.abs - 1, 12, 31, 23, 59)
60
65
  @weeks = SortedSet.new
61
- @weeks << Week.new(from,to,1)
62
-
63
- @@workpatterns[name]=self
66
+ @weeks << Week.new(from, to, 1)
67
+
68
+ @@workpatterns[name] = self
64
69
  end
65
-
70
+
66
71
  # Deletes all <tt>Workpattern</tt> objects
67
72
  #
68
73
  def self.clear
69
74
  @@workpatterns.clear
70
75
  end
71
-
76
+
72
77
  # Returns an Array containing all the <tt>Workpattern</tt> objects
73
78
  # @return [Array] all <tt>Workpattern</tt> objects
74
- #
79
+ #
75
80
  def self.to_a
76
81
  @@workpatterns.to_a
77
82
  end
78
-
83
+
79
84
  # Returns the specific named <tt>Workpattern</tt>
80
85
  # @param [String] name of the required <tt>Workpattern</tt>
81
- # @raise [NameError] if a <tt>Workpattern</tt> of the supplied name does not exist
86
+ # @raise [NameError] if a <tt>Workpattern</tt> of the supplied name does not
87
+ # exist
82
88
  #
83
89
  def self.get(name)
84
- return @@workpatterns[name] if @@workpatterns.key?(name)
90
+ return @@workpatterns[name] if @@workpatterns.key?(name)
85
91
  raise(NameError, "Workpattern '#{name}' doesn't exist so can't be retrieved")
86
92
  end
87
-
93
+
88
94
  # Deletes the specific named <tt>Workpattern</tt>
89
95
  # @param [String] name of the required <tt>Workpattern</tt>
90
- # @return [Boolean] true if the named <tt>Workpattern</tt> existed or false if it doesn't
96
+ # @return [Boolean] true if the named <tt>Workpattern</tt> existed or false
97
+ # if it doesn't
91
98
  #
92
99
  def self.delete(name)
93
100
  @@workpatterns.delete(name).nil? ? false : true
94
101
  end
95
-
102
+
96
103
  # Applys a working or resting pattern to the <tt>Workpattern</tt> object.
97
104
  #
98
- # The #resting and #working methods are convenience methods that call
105
+ # The #resting and #working methods are convenience methods that call
99
106
  # this with the appropriate <tt>:work_type</tt> already set.
100
107
  #
101
108
  # @param [Hash] opts the options used to apply a workpattern
@@ -103,103 +110,114 @@ module Workpattern
103
110
  # to the <tt>start</tt> attribute.
104
111
  # @option opts [Date] :finish The last date to apply the pattern. Defaults
105
112
  # to the <tt>finish</tt> attribute.
106
- # @option opts [DAYNAMES] :days The specific day or days the pattern will apply to.
107
- # It defaults to <tt>:all</tt>
108
- # @option opts [(#hour, #min)] :start_time The first time in the selected days to apply the pattern.
109
- # Defaults to <tt>00:00</tt>.
110
- # @option opts [(#hour, #min)] :finish_time The last time in the selected days to apply the pattern.
111
- # Defaults to <tt>23:59</tt>.
112
- # @option opts [(WORK || REST)] :work_type Either working or resting. Defaults to working.
113
+ # @option opts [DAYNAMES] :days The specific day or days the pattern will
114
+ # apply to.It defaults to <tt>:all</tt>
115
+ # @option opts [(#hour, #min)] :start_time The first time in the selected
116
+ # days to apply the pattern. Defaults to <tt>00:00</tt>.
117
+ # @option opts [(#hour, #min)] :finish_time The last time in the selected
118
+ # days to apply the pattern. Defaults to <tt>23:59</tt>.
119
+ # @option opts [(WORK || REST)] :work_type Either working or resting.
120
+ # Defaults to working.
113
121
  # @see #working
114
122
  # @see #resting
115
123
  #
116
- def workpattern(opts={})
117
-
118
- args={:start => from, :finish => to, :days => :all,
119
- :from_time => FIRST_TIME_IN_DAY, :to_time => LAST_TIME_IN_DAY,
120
- :work_type => WORK}
121
-
124
+ 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
+
122
129
  args.merge! opts
123
-
124
- @@persistence.store( name: name, workpattern: args) if self.class.persistence?
130
+
131
+ @@persist.store(name: name, workpattern: args) if self.class.persistence?
125
132
 
126
133
  args[:start] = dmy_date(args[:start])
127
134
  args[:finish] = dmy_date(args[:finish])
128
135
  args[:from_time] = hhmn_date(args[:from_time])
129
136
  args[:to_time] = hhmn_date(args[:to_time])
130
137
 
131
- upd_start=args[:start]
132
- upd_finish=args[:finish]
133
- while (upd_start <= upd_finish)
138
+ upd_start = to_utc(args[:start])
139
+ upd_finish = to_utc(args[:finish])
140
+ while upd_start <= upd_finish
134
141
 
135
- current_wp=find_weekpattern(upd_start)
142
+ current_wp = find_weekpattern(upd_start)
136
143
 
137
- if (current_wp.start == upd_start)
138
- if (current_wp.finish > upd_finish)
139
- clone_wp=clone_and_adjust_current_wp(current_wp, upd_finish+1,current_wp.finish,upd_start,upd_finish)
140
- set_workpattern_and_store(clone_wp,args)
141
- upd_start=upd_finish+1
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
142
153
  else # (current_wp.finish == upd_finish)
143
- current_wp.workpattern(args[:days],args[:from_time],args[:to_time],args[:work_type])
144
- upd_start=current_wp.finish + 1
154
+ current_wp.workpattern(args[:days], args[:from_time],
155
+ args[:to_time], args[:work_type])
156
+ upd_start = current_wp.finish + DAY
145
157
  end
146
158
  else
147
- clone_wp=clone_and_adjust_current_wp(current_wp, current_wp.start,upd_start-1,upd_start)
148
- if (clone_wp.finish <= upd_finish)
149
- set_workpattern_and_store(clone_wp,args)
150
- upd_start=clone_wp.finish+1
151
- else
152
- after_wp=clone_and_adjust_current_wp(clone_wp, upd_start,upd_finish,upd_finish+1)
153
- weeks<< after_wp
154
- set_workpattern_and_store(clone_wp,args)
155
- upd_start=clone_wp.finish+1
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
156
167
  end
157
- end
168
+ set_workpattern_and_store(clone_wp, args)
169
+ upd_start = clone_wp.finish + DAY
170
+ end
158
171
  end
159
172
  end
160
-
161
- # Convenience method that calls <tt>#workpattern</tt> with the <tt>:work_type</tt> specified as resting.
173
+
174
+ # Convenience method that calls <tt>#workpattern</tt> with the
175
+ # <tt>:work_type</tt> specified as resting.
162
176
  #
163
177
  # @see #workpattern
164
178
  #
165
- def resting(args={})
166
- args[:work_type]=REST
179
+ def resting(args = {})
180
+ args[:work_type] = REST
167
181
  workpattern(args)
168
182
  end
169
-
170
- # Convenience method that calls <tt>#workpattern</tt> with the <tt>:work_type</tt> specified as working.
183
+
184
+ # Convenience method that calls <tt>#workpattern</tt> with the
185
+ # <tt>:work_type</tt> specified as working.
171
186
  #
172
187
  # @see #workpattern
173
188
  #
174
- def working(args={})
175
- args[:work_type]=WORK
189
+ def working(args = {})
190
+ args[:work_type] = WORK
176
191
  workpattern(args)
177
192
  end
178
-
179
- # Calculates the resulting date when the <tt>duration</tt> in minutes is added to the <tt>start</tt> date.
180
- # The <tt>duration</tt> is always in whole minutes and subtracts from <tt>start</tt> when it is a
181
- # negative number.
193
+
194
+ # Calculates the resulting date when the <tt>duration</tt> in minutes
195
+ # is added to the <tt>start</tt> date.
196
+ # The <tt>duration</tt> is always in whole minutes and subtracts from
197
+ # <tt>start</tt> when it is a negative number.
182
198
  #
183
199
  # @param [DateTime] start date to add or subtract minutes
184
200
  # @param [Integer] duration in minutes to add or subtract to date
185
- # @return [DateTime] the date when <tt>duration</tt> is added to <tt>start</tt>
201
+ # @return [DateTime] the date when <tt>duration</tt> is added to
202
+ # <tt>start</tt>
186
203
  #
187
- def calc(start,duration)
188
- return start if duration==0
189
- midnight=false
190
-
191
- while (duration !=0)
192
- week=find_weekpattern(start)
193
- if (week.start==start) && (duration<0) && (!midnight)
194
- start=start.prev_day
195
- week=find_weekpattern(start)
196
- midnight=true
197
- end
198
-
199
- start,duration,midnight=week.calc(start,duration,midnight)
200
- end
201
-
202
- return start
204
+ def calc(start, duration)
205
+ return start if duration == 0
206
+ midnight = false
207
+
208
+ utc_start = to_utc(start)
209
+ 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
+
217
+ utc_start, duration, midnight = week.calc(utc_start, duration, midnight)
218
+ end
219
+
220
+ to_local(utc_start)
203
221
  end
204
222
 
205
223
  # Returns true if the given minute is working and false if it is resting.
@@ -208,89 +226,99 @@ module Workpattern
208
226
  # @return [Boolean] true if working and false if resting
209
227
  #
210
228
  def working?(start)
211
- return find_weekpattern(start).working?(start)
212
- end
213
-
229
+ utc_start = to_utc(start)
230
+ find_weekpattern(utc_start).working?(utc_start)
231
+ end
232
+
214
233
  # Returns number of minutes between two dates
215
234
  #
216
235
  # @param [DateTime] start is the date to start from
217
236
  # @param [DateTime] finish is the date to end with
218
237
  # @return [Integer] number of minutes between the two dates
219
238
  #
220
- def diff(start,finish)
221
-
222
- start,finish=finish,start if finish<start
223
- duration=0
224
- while(start!=finish) do
225
- week=find_weekpattern(start)
226
- result_duration,start=week.diff(start,finish)
227
- duration+=result_duration
239
+ def diff(start, finish)
240
+ utc_start = to_utc(start)
241
+ utc_finish = to_utc(finish)
242
+ utc_start, utc_finish = utc_finish, utc_start if finish < start
243
+ duration = 0
244
+ while utc_start != utc_finish
245
+ week = find_weekpattern(utc_start)
246
+ result_duration, utc_start = week.diff(utc_start, utc_finish)
247
+ duration += result_duration
228
248
  end
229
- return duration
230
- end
231
-
249
+ duration
250
+ end
251
+
232
252
  private
233
-
253
+
234
254
  # Retrieve the correct <tt>Week</tt> pattern for the supplied date.
235
255
  #
236
- # If the supplied <tt>date</tt> is outside the span of the <tt>Workpattern</tt> object
237
- # then it returns an all working <tt>Week</tt> object for the calculation.
238
- #
256
+ # If the supplied <tt>date</tt> is outside the span of the
257
+ # <tt>Workpattern</tt> object then it returns an all working <tt>Week</tt>
258
+ # object for the calculation.
259
+ #
239
260
  # @param [DateTime] date whose containing <tt>Week</tt> pattern is required
240
- # @return [Week] <tt>Week</tt> object that includes the supplied <tt>date</tt> in it's range
261
+ # @return [Week] <tt>Week</tt> object that includes the supplied
262
+ # <tt>date</tt> in it's range
241
263
  #
242
264
  def find_weekpattern(date)
243
265
  # find the pattern that fits the date
244
266
  #
245
- if date<from
246
- result = Week.new(DateTime.jd(0),from-MINUTE,1)
247
- elsif date>to
248
- result = Week.new(to+MINUTE,DateTime.new(9999),1)
267
+ if date < from
268
+ result = Week.new(Time.at(0), from - MINUTE, 1)
269
+ elsif date > to
270
+ result = Week.new(to + MINUTE, Time.new(9999), 1)
249
271
  else
250
-
251
- date = DateTime.new(date.year,date.month,date.day)
252
272
 
253
- result=weeks.find {|week| week.start <= date and week.finish >= date}
273
+ date = Time.gm(date.year, date.month, date.day)
274
+
275
+ result = weeks.find { |week| week.start <= date && week.finish >= date }
254
276
  end
255
- return result
277
+ result
256
278
  end
257
-
258
- # Strips off hours, minutes, seconds and so forth from a supplied <tt>Date</tt> or
279
+
280
+ # Strips off hours, minutes, seconds etc from a supplied <tt>Date</tt> or
259
281
  # <tt>DateTime</tt>
260
282
  #
261
- # @param [DateTime] date
283
+ # @param [DateTime] date
262
284
  # @return [DateTime] with zero hours, minutes, seconds and so forth.
263
- #
285
+ #
264
286
  def dmy_date(date)
265
- return DateTime.new(date.year,date.month,date.day)
287
+ Time.gm(date.year, date.month, date.day)
266
288
  end
267
-
289
+
268
290
  # Extract the time into a <tt>Clock</tt> object
269
291
  #
270
292
  # @param [DateTime] date
271
- # @return [Clock]
293
+ # @return [Clock]
272
294
  def hhmn_date(date)
273
- return Clock.new(date.hour,date.min)
295
+ Clock.new(date.hour, date.min)
274
296
  end
275
-
297
+
276
298
  # Handles cloning of Week Pattern including date adjustments
277
- #
278
- def clone_and_adjust_current_wp(current_wp, current_start,current_finish,clone_start,clone_finish=nil)
279
- clone_wp=current_wp.duplicate
280
- adjust_date_range(current_wp,current_start,current_finish)
281
- clone_finish.nil? ? adjust_date_range(clone_wp,clone_start,clone_wp.finish) : adjust_date_range(clone_wp, clone_start,clone_finish)
282
- return clone_wp
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
283
310
  end
284
311
 
285
312
  def set_workpattern_and_store(new_wp, args)
286
- new_wp.workpattern(args[:days],args[:from_time],args[:to_time],args[:work_type])
287
- weeks<< new_wp
313
+ new_wp.workpattern(args[:days], args[:from_time],
314
+ args[:to_time], args[:work_type])
315
+ weeks << new_wp
288
316
  end
289
317
 
290
- def adjust_date_range(week_pattern,start_date,finish_date)
318
+ def adjust_date_range(week_pattern, start_date, finish_date)
291
319
  week_pattern.start = start_date
292
320
  week_pattern.finish = finish_date
293
321
  end
322
+
294
323
  end
295
324
  end
296
-