workpattern 0.4.0 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
-