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.
- checksums.yaml +5 -5
- data/.gitignore +8 -20
- data/.travis.yml +18 -12
- data/{CHANGELOG → CHANGELOG.md} +19 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +31 -0
- data/LICENSE.txt +21 -0
- data/README.md +2 -27
- data/Rakefile +0 -0
- data/lib/workpattern.rb +5 -73
- data/lib/workpattern/clock.rb +0 -0
- data/lib/workpattern/constants.rb +72 -0
- data/lib/workpattern/day.rb +245 -0
- data/lib/workpattern/version.rb +1 -1
- data/lib/workpattern/week.rb +119 -296
- data/lib/workpattern/week_pattern.rb +144 -0
- data/lib/workpattern/workpattern.rb +73 -125
- data/script/console +0 -0
- data/script/destroy +0 -0
- data/script/generate +0 -0
- data/script/txt2html +0 -0
- data/workpattern.gemspec +32 -20
- metadata +21 -33
- data/lib/workpattern/utility/base.rb +0 -27
- data/test/test_clock.rb +0 -29
- data/test/test_helper.rb +0 -2
- data/test/test_week.rb +0 -532
- data/test/test_workpattern.rb +0 -307
- data/test/test_workpattern_module.rb +0 -88
@@ -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
|
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
|
90
|
+
@weeks << Week.new(@from, @to)
|
91
|
+
|
92
|
+
workpatterns[@name] = self
|
93
|
+
@week_pattern = WeekPattern.new(self)
|
94
|
+
end
|
67
95
|
|
68
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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 [(
|
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
|
-
|
126
|
-
|
127
|
-
|
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] =
|
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] =
|
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
|
-
|
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
|
-
|
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
|
243
|
-
|
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
|
-
|
247
|
-
|
240
|
+
r_minutes, utc_start = week.diff(utc_start, utc_finish)
|
241
|
+
minutes += r_minutes
|
248
242
|
end
|
249
|
-
|
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,
|
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),
|
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
|