time_frame 0.6.1 → 0.6.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +3 -1
- data/lib/time_frame.rb +1 -1
- data/lib/time_frame/time_frame.rb +26 -18
- data/lib/time_frame/time_frame_overlaps.rb +1 -1
- data/lib/time_frame/{time_frame_handler.rb → time_frame_predicate_builder_handler.rb} +4 -7
- data/lib/time_frame/version.rb +1 -1
- data/spec/collection_spec.rb +1 -3
- data/spec/time_frame_predicate_builder_handler_spec.rb +41 -0
- data/spec/time_frame_spec.rb +7 -12
- data/time_frame.gemspec +6 -6
- metadata +17 -17
- data/spec/time_frame_handler_spec.rb +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 46d453a744497db5265eff93a9c51b5dae6dad53
|
4
|
+
data.tar.gz: 0638ebac436ed0d318e36dcc10e7d9fd9dfa4c0a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 44a1dcdaa9100b07e263ded5a781f9d62ceacb812e7ce494aed9e362e5f817e2b9ebd0b04267bad856cd4a7dbc0dd27252153254fae7e307db1cd20507ef1c9b
|
7
|
+
data.tar.gz: e5ddb3de3a236f3b2c0359c35b4fb30a0373d4d75acf07109fe8b9e6bffe704cc5880d8efa720a7be09918e978367f5e497be2ccd43c20bdb4a011a45d034b7f
|
data/.rubocop.yml
CHANGED
data/lib/time_frame.rb
CHANGED
@@ -10,7 +10,7 @@ require 'time_frame/time_frame_overlaps'
|
|
10
10
|
require 'time_frame/time_frame_uniter'
|
11
11
|
|
12
12
|
require 'time_frame/time_frame'
|
13
|
-
require 'time_frame/
|
13
|
+
require 'time_frame/time_frame_predicate_builder_handler'
|
14
14
|
|
15
15
|
require 'time_frame/tree_node'
|
16
16
|
require 'time_frame/collection'
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# Encoding: utf-8
|
2
2
|
|
3
3
|
# Temporary disable class length cop.
|
4
|
-
# rubocop:disable
|
4
|
+
# rubocop:disable Metrics/ClassLength
|
5
5
|
|
6
6
|
# The time frame class provides an specialized and enhanced range for time
|
7
7
|
# values.
|
@@ -24,7 +24,7 @@ class TimeFrame
|
|
24
24
|
|
25
25
|
def ==(other)
|
26
26
|
@min_float == other.min_float &&
|
27
|
-
|
27
|
+
@max_float == other.max_float
|
28
28
|
end
|
29
29
|
|
30
30
|
def <=>(other)
|
@@ -40,7 +40,7 @@ class TimeFrame
|
|
40
40
|
def cover?(element)
|
41
41
|
if element.is_a?(TimeFrame)
|
42
42
|
element.empty? ||
|
43
|
-
|
43
|
+
@min_float <= element.min_float && element.max_float <= max_float
|
44
44
|
else
|
45
45
|
min_float <= element.to_f && element.to_f <= max_float
|
46
46
|
end
|
@@ -69,13 +69,11 @@ class TimeFrame
|
|
69
69
|
def time_between(item)
|
70
70
|
case
|
71
71
|
when item.is_a?(TimeFrame)
|
72
|
-
|
73
|
-
[time_between(item.min), time_between(item.max)].min_by(&:abs)
|
72
|
+
time_between_time_frame(item)
|
74
73
|
when cover?(item)
|
75
74
|
0
|
76
75
|
else
|
77
|
-
|
78
|
-
[(float_value - min_float).abs, (float_value - max_float).abs].min
|
76
|
+
time_between_float(item.to_f)
|
79
77
|
end
|
80
78
|
end
|
81
79
|
|
@@ -134,7 +132,7 @@ class TimeFrame
|
|
134
132
|
|
135
133
|
def self.each_overlap(frames1, frames2)
|
136
134
|
Overlaps.new(frames1, frames2).each do |first, second|
|
137
|
-
yield
|
135
|
+
yield first, second
|
138
136
|
end
|
139
137
|
end
|
140
138
|
|
@@ -144,31 +142,41 @@ class TimeFrame
|
|
144
142
|
|
145
143
|
protected
|
146
144
|
|
147
|
-
|
148
|
-
intersection = self & other
|
145
|
+
attr_reader :min_float, :max_float
|
149
146
|
|
147
|
+
def without_frame(other)
|
150
148
|
result = []
|
151
|
-
|
152
|
-
|
149
|
+
|
150
|
+
if other.min_float > min_float
|
151
|
+
result << TimeFrame.new(min: min, max: other.min)
|
153
152
|
end
|
154
|
-
|
155
|
-
|
153
|
+
|
154
|
+
if other.max_float < max_float
|
155
|
+
result << TimeFrame.new(min: other.max, max: max)
|
156
156
|
end
|
157
|
+
|
157
158
|
result
|
158
159
|
end
|
159
160
|
|
160
|
-
attr_reader :min_float, :max_float
|
161
|
-
|
162
161
|
private
|
163
162
|
|
164
163
|
def fail_if_empty(item)
|
165
164
|
fail ArgumentError, 'time frame is empty' if item.respond_to?(:empty?) &&
|
166
|
-
|
165
|
+
item.empty?
|
167
166
|
end
|
168
167
|
|
169
168
|
def check_bounds
|
170
169
|
fail ArgumentError, 'min is greater than max.' if min > max
|
171
170
|
end
|
171
|
+
|
172
|
+
def time_between_time_frame(time_frame)
|
173
|
+
fail_if_empty time_frame
|
174
|
+
[time_between(time_frame.min), time_between(time_frame.max)].min_by(&:abs)
|
175
|
+
end
|
176
|
+
|
177
|
+
def time_between_float(float_value)
|
178
|
+
[(float_value - min_float).abs, (float_value - max_float).abs].min
|
179
|
+
end
|
172
180
|
end
|
173
181
|
|
174
|
-
# rubocop:enable
|
182
|
+
# rubocop:enable Metrics/ClassLength
|
@@ -1,16 +1,13 @@
|
|
1
1
|
class TimeFrame
|
2
2
|
# This class tells the active_record predicate builder how to handle
|
3
3
|
# time_frame classes when passed into a where-clause
|
4
|
-
class
|
4
|
+
class PredicateBuilderHandler
|
5
5
|
def call(column, time_frame)
|
6
|
-
|
7
|
-
column,
|
8
|
-
Arel::Nodes::And.new([time_frame.min, time_frame.max])
|
9
|
-
)
|
6
|
+
column.in(time_frame.min..time_frame.max)
|
10
7
|
end
|
11
8
|
end
|
12
9
|
end
|
13
10
|
|
14
11
|
ActiveRecord::PredicateBuilder.register_handler(
|
15
|
-
|
16
|
-
|
12
|
+
TimeFrame, TimeFrame::PredicateBuilderHandler.new
|
13
|
+
)
|
data/lib/time_frame/version.rb
CHANGED
data/spec/collection_spec.rb
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe TimeFrame::Collection do
|
4
|
-
|
5
4
|
let(:time_frame) { TimeFrame.new(min: Time.utc(2014), duration: 20.days) }
|
6
5
|
let(:time) { Time.utc(2014) }
|
7
6
|
|
@@ -10,7 +9,7 @@ describe TimeFrame::Collection do
|
|
10
9
|
time_frames = 20.times.map { |i| time_frame.shift_by((5 * i).days) }
|
11
10
|
objects = time_frames.map { |tf| OpenStruct.new(interval: tf) }
|
12
11
|
tree = TimeFrame::Collection.new(objects) { |o| o.interval }
|
13
|
-
expect(tree.map
|
12
|
+
expect(tree.map(&:interval)).to eq time_frames
|
14
13
|
end
|
15
14
|
end
|
16
15
|
|
@@ -202,7 +201,6 @@ describe TimeFrame::Collection do
|
|
202
201
|
|
203
202
|
result = tree.all_intersecting(interval.shift_by(300.days))
|
204
203
|
expect(result).to eq []
|
205
|
-
|
206
204
|
end
|
207
205
|
end
|
208
206
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'models/vogon_poem'
|
3
|
+
|
4
|
+
describe TimeFrame::PredicateBuilderHandler do
|
5
|
+
let(:time_frame) { TimeFrame.new(min: 5.days.ago, duration: 20.days) }
|
6
|
+
|
7
|
+
before { VogonPoem.delete_all }
|
8
|
+
|
9
|
+
it 'returns all records between min and max' do
|
10
|
+
poem_min = VogonPoem.create(written_at: time_frame.min)
|
11
|
+
poem_max = VogonPoem.create(written_at: time_frame.max)
|
12
|
+
|
13
|
+
result = VogonPoem.where(written_at: time_frame)
|
14
|
+
|
15
|
+
expect(result.count).to eq 2
|
16
|
+
expect(result.first).to eq poem_min
|
17
|
+
expect(result.last).to eq poem_max
|
18
|
+
end
|
19
|
+
|
20
|
+
it 'ignores records outside of min and max' do
|
21
|
+
poem_min = VogonPoem.create(written_at: time_frame.min + 1.minute)
|
22
|
+
VogonPoem.create(written_at: time_frame.min - 2.years)
|
23
|
+
VogonPoem.create(written_at: time_frame.max + 1.month)
|
24
|
+
|
25
|
+
result = VogonPoem.where(written_at: time_frame)
|
26
|
+
|
27
|
+
expect(result.count).to eq 1
|
28
|
+
expect(result.first).to eq poem_min
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'handles items close to the borders correctly' do
|
32
|
+
poem_min = VogonPoem.create(written_at: time_frame.min + 1.second)
|
33
|
+
VogonPoem.create(written_at: time_frame.min - 1.second)
|
34
|
+
VogonPoem.create(written_at: time_frame.max + 1.second)
|
35
|
+
|
36
|
+
result = VogonPoem.where(written_at: time_frame)
|
37
|
+
|
38
|
+
expect(result.count).to eq 1
|
39
|
+
expect(result.first).to eq poem_min
|
40
|
+
end
|
41
|
+
end
|
data/spec/time_frame_spec.rb
CHANGED
@@ -10,12 +10,12 @@ describe TimeFrame do
|
|
10
10
|
I18n.enforce_available_locales = true
|
11
11
|
end
|
12
12
|
|
13
|
-
it '
|
13
|
+
it 'is hashable' do
|
14
14
|
hash = {}
|
15
15
|
time_frame1 = TimeFrame.new(min: time, duration: duration)
|
16
16
|
time_frame2 = TimeFrame.new(min: time, duration: duration)
|
17
17
|
time_frame3 = TimeFrame.new(min: time, duration: duration / 2)
|
18
|
-
time_frame4 = TimeFrame.new(min: time - duration / 2
|
18
|
+
time_frame4 = TimeFrame.new(min: time - duration / 2, max: time + duration)
|
19
19
|
hash[time_frame1] = 1
|
20
20
|
expect(hash[time_frame2]).to eq 1
|
21
21
|
expect(hash[time_frame3]).not_to eq 1
|
@@ -92,14 +92,13 @@ describe TimeFrame do
|
|
92
92
|
|
93
93
|
context 'when min is a date' do
|
94
94
|
context 'and duration is 0' do
|
95
|
-
it '
|
95
|
+
it 'is valid' do
|
96
96
|
expect do
|
97
97
|
TimeFrame.new(min: Time.utc(2012), duration: 0.seconds)
|
98
98
|
end.not_to raise_error
|
99
99
|
end
|
100
100
|
end
|
101
101
|
end
|
102
|
-
|
103
102
|
end
|
104
103
|
context 'and time sframe covers a DST shift' do
|
105
104
|
let(:time) do
|
@@ -135,14 +134,14 @@ describe TimeFrame do
|
|
135
134
|
it { should eq 0 }
|
136
135
|
end
|
137
136
|
context 'when time frame containts a DST shift' do
|
138
|
-
it '
|
137
|
+
it 'gains 1 hour on summer -> winter shifts' do
|
139
138
|
Time.use_zone('Europe/Berlin') do
|
140
139
|
time_frame = TimeFrame.new(min: Time.zone.local(2013, 10, 27),
|
141
140
|
max: Time.zone.local(2013, 10, 28))
|
142
141
|
expect(time_frame.duration).to eq 25.hours
|
143
142
|
end
|
144
143
|
end
|
145
|
-
it '
|
144
|
+
it 'loses 1 hour on winter -> summer shifts' do
|
146
145
|
Time.use_zone('Europe/Berlin') do
|
147
146
|
time_frame = TimeFrame.new(min: Time.zone.local(2013, 3, 31),
|
148
147
|
max: Time.zone.local(2013, 4, 1))
|
@@ -192,7 +191,7 @@ describe TimeFrame do
|
|
192
191
|
describe '#<=>' do
|
193
192
|
let(:time_frames) do
|
194
193
|
array = TimeFrame.new(min: Time.utc(2014), duration: 30.days)
|
195
|
-
|
194
|
+
.split_by_interval(1.day)
|
196
195
|
time_frame1 = TimeFrame.new(min: Time.utc(2014), duration: 2.days)
|
197
196
|
array << time_frame1
|
198
197
|
array << time_frame1.shift_by(1.day)
|
@@ -408,7 +407,6 @@ describe TimeFrame do
|
|
408
407
|
end
|
409
408
|
|
410
409
|
describe '.union' do
|
411
|
-
|
412
410
|
context 'when given an empty array' do
|
413
411
|
subject { TimeFrame.union([]) }
|
414
412
|
it { should eq [] }
|
@@ -749,7 +747,7 @@ describe TimeFrame do
|
|
749
747
|
expect(subject[day]).to eq expected.shift_by(day.days)
|
750
748
|
end
|
751
749
|
end
|
752
|
-
it '
|
750
|
+
it 'has a smaller time_frame at the end' do
|
753
751
|
expected = TimeFrame.new(min: time + 7.days, duration: 12.hours)
|
754
752
|
expect(subject[7]).to eq expected
|
755
753
|
end
|
@@ -826,7 +824,6 @@ describe TimeFrame do
|
|
826
824
|
end
|
827
825
|
|
828
826
|
describe '#shift_to' do
|
829
|
-
|
830
827
|
let(:duration) { 1.day }
|
831
828
|
let(:min) { Time.zone.local(2012, 1, 2) }
|
832
829
|
let(:max) { min + duration }
|
@@ -1013,7 +1010,6 @@ describe TimeFrame do
|
|
1013
1010
|
end
|
1014
1011
|
|
1015
1012
|
describe '.covering_time_frame_for' do
|
1016
|
-
|
1017
1013
|
context 'for a single time frame' do
|
1018
1014
|
let(:time_frame) { TimeFrame.new(min: time, duration: 1.hour) }
|
1019
1015
|
subject { TimeFrame.covering_time_frame_for([time_frame]) }
|
@@ -1054,7 +1050,6 @@ describe TimeFrame do
|
|
1054
1050
|
end
|
1055
1051
|
|
1056
1052
|
describe '.each_overlap' do
|
1057
|
-
|
1058
1053
|
# Visualization of example input:
|
1059
1054
|
#
|
1060
1055
|
# array1: |---|-------| |-------|-----------|
|
data/time_frame.gemspec
CHANGED
@@ -26,11 +26,11 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
27
27
|
spec.require_paths = ['lib']
|
28
28
|
|
29
|
-
spec.add_development_dependency 'rake', '~> 10.3
|
30
|
-
spec.add_development_dependency 'rspec', '~> 3.0
|
31
|
-
spec.add_development_dependency 'simplecov', '~> 0.8
|
32
|
-
spec.add_development_dependency 'rubocop', '~> 0.23
|
29
|
+
spec.add_development_dependency 'rake', '~> 10.3'
|
30
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
31
|
+
spec.add_development_dependency 'simplecov', '~> 0.8'
|
32
|
+
spec.add_development_dependency 'rubocop', '~> 0.23'
|
33
33
|
spec.add_development_dependency 'sqlite3'
|
34
|
-
spec.add_dependency 'activerecord', '~> 4.
|
35
|
-
spec.add_dependency 'activesupport', '~> 4.
|
34
|
+
spec.add_dependency 'activerecord', '~> 4.2'
|
35
|
+
spec.add_dependency 'activesupport', '~> 4.2'
|
36
36
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_frame
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.6.
|
4
|
+
version: 0.6.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Derichs
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2014-
|
13
|
+
date: 2014-12-30 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: rake
|
@@ -18,56 +18,56 @@ dependencies:
|
|
18
18
|
requirements:
|
19
19
|
- - "~>"
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: 10.3
|
21
|
+
version: '10.3'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
requirements:
|
26
26
|
- - "~>"
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
version: 10.3
|
28
|
+
version: '10.3'
|
29
29
|
- !ruby/object:Gem::Dependency
|
30
30
|
name: rspec
|
31
31
|
requirement: !ruby/object:Gem::Requirement
|
32
32
|
requirements:
|
33
33
|
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: 3.0
|
35
|
+
version: '3.0'
|
36
36
|
type: :development
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
40
|
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: 3.0
|
42
|
+
version: '3.0'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
44
|
name: simplecov
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: 0.8
|
49
|
+
version: '0.8'
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: 0.8
|
56
|
+
version: '0.8'
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
58
|
name: rubocop
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: 0.23
|
63
|
+
version: '0.23'
|
64
64
|
type: :development
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
68
|
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: 0.23
|
70
|
+
version: '0.23'
|
71
71
|
- !ruby/object:Gem::Dependency
|
72
72
|
name: sqlite3
|
73
73
|
requirement: !ruby/object:Gem::Requirement
|
@@ -88,28 +88,28 @@ dependencies:
|
|
88
88
|
requirements:
|
89
89
|
- - "~>"
|
90
90
|
- !ruby/object:Gem::Version
|
91
|
-
version: 4.
|
91
|
+
version: '4.2'
|
92
92
|
type: :runtime
|
93
93
|
prerelease: false
|
94
94
|
version_requirements: !ruby/object:Gem::Requirement
|
95
95
|
requirements:
|
96
96
|
- - "~>"
|
97
97
|
- !ruby/object:Gem::Version
|
98
|
-
version: 4.
|
98
|
+
version: '4.2'
|
99
99
|
- !ruby/object:Gem::Dependency
|
100
100
|
name: activesupport
|
101
101
|
requirement: !ruby/object:Gem::Requirement
|
102
102
|
requirements:
|
103
103
|
- - "~>"
|
104
104
|
- !ruby/object:Gem::Version
|
105
|
-
version: 4.
|
105
|
+
version: '4.2'
|
106
106
|
type: :runtime
|
107
107
|
prerelease: false
|
108
108
|
version_requirements: !ruby/object:Gem::Requirement
|
109
109
|
requirements:
|
110
110
|
- - "~>"
|
111
111
|
- !ruby/object:Gem::Version
|
112
|
-
version: 4.
|
112
|
+
version: '4.2'
|
113
113
|
description: TimeFrame
|
114
114
|
email:
|
115
115
|
- patrick.derichs@invision.de
|
@@ -131,8 +131,8 @@ files:
|
|
131
131
|
- lib/time_frame/empty.rb
|
132
132
|
- lib/time_frame/time_frame.rb
|
133
133
|
- lib/time_frame/time_frame_covered.rb
|
134
|
-
- lib/time_frame/time_frame_handler.rb
|
135
134
|
- lib/time_frame/time_frame_overlaps.rb
|
135
|
+
- lib/time_frame/time_frame_predicate_builder_handler.rb
|
136
136
|
- lib/time_frame/time_frame_splitter.rb
|
137
137
|
- lib/time_frame/time_frame_uniter.rb
|
138
138
|
- lib/time_frame/tree_node.rb
|
@@ -140,7 +140,7 @@ files:
|
|
140
140
|
- spec/collection_spec.rb
|
141
141
|
- spec/models/vogon_poem.rb
|
142
142
|
- spec/spec_helper.rb
|
143
|
-
- spec/
|
143
|
+
- spec/time_frame_predicate_builder_handler_spec.rb
|
144
144
|
- spec/time_frame_spec.rb
|
145
145
|
- time_frame.gemspec
|
146
146
|
homepage: https://github.com/injixo/time_frame
|
@@ -171,5 +171,5 @@ test_files:
|
|
171
171
|
- spec/collection_spec.rb
|
172
172
|
- spec/models/vogon_poem.rb
|
173
173
|
- spec/spec_helper.rb
|
174
|
-
- spec/
|
174
|
+
- spec/time_frame_predicate_builder_handler_spec.rb
|
175
175
|
- spec/time_frame_spec.rb
|
@@ -1,14 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
2
|
-
require 'models/vogon_poem'
|
3
|
-
|
4
|
-
describe TimeFrame::Handler do
|
5
|
-
let(:time_frame) { TimeFrame.new(min: 5.days.ago, duration: 20.days) }
|
6
|
-
let!(:poem_min) { VogonPoem.create(written_at: time_frame.min) }
|
7
|
-
let!(:poem_max) { VogonPoem.create(written_at: time_frame.max) }
|
8
|
-
|
9
|
-
it 'should return all records between min and max' do
|
10
|
-
expect(VogonPoem.where(written_at: time_frame).count).to eq 2
|
11
|
-
expect(VogonPoem.where(written_at: time_frame).first).to eq poem_min
|
12
|
-
expect(VogonPoem.where(written_at: time_frame).last).to eq poem_max
|
13
|
-
end
|
14
|
-
end
|