workpattern 0.5.0 → 0.7.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 +9 -19
- data/.travis.yml +17 -12
- data/{CHANGELOG → CHANGELOG.md} +27 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +17 -20
- data/Rakefile +0 -0
- data/lib/workpattern/clock.rb +0 -0
- data/lib/workpattern/constants.rb +72 -0
- data/lib/workpattern/day.rb +268 -0
- data/lib/workpattern/version.rb +1 -1
- data/lib/workpattern/week.rb +135 -298
- data/lib/workpattern/week_pattern.rb +142 -0
- data/lib/workpattern/workpattern.rb +124 -132
- data/lib/workpattern.rb +25 -73
- data/script/console +0 -0
- data/script/destroy +0 -0
- data/script/generate +0 -0
- data/script/txt2html +0 -0
- data/workpattern.gemspec +41 -20
- metadata +20 -36
- 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,142 @@
|
|
|
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 = {})
|
|
44
|
+
args = all_workpattern_options(opts)
|
|
45
|
+
|
|
46
|
+
args = standardise_args(args)
|
|
47
|
+
|
|
48
|
+
upd_start = work_pattern.to_utc(args[:start])
|
|
49
|
+
upd_finish = work_pattern.to_utc(args[:finish])
|
|
50
|
+
|
|
51
|
+
while upd_start <= upd_finish
|
|
52
|
+
|
|
53
|
+
current_wp = work_pattern.find_weekpattern(upd_start)
|
|
54
|
+
|
|
55
|
+
if current_wp.start == upd_start
|
|
56
|
+
if current_wp.finish > upd_finish
|
|
57
|
+
clone_wp = fetch_updatable_week_pattern(current_wp,
|
|
58
|
+
upd_finish + DAY,
|
|
59
|
+
current_wp.finish,
|
|
60
|
+
upd_start,
|
|
61
|
+
upd_finish)
|
|
62
|
+
update_and_store_week_pattern(clone_wp, args)
|
|
63
|
+
upd_start = upd_finish + DAY
|
|
64
|
+
else # (current_wp.finish == upd_finish)
|
|
65
|
+
current_wp.workpattern(args[:days], args[:from_time],
|
|
66
|
+
args[:to_time], args[:work_type])
|
|
67
|
+
upd_start = current_wp.finish + DAY
|
|
68
|
+
end
|
|
69
|
+
else
|
|
70
|
+
clone_wp = fetch_updatable_week_pattern(current_wp, current_wp.start,
|
|
71
|
+
upd_start - DAY, upd_start)
|
|
72
|
+
if clone_wp.finish > upd_finish
|
|
73
|
+
after_wp = fetch_updatable_week_pattern(clone_wp,
|
|
74
|
+
upd_start,
|
|
75
|
+
upd_finish,
|
|
76
|
+
upd_finish + DAY)
|
|
77
|
+
weeks << after_wp
|
|
78
|
+
end
|
|
79
|
+
update_and_store_week_pattern(clone_wp, args)
|
|
80
|
+
upd_start = clone_wp.finish + DAY
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
private
|
|
86
|
+
|
|
87
|
+
def all_workpattern_options(opts)
|
|
88
|
+
|
|
89
|
+
args = { start: from, finish: to, days: :all,
|
|
90
|
+
from_time: FIRST_TIME_IN_DAY, to_time: LAST_TIME_IN_DAY,
|
|
91
|
+
work_type: WORK_TYPE }
|
|
92
|
+
|
|
93
|
+
args.merge! opts
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def standardise_args(args)
|
|
97
|
+
|
|
98
|
+
args[:start] = dmy_date(args[:start])
|
|
99
|
+
args[:finish] = dmy_date(args[:finish])
|
|
100
|
+
|
|
101
|
+
args
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Clones the supplied Week Pattern then changes the dates on it
|
|
105
|
+
# The newly cloned Week pattern dates are also changed and it is
|
|
106
|
+
# returned by this method
|
|
107
|
+
#
|
|
108
|
+
def fetch_updatable_week_pattern(keep_week, keep_start, keep_finish,
|
|
109
|
+
change_start, change_finish = nil)
|
|
110
|
+
change_week = keep_week.duplicate
|
|
111
|
+
adjust_date_range(keep_week, keep_start, keep_finish)
|
|
112
|
+
if change_finish.nil?
|
|
113
|
+
adjust_date_range(change_week, change_start, change_week.finish)
|
|
114
|
+
else
|
|
115
|
+
adjust_date_range(change_week, change_start, change_finish)
|
|
116
|
+
end
|
|
117
|
+
change_week
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def update_and_store_week_pattern(week_pattern, args)
|
|
121
|
+
week_pattern.workpattern(args[:days], args[:from_time],
|
|
122
|
+
args[:to_time], args[:work_type])
|
|
123
|
+
weeks << week_pattern
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def adjust_date_range(week_pattern, start_date, finish_date)
|
|
127
|
+
week_pattern.start = start_date
|
|
128
|
+
week_pattern.finish = finish_date
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Strips off hours, minutes, seconds etc from a supplied <tt>Date</tt> or
|
|
132
|
+
# <tt>DateTime</tt>
|
|
133
|
+
#
|
|
134
|
+
# @param [DateTime] date
|
|
135
|
+
# @return [DateTime] with zero hours, minutes, seconds and so forth.
|
|
136
|
+
#
|
|
137
|
+
def dmy_date(date)
|
|
138
|
+
Time.gm(date.year, date.month, date.day)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
end
|
|
142
|
+
end
|
|
@@ -10,14 +10,15 @@ 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
|
+
|
|
21
22
|
# @!attribute [r] name
|
|
22
23
|
# Name given to the <tt>Workpattern</tt>
|
|
23
24
|
# @!attribute [r] base
|
|
@@ -33,14 +34,23 @@ module Workpattern
|
|
|
33
34
|
#
|
|
34
35
|
attr_reader :name, :base, :span, :from, :to, :weeks
|
|
35
36
|
|
|
36
|
-
#
|
|
37
|
+
# Holds local timezone info
|
|
38
|
+
@@tz = nil
|
|
39
|
+
|
|
40
|
+
# Converts a date like object into utc
|
|
37
41
|
#
|
|
38
|
-
def
|
|
39
|
-
|
|
42
|
+
def to_utc(date)
|
|
43
|
+
date.to_time.utc
|
|
44
|
+
end
|
|
45
|
+
# Converts a date like object into local time
|
|
46
|
+
#
|
|
47
|
+
def to_local(date)
|
|
48
|
+
date.to_time.getgm
|
|
40
49
|
end
|
|
41
50
|
|
|
42
|
-
|
|
43
|
-
|
|
51
|
+
# Retrieves the local timezone
|
|
52
|
+
def timezone
|
|
53
|
+
@@tz || @@tz = TZInfo::Timezone.get(Time.now.zone)
|
|
44
54
|
end
|
|
45
55
|
|
|
46
56
|
# The new <tt>Workpattern</tt> object is created with all working minutes.
|
|
@@ -51,8 +61,8 @@ module Workpattern
|
|
|
51
61
|
# 31st December.
|
|
52
62
|
# @raise [NameError] if the given name already exists
|
|
53
63
|
#
|
|
54
|
-
def initialize(name =
|
|
55
|
-
if
|
|
64
|
+
def initialize(name = DEFAULT_WORKPATTERN_NAME, base = DEFAULT_BASE_YEAR, span = DEFAULT_SPAN)
|
|
65
|
+
if workpatterns.key?(name)
|
|
56
66
|
raise(NameError, "Workpattern '#{name}' already exists and can't be created again")
|
|
57
67
|
end
|
|
58
68
|
offset = span < 0 ? span.abs - 1 : 0
|
|
@@ -60,25 +70,35 @@ module Workpattern
|
|
|
60
70
|
@name = name
|
|
61
71
|
@base = base
|
|
62
72
|
@span = span
|
|
63
|
-
@from = Time.gm(base.abs - offset)
|
|
64
|
-
@to = Time.gm(from.year + span.abs - 1, 12, 31, 23, 59)
|
|
73
|
+
@from = Time.gm(@base.abs - offset)
|
|
74
|
+
@to = Time.gm(@from.year + @span.abs - 1, 12, 31, 23, 59)
|
|
65
75
|
@weeks = SortedSet.new
|
|
66
|
-
@weeks << Week.new(from, to
|
|
76
|
+
@weeks << Week.new(@from, @to)
|
|
67
77
|
|
|
68
|
-
|
|
78
|
+
workpatterns[@name] = self
|
|
79
|
+
@week_pattern = WeekPattern.new(self)
|
|
69
80
|
end
|
|
70
81
|
|
|
82
|
+
def week_pattern
|
|
83
|
+
@week_pattern
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
private def workpatterns
|
|
87
|
+
@@workpatterns
|
|
88
|
+
end
|
|
89
|
+
public
|
|
90
|
+
|
|
71
91
|
# Deletes all <tt>Workpattern</tt> objects
|
|
72
92
|
#
|
|
73
93
|
def self.clear
|
|
74
|
-
|
|
94
|
+
workpatterns.clear
|
|
75
95
|
end
|
|
76
96
|
|
|
77
97
|
# Returns an Array containing all the <tt>Workpattern</tt> objects
|
|
78
98
|
# @return [Array] all <tt>Workpattern</tt> objects
|
|
79
99
|
#
|
|
80
100
|
def self.to_a
|
|
81
|
-
|
|
101
|
+
workpatterns.to_a
|
|
82
102
|
end
|
|
83
103
|
|
|
84
104
|
# Returns the specific named <tt>Workpattern</tt>
|
|
@@ -87,7 +107,7 @@ module Workpattern
|
|
|
87
107
|
# exist
|
|
88
108
|
#
|
|
89
109
|
def self.get(name)
|
|
90
|
-
return
|
|
110
|
+
return workpatterns[name] if workpatterns.key?(name)
|
|
91
111
|
raise(NameError, "Workpattern '#{name}' doesn't exist so can't be retrieved")
|
|
92
112
|
end
|
|
93
113
|
|
|
@@ -97,7 +117,7 @@ module Workpattern
|
|
|
97
117
|
# if it doesn't
|
|
98
118
|
#
|
|
99
119
|
def self.delete(name)
|
|
100
|
-
|
|
120
|
+
workpatterns.delete(name).nil? ? false : true
|
|
101
121
|
end
|
|
102
122
|
|
|
103
123
|
# Applys a working or resting pattern to the <tt>Workpattern</tt> object.
|
|
@@ -116,59 +136,13 @@ module Workpattern
|
|
|
116
136
|
# days to apply the pattern. Defaults to <tt>00:00</tt>.
|
|
117
137
|
# @option opts [(#hour, #min)] :finish_time The last time in the selected
|
|
118
138
|
# days to apply the pattern. Defaults to <tt>23:59</tt>.
|
|
119
|
-
# @option opts [(
|
|
139
|
+
# @option opts [(WORK_TYPE || REST_TYPE)] :work_type Either working or resting.
|
|
120
140
|
# Defaults to working.
|
|
121
141
|
# @see #working
|
|
122
142
|
# @see #resting
|
|
123
143
|
#
|
|
124
144
|
def workpattern(opts = {})
|
|
125
|
-
|
|
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
|
|
171
|
-
end
|
|
145
|
+
week_pattern.workpattern(opts)
|
|
172
146
|
end
|
|
173
147
|
|
|
174
148
|
# Convenience method that calls <tt>#workpattern</tt> with the
|
|
@@ -177,7 +151,7 @@ module Workpattern
|
|
|
177
151
|
# @see #workpattern
|
|
178
152
|
#
|
|
179
153
|
def resting(args = {})
|
|
180
|
-
args[:work_type] =
|
|
154
|
+
args[:work_type] = REST_TYPE
|
|
181
155
|
workpattern(args)
|
|
182
156
|
end
|
|
183
157
|
|
|
@@ -187,10 +161,67 @@ module Workpattern
|
|
|
187
161
|
# @see #workpattern
|
|
188
162
|
#
|
|
189
163
|
def working(args = {})
|
|
190
|
-
args[:work_type] =
|
|
164
|
+
args[:work_type] = WORK_TYPE
|
|
191
165
|
workpattern(args)
|
|
192
166
|
end
|
|
193
167
|
|
|
168
|
+
def to_h
|
|
169
|
+
{ version: 1,
|
|
170
|
+
name: @name,
|
|
171
|
+
base: @base,
|
|
172
|
+
span: @span,
|
|
173
|
+
weeks: @weeks.map(&:to_h) }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def self.from_h(hash, overwrite: false)
|
|
177
|
+
unless hash.key?(:version)
|
|
178
|
+
raise ArgumentError, "from_h: hash is missing a :version key " \
|
|
179
|
+
"(if deserialising from JSON, use symbolize_names: true)"
|
|
180
|
+
end
|
|
181
|
+
unless hash[:version] == 1
|
|
182
|
+
raise ArgumentError, "from_h: unsupported version #{hash[:version].inspect} " \
|
|
183
|
+
"(supported: 1)"
|
|
184
|
+
end
|
|
185
|
+
unless hash[:name].is_a?(String) && !hash[:name].empty?
|
|
186
|
+
raise ArgumentError, "from_h: :name must be a non-empty String"
|
|
187
|
+
end
|
|
188
|
+
unless hash[:base].is_a?(Integer)
|
|
189
|
+
raise ArgumentError, "from_h: :base must be an Integer"
|
|
190
|
+
end
|
|
191
|
+
unless hash[:span].is_a?(Integer) && hash[:span] != 0
|
|
192
|
+
raise ArgumentError, "from_h: :span must be a non-zero Integer"
|
|
193
|
+
end
|
|
194
|
+
unless hash[:weeks].is_a?(Array)
|
|
195
|
+
raise ArgumentError, "from_h: :weeks must be an Array"
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
name = hash[:name]
|
|
199
|
+
if workpatterns.key?(name) && !overwrite
|
|
200
|
+
raise NameError, "Workpattern '#{name}' already exists and can't be created again"
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
wp = allocate
|
|
204
|
+
wp.instance_variable_set(:@name, name)
|
|
205
|
+
wp.instance_variable_set(:@base, hash[:base])
|
|
206
|
+
wp.instance_variable_set(:@span, hash[:span])
|
|
207
|
+
|
|
208
|
+
offset = hash[:span] < 0 ? hash[:span].abs - 1 : 0
|
|
209
|
+
from_time = Time.gm(hash[:base].abs - offset)
|
|
210
|
+
to_time = Time.gm(from_time.year + hash[:span].abs - 1, 12, 31, 23, 59)
|
|
211
|
+
wp.instance_variable_set(:@from, from_time)
|
|
212
|
+
wp.instance_variable_set(:@to, to_time)
|
|
213
|
+
|
|
214
|
+
weeks = SortedSet.new
|
|
215
|
+
hash[:weeks].each { |wh| weeks << Week.from_h(wh) }
|
|
216
|
+
raise ArgumentError, "from_h: :weeks must not be empty" if weeks.empty?
|
|
217
|
+
wp.instance_variable_set(:@weeks, weeks)
|
|
218
|
+
wp.instance_variable_set(:@week_pattern, WeekPattern.new(wp))
|
|
219
|
+
|
|
220
|
+
workpatterns.delete(name) if overwrite
|
|
221
|
+
workpatterns[name] = wp
|
|
222
|
+
wp
|
|
223
|
+
end
|
|
224
|
+
|
|
194
225
|
# Calculates the resulting date when the <tt>duration</tt> in minutes
|
|
195
226
|
# is added to the <tt>start</tt> date.
|
|
196
227
|
# The <tt>duration</tt> is always in whole minutes and subtracts from
|
|
@@ -203,20 +234,26 @@ module Workpattern
|
|
|
203
234
|
#
|
|
204
235
|
def calc(start, duration)
|
|
205
236
|
return start if duration == 0
|
|
206
|
-
|
|
237
|
+
a_day = SAME_DAY
|
|
207
238
|
|
|
208
239
|
utc_start = to_utc(start)
|
|
240
|
+
|
|
209
241
|
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
242
|
|
|
217
|
-
|
|
243
|
+
if a_day == PREVIOUS_DAY
|
|
244
|
+
utc_start -= DAY
|
|
245
|
+
a_day = SAME_DAY
|
|
246
|
+
utc_start = Time.gm(utc_start.year, utc_start.month, utc_start.day,LAST_TIME_IN_DAY.hour, LAST_TIME_IN_DAY.min)
|
|
247
|
+
week = find_weekpattern(utc_start)
|
|
248
|
+
|
|
249
|
+
if week.working?(utc_start)
|
|
250
|
+
duration += 1
|
|
251
|
+
end
|
|
252
|
+
else
|
|
253
|
+
week = find_weekpattern(utc_start)
|
|
254
|
+
end
|
|
255
|
+
utc_start, duration, a_day = week.calc(utc_start, duration, a_day)
|
|
218
256
|
end
|
|
219
|
-
|
|
220
257
|
to_local(utc_start)
|
|
221
258
|
end
|
|
222
259
|
|
|
@@ -239,18 +276,17 @@ module Workpattern
|
|
|
239
276
|
def diff(start, finish)
|
|
240
277
|
utc_start = to_utc(start)
|
|
241
278
|
utc_finish = to_utc(finish)
|
|
242
|
-
utc_start, utc_finish = utc_finish, utc_start if
|
|
243
|
-
|
|
279
|
+
utc_start, utc_finish = utc_finish, utc_start if utc_finish < utc_start
|
|
280
|
+
minutes = 0
|
|
281
|
+
|
|
244
282
|
while utc_start != utc_finish
|
|
245
283
|
week = find_weekpattern(utc_start)
|
|
246
|
-
|
|
247
|
-
|
|
284
|
+
r_minutes, utc_start = week.diff(utc_start, utc_finish)
|
|
285
|
+
minutes += r_minutes
|
|
248
286
|
end
|
|
249
|
-
|
|
287
|
+
minutes
|
|
250
288
|
end
|
|
251
289
|
|
|
252
|
-
private
|
|
253
|
-
|
|
254
290
|
# Retrieve the correct <tt>Week</tt> pattern for the supplied date.
|
|
255
291
|
#
|
|
256
292
|
# If the supplied <tt>date</tt> is outside the span of the
|
|
@@ -264,61 +300,17 @@ module Workpattern
|
|
|
264
300
|
def find_weekpattern(date)
|
|
265
301
|
# find the pattern that fits the date
|
|
266
302
|
#
|
|
267
|
-
if date < from
|
|
268
|
-
result = Week.new(Time.at(0), from - MINUTE,
|
|
303
|
+
if date < @from
|
|
304
|
+
result = Week.new(Time.at(0), @from - MINUTE, WORK_TYPE)
|
|
269
305
|
elsif date > to
|
|
270
|
-
result = Week.new(to + MINUTE, Time.new(9999),
|
|
306
|
+
result = Week.new(@to + MINUTE, Time.new(9999), WORK_TYPE)
|
|
271
307
|
else
|
|
272
308
|
|
|
273
309
|
date = Time.gm(date.year, date.month, date.day)
|
|
274
310
|
|
|
275
|
-
result = weeks.find { |week| week.start <= date && week.finish >= date }
|
|
311
|
+
result = @weeks.find { |week| week.start <= date && week.finish >= date }
|
|
276
312
|
end
|
|
277
313
|
result
|
|
278
314
|
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
315
|
end
|
|
324
316
|
end
|
data/lib/workpattern.rb
CHANGED
|
@@ -7,12 +7,14 @@ $LOAD_PATH.unshift(File.dirname(__FILE__)) unless
|
|
|
7
7
|
$LOAD_PATH.include?(File.dirname(__FILE__)) || $LOAD_PATH.include?(File.expand_path(File.dirname(__FILE__)))
|
|
8
8
|
|
|
9
9
|
require 'rubygems'
|
|
10
|
+
require 'sorted_set' if RUBY_VERSION >= "2.4"
|
|
10
11
|
require 'date'
|
|
11
|
-
require 'workpattern/utility/base.rb'
|
|
12
12
|
require 'workpattern/clock'
|
|
13
|
-
|
|
13
|
+
require 'workpattern/constants'
|
|
14
|
+
require 'workpattern/day'
|
|
14
15
|
require 'workpattern/week'
|
|
15
16
|
require 'workpattern/workpattern'
|
|
17
|
+
require 'workpattern/week_pattern'
|
|
16
18
|
|
|
17
19
|
#
|
|
18
20
|
# workpattern.rb - date calculation library that takes into account patterns of
|
|
@@ -24,67 +26,7 @@ require 'workpattern/workpattern'
|
|
|
24
26
|
# Documentation: Barrie Callender <barrie@callenb.org>
|
|
25
27
|
#
|
|
26
28
|
module Workpattern
|
|
27
|
-
# Represents a full working hour
|
|
28
|
-
# @since 0.2.0
|
|
29
|
-
WORKING_HOUR = 2**60 - 1
|
|
30
29
|
|
|
31
|
-
# Represents a full resting hour
|
|
32
|
-
# @since 0.2.0
|
|
33
|
-
RESTING_HOUR = 0
|
|
34
|
-
|
|
35
|
-
# The default workpattern name
|
|
36
|
-
# @since 0.2.0
|
|
37
|
-
DEFAULT_WORKPATTERN_NAME = 'default'.freeze
|
|
38
|
-
|
|
39
|
-
# The default base year
|
|
40
|
-
# @since 0.2.0
|
|
41
|
-
DEFAULT_BASE_YEAR = 2000
|
|
42
|
-
|
|
43
|
-
# The default span in years
|
|
44
|
-
# @since 0.2.0
|
|
45
|
-
DEFAULT_SPAN = 100
|
|
46
|
-
|
|
47
|
-
# Minute in terms of seconds
|
|
48
|
-
#
|
|
49
|
-
MINUTE = 60
|
|
50
|
-
|
|
51
|
-
# Hour interms od seconds
|
|
52
|
-
#
|
|
53
|
-
HOUR = MINUTE * 60
|
|
54
|
-
|
|
55
|
-
# Day in terms of seconds
|
|
56
|
-
#
|
|
57
|
-
DAY = HOUR * 24
|
|
58
|
-
|
|
59
|
-
# Earliest or first time in the day
|
|
60
|
-
# @since 0.0.1
|
|
61
|
-
FIRST_TIME_IN_DAY = Clock.new(0, 0)
|
|
62
|
-
|
|
63
|
-
# Latest or last time in the day
|
|
64
|
-
# @since 0.0.1
|
|
65
|
-
LAST_TIME_IN_DAY = Clock.new(23, 59)
|
|
66
|
-
|
|
67
|
-
# Specifies a working pattern
|
|
68
|
-
# @since 0.0.1
|
|
69
|
-
WORK = 1
|
|
70
|
-
|
|
71
|
-
# Specifies a resting pattern
|
|
72
|
-
# @since 0.0.1
|
|
73
|
-
REST = 0
|
|
74
|
-
|
|
75
|
-
# Represents the days of the week to be used in applying working
|
|
76
|
-
# and resting patterns.
|
|
77
|
-
# Values exist for each day of the week as well as for the weekend
|
|
78
|
-
# (Saturday and Sunday),
|
|
79
|
-
# the week (Monday to Friday) and all days in the week.
|
|
80
|
-
#
|
|
81
|
-
# @since 0.0.1
|
|
82
|
-
daynames = { sun: [0], mon: [1], tue: [2], wed: [3],
|
|
83
|
-
thu: [4], fri: [5], sat: [6],
|
|
84
|
-
weekday: [1, 2, 3, 4, 5],
|
|
85
|
-
weekend: [0, 6],
|
|
86
|
-
all: [0, 1, 2, 3, 4, 5, 6] }
|
|
87
|
-
DAYNAMES = daynames.freeze
|
|
88
30
|
# Covenience method to obtain a new <tt>Workpattern</tt>
|
|
89
31
|
#
|
|
90
32
|
# A negative <tt>span</tt> counts back from the <tt>base</tt> year
|
|
@@ -94,7 +36,6 @@ module Workpattern
|
|
|
94
36
|
# @param [Integer] number of years ending on 31st December.
|
|
95
37
|
# @return [Workpattern]
|
|
96
38
|
# @raise [NameError] creating a Workpattern with a name that already exists
|
|
97
|
-
# @since 0.2.0
|
|
98
39
|
#
|
|
99
40
|
def self.new(name = DEFAULT_WORKPATTERN_NAME,
|
|
100
41
|
base = DEFAULT_BASE_YEAR,
|
|
@@ -107,8 +48,6 @@ module Workpattern
|
|
|
107
48
|
#
|
|
108
49
|
# @return [Array] all <tt>Workpattern</tt> objects
|
|
109
50
|
#
|
|
110
|
-
# @since 0.2.0
|
|
111
|
-
#
|
|
112
51
|
def self.to_a
|
|
113
52
|
Workpattern.to_a
|
|
114
53
|
end
|
|
@@ -118,8 +57,6 @@ module Workpattern
|
|
|
118
57
|
# @param [String] name The name of the Workpattern to retrieve.
|
|
119
58
|
# @return [Workpattern]
|
|
120
59
|
#
|
|
121
|
-
# @since 0.2.0
|
|
122
|
-
#
|
|
123
60
|
def self.get(name)
|
|
124
61
|
Workpattern.get(name)
|
|
125
62
|
end
|
|
@@ -128,20 +65,36 @@ module Workpattern
|
|
|
128
65
|
#
|
|
129
66
|
# @param [String] name The name of the Workpattern to be deleted.
|
|
130
67
|
#
|
|
131
|
-
# @since 0.2.0
|
|
132
|
-
#
|
|
133
68
|
def self.delete(name)
|
|
134
69
|
Workpattern.delete(name)
|
|
135
70
|
end
|
|
136
71
|
|
|
137
72
|
# Convenience method to delete all Workpatterns.
|
|
138
73
|
#
|
|
139
|
-
# @since 0.2.0
|
|
140
|
-
#
|
|
141
74
|
def self.clear
|
|
142
75
|
Workpattern.clear
|
|
143
76
|
end
|
|
144
77
|
|
|
78
|
+
# Convenience method to deserialise a Workpattern from a plain hash.
|
|
79
|
+
#
|
|
80
|
+
# @param [Hash] hash produced by Workpattern#to_h
|
|
81
|
+
# @param [Boolean] overwrite replace an existing same-named workpattern
|
|
82
|
+
# @return [Workpattern]
|
|
83
|
+
# @raise [ArgumentError] if the hash is missing or has an unsupported version
|
|
84
|
+
# @raise [NameError] if a same-named workpattern already exists and overwrite is false
|
|
85
|
+
#
|
|
86
|
+
def self.from_h(hash, overwrite: false)
|
|
87
|
+
Workpattern.from_h(hash, overwrite: overwrite)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Convenience method to access the registry of all known Workpattern objects.
|
|
91
|
+
#
|
|
92
|
+
# @return [Hash]
|
|
93
|
+
#
|
|
94
|
+
def self.workpatterns
|
|
95
|
+
Workpattern.workpatterns
|
|
96
|
+
end
|
|
97
|
+
|
|
145
98
|
# Convenience method to create a Clock object. This can be used for
|
|
146
99
|
# specifying times if you don't want to create a <tt>DateTime</tt> object
|
|
147
100
|
#
|
|
@@ -150,9 +103,8 @@ module Workpattern
|
|
|
150
103
|
# @return [Clock]
|
|
151
104
|
# @see Clock
|
|
152
105
|
#
|
|
153
|
-
# @since 0.2.0
|
|
154
|
-
#
|
|
155
106
|
def self.clock(hour, min)
|
|
156
107
|
Clock.new(hour, min)
|
|
157
108
|
end
|
|
109
|
+
|
|
158
110
|
end
|
data/script/console
CHANGED
|
File without changes
|
data/script/destroy
CHANGED
|
File without changes
|
data/script/generate
CHANGED
|
File without changes
|
data/script/txt2html
CHANGED
|
File without changes
|