time_span 0.0.1.alpha03 → 0.0.1.alpha04
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/time_span.rb +148 -4
- data/lib/time_span/version.rb +1 -1
- data/spec/time_span/time_span_spec.rb +250 -79
- metadata +4 -4
data/lib/time_span.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
#require "time_span/version"
|
2
|
-
require 'date'
|
3
2
|
|
4
3
|
module TimeSpan
|
5
|
-
|
4
|
+
|
5
|
+
class TimeSpan
|
6
6
|
|
7
7
|
attr_accessor :starts, :ends # RelativeTime objects
|
8
8
|
|
@@ -11,6 +11,7 @@ module TimeSpan
|
|
11
11
|
##################################
|
12
12
|
|
13
13
|
def initialize(starting_at, ending_at)
|
14
|
+
raise "Cannot make a span unless both points are on the same timeline" unless starting_at.colinear_with?(ending_at)
|
14
15
|
self.starts = starting_at
|
15
16
|
self.ends = ending_at
|
16
17
|
starting_at.kind_of?(RelativeTime) && ending_at.kind_of?(RelativeTime)
|
@@ -85,6 +86,10 @@ module TimeSpan
|
|
85
86
|
ends_with?(b) && starts_with?(b)
|
86
87
|
end
|
87
88
|
|
89
|
+
def != (b)
|
90
|
+
!end_with(b) || !starts_with(b)
|
91
|
+
end
|
92
|
+
|
88
93
|
def < (b)
|
89
94
|
ends_before_other_starts?(b)
|
90
95
|
end
|
@@ -110,8 +115,147 @@ module TimeSpan
|
|
110
115
|
end
|
111
116
|
|
112
117
|
end
|
118
|
+
#
|
119
|
+
class TimeLine < Array
|
120
|
+
|
121
|
+
attr_accessor :line, :indices_of, :name
|
122
|
+
|
123
|
+
def to_s
|
124
|
+
name.to_s
|
125
|
+
end
|
126
|
+
|
127
|
+
def initialize(name="")
|
128
|
+
@name = name
|
129
|
+
@line = []
|
130
|
+
@indices_of = {}
|
131
|
+
end
|
132
|
+
|
133
|
+
def position_of(obj)
|
134
|
+
@indices_of[obj]
|
135
|
+
end
|
136
|
+
|
137
|
+
def append(obj)
|
138
|
+
insert_at(@line.size, obj)
|
139
|
+
end
|
140
|
+
|
141
|
+
def increase_after(pos, by=1)
|
142
|
+
@indices_of.each_key do |key|
|
143
|
+
@indices_of[key] += by if (@indices_of[key] >= pos)
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def append_to_next(relative_obj, obj, relative=1)
|
148
|
+
insert_at(position_of(relative_obj)+relative, obj)
|
149
|
+
end
|
150
|
+
|
151
|
+
def insert_before_next(relative_obj, obj, relative_offset=1)
|
152
|
+
relative_position = position_of(relative_obj)
|
153
|
+
increase_after(relative_position + relative_offset, relative_offset)
|
154
|
+
@line[relative_position + relative_offset,0] = nil
|
155
|
+
insert_at(relative_position+relative_offset, obj)
|
156
|
+
end
|
157
|
+
|
158
|
+
## needs work for RelativeTime objects
|
159
|
+
def insert_at(pos, obj)
|
160
|
+
|
161
|
+
raise "can only add a time to its own timeline" unless obj.timeline.equal? self
|
162
|
+
if @line[pos].nil?
|
163
|
+
@line[pos] = [obj]
|
164
|
+
@indices_of[obj] = pos
|
165
|
+
else
|
166
|
+
op = @line[pos].kind_of?(Array) ? '<<' : '='
|
167
|
+
@line[pos].send(op.to_sym, obj) unless @line[pos].include?(obj) # no dupliates
|
168
|
+
@indices_of[obj] = pos # dup overwrites
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def remove(obj)
|
173
|
+
# cannot remove [] or the @indices_of will be wrong
|
174
|
+
# call to #compress to remove the extra []s
|
175
|
+
pos = position_of(obj)
|
176
|
+
|
177
|
+
if pos # do nothing if it isn't there'
|
178
|
+
@line[pos].delete(obj)
|
179
|
+
@indices_of.delete(obj)
|
180
|
+
end
|
181
|
+
|
182
|
+
end
|
183
|
+
|
184
|
+
## removes all [] elements, and decrements accordingly the @indices_of
|
185
|
+
## ideally this should be transactional
|
186
|
+
|
187
|
+
def compress!
|
188
|
+
|
189
|
+
mod_level = 0
|
190
|
+
offsets = []
|
191
|
+
0.upto(line.size-1) do |i|
|
192
|
+
mod_level -= 1 if @line[i].empty?
|
193
|
+
offsets << mod_level
|
194
|
+
end
|
195
|
+
|
196
|
+
## poor man's transaction. Don't do directly on indices_of so less chance of interruption
|
197
|
+
indices = indices_of
|
198
|
+
|
199
|
+
## refactor to use increase_after
|
200
|
+
indices.each_key do |key|
|
201
|
+
indices[key] = indices[key] + offsets[indices[key]]
|
202
|
+
end
|
203
|
+
|
204
|
+
indices_of = indices
|
205
|
+
@line.delete([])
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
###########################################################################################
|
211
|
+
# #
|
212
|
+
# A relative time must be able to be have equal times #
|
213
|
+
# implemented as an Array of Arrays; all elements of the 'subarray' are equal in time, #
|
214
|
+
# otherwise, the time relationship are based on position in the main array #
|
215
|
+
# #
|
216
|
+
# diff cannot be done, it makes no sense, due to the fuzziness #
|
217
|
+
# #
|
218
|
+
# RelativeTime must be within a TimeLine #
|
219
|
+
###########################################################################################
|
220
|
+
|
221
|
+
class RelativeTime
|
222
|
+
|
223
|
+
attr_accessor :timeline, :reference_to # reference_to should respond_to? :to_s
|
224
|
+
|
225
|
+
# create a realtive time *within a timeline* after position
|
226
|
+
def initialize tline, ref
|
227
|
+
@timeline= tline
|
228
|
+
@reference_to= ref
|
229
|
+
end
|
230
|
+
|
231
|
+
def to_s
|
232
|
+
reference_to.to_s
|
233
|
+
end
|
234
|
+
|
235
|
+
## any method on fixnum with 1 RelativeTime param can be in the list below
|
236
|
+
%w{< <= == != >= >}.each{ |meth|
|
237
|
+
self.send(:define_method, meth) {|b|
|
238
|
+
raise "can only compare to other times on the same timeline." unless valid_and_comparable_with?(b) # can NOT compare across TimeLines
|
239
|
+
self.timeline.position_of(self).send(meth, b.timeline.position_of(b))
|
240
|
+
}
|
241
|
+
}
|
242
|
+
|
243
|
+
def positioned?
|
244
|
+
self.timeline.indices_of.keys.include?(self)
|
245
|
+
end
|
246
|
+
|
247
|
+
def colinear_with?(b)
|
248
|
+
self.timeline.equal?(b.timeline)
|
249
|
+
end
|
250
|
+
|
251
|
+
|
252
|
+
protected
|
253
|
+
|
254
|
+
def valid_and_comparable_with?(b)
|
255
|
+
!self.timeline.nil? && !b.timeline.nil? && colinear_with?(b)
|
256
|
+
end
|
257
|
+
|
113
258
|
|
114
|
-
# first implementation is just as DateTime; later add fuzzy time
|
115
|
-
class RelativeTime < ::DateTime
|
116
259
|
end
|
260
|
+
|
117
261
|
end
|
data/lib/time_span/version.rb
CHANGED
@@ -2,82 +2,222 @@ require File.expand_path(File.dirname(__FILE__) + '../../spec_helper')
|
|
2
2
|
|
3
3
|
describe "TimeSpan" do
|
4
4
|
|
5
|
-
|
5
|
+
let(:timeline) do
|
6
|
+
TimeSpan::TimeLine.new "test timeline"
|
7
|
+
end
|
8
|
+
|
9
|
+
let (:time_a) do
|
10
|
+
TimeSpan::RelativeTime.new timeline, "Some timepoint a"
|
11
|
+
end
|
12
|
+
|
13
|
+
let (:time_b) do
|
14
|
+
TimeSpan::RelativeTime.new timeline, "Some timepoint b"
|
15
|
+
end
|
16
|
+
|
17
|
+
let (:time_c) do
|
18
|
+
TimeSpan::RelativeTime.new timeline, "Some timepoint c"
|
19
|
+
end
|
20
|
+
|
21
|
+
let (:time_d) do
|
22
|
+
TimeSpan::RelativeTime.new timeline, "Some timepoint d"
|
23
|
+
end
|
24
|
+
|
25
|
+
before(:each) do
|
26
|
+
timeline.append time_a
|
27
|
+
timeline.append time_b
|
28
|
+
timeline.append time_c
|
29
|
+
end
|
30
|
+
|
31
|
+
let (:time_span) do
|
32
|
+
TimeSpan::TimeSpan.new(time_a, time_c)
|
33
|
+
end
|
34
|
+
|
35
|
+
context "TimeSpan::TimeLine" do
|
6
36
|
|
7
37
|
context "instance methods" do
|
8
38
|
|
9
|
-
|
10
|
-
|
39
|
+
context "insertion" do
|
40
|
+
|
41
|
+
it "inserts a TimeSpan::RelativeTime into the timeline." do
|
42
|
+
timeline.line.should_not be_empty
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'populates the indices_of as well' do
|
46
|
+
timeline.indices_of.should_not be_empty
|
47
|
+
end
|
48
|
+
|
49
|
+
it "won't insert into the wrong timeline" do
|
50
|
+
timeline_b = TimeSpan::TimeLine.new "Another Timeline"
|
51
|
+
lambda {
|
52
|
+
timeline_b.append(time_a) }.should raise_error RuntimeError
|
53
|
+
end
|
54
|
+
|
55
|
+
it "populates the index hash" do
|
56
|
+
timeline.indices_of.should_not be_empty
|
57
|
+
end
|
58
|
+
|
59
|
+
it "appends to the next element if it exists" do
|
60
|
+
timeline.append_to_next time_a, time_d
|
61
|
+
timeline.line.should == [[time_a], [time_b, time_d], [time_c]]
|
62
|
+
end
|
63
|
+
|
64
|
+
it "inserts after and creates if element does not exist" do
|
65
|
+
timeline.append_to_next time_c, time_d
|
66
|
+
timeline.line.should == [[time_a], [time_b], [time_c], [time_d]]
|
67
|
+
end
|
68
|
+
|
69
|
+
it "inserts after a given time with insert_next" do
|
70
|
+
timeline.insert_before_next time_a, time_d
|
71
|
+
timeline.line.should == [[time_a], [time_d], [time_b], [time_c]]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should adjust the indices when inserting next" do
|
75
|
+
timeline.insert_before_next time_a, time_d
|
76
|
+
timeline.indices_of[time_b].should == 2
|
77
|
+
end
|
78
|
+
|
11
79
|
end
|
12
80
|
|
13
|
-
|
14
|
-
|
81
|
+
context "appending" do
|
82
|
+
|
83
|
+
it "appends to the timeline" do
|
84
|
+
timeline.append time_d
|
85
|
+
timeline.indices_of[time_d].should == 3
|
86
|
+
end
|
87
|
+
|
88
|
+
it "has only one RelativeTime after appending to an empty timeline" do
|
89
|
+
timeline.append time_d
|
90
|
+
timeline.line.size.should be(4)
|
91
|
+
end
|
92
|
+
|
15
93
|
end
|
16
94
|
|
95
|
+
context "finding" do
|
96
|
+
|
97
|
+
it "finds an inserted time_span" do
|
98
|
+
timeline.position_of(time_b).should == 1
|
99
|
+
end
|
17
100
|
|
18
|
-
let (:alpha) do
|
19
|
-
TimeSpan::TimeSpan.new(alpha_start, alpha_end)
|
20
101
|
end
|
21
102
|
|
22
|
-
context "
|
103
|
+
context "removal" do
|
23
104
|
|
24
|
-
|
105
|
+
it "removes the content with #remove(obj)" do
|
106
|
+
timeline.remove time_b
|
107
|
+
timeline.position_of(time_b).should be_nil
|
108
|
+
end
|
25
109
|
|
26
|
-
|
27
|
-
|
28
|
-
|
110
|
+
it "removes from the index as well" do
|
111
|
+
timeline.remove time_b
|
112
|
+
timeline.indices_of[time_b].should be_nil
|
113
|
+
end
|
29
114
|
|
30
|
-
|
31
|
-
|
32
|
-
|
115
|
+
it "does nothing when asked to remove a non-existent object" do
|
116
|
+
lambda { timeline.remove(time_d)}.should_not change(timeline, :indices_of)
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
context "compression" do
|
122
|
+
|
123
|
+
it "returns identity when compressing full timeline" do
|
124
|
+
timeline.compress!
|
125
|
+
timeline.line.size.should be 3
|
126
|
+
end
|
127
|
+
|
128
|
+
it "does not remove from index position when not compressing" do
|
129
|
+
timeline.remove time_b
|
130
|
+
timeline.indices_of[time_c].should == 2
|
131
|
+
end
|
33
132
|
|
34
|
-
|
133
|
+
it "removes from the index when compressing" do
|
134
|
+
timeline.remove time_b
|
135
|
+
timeline.compress!
|
136
|
+
timeline.indices_of[time_c].should == 1
|
137
|
+
end
|
35
138
|
|
36
|
-
|
37
|
-
|
38
|
-
|
139
|
+
it "removes content positions from the timeline when compressing" do
|
140
|
+
timeline.remove time_b
|
141
|
+
timeline.compress!
|
142
|
+
timeline.line.should == [[time_a], [time_c]]
|
143
|
+
end
|
39
144
|
|
145
|
+
end
|
146
|
+
|
147
|
+
context "finding position" do
|
148
|
+
|
149
|
+
it "returns the position of a RelativeTime" do
|
150
|
+
timeline.position_of(time_b).should == 1
|
151
|
+
end
|
152
|
+
|
153
|
+
end
|
154
|
+
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
40
158
|
|
41
|
-
|
42
|
-
alpha.should be_ends_with(beta)
|
43
|
-
end
|
159
|
+
context "TimeSpan::TimeSpan" do
|
44
160
|
|
161
|
+
context "instance methods" do
|
162
|
+
|
163
|
+
context "creation" do
|
164
|
+
|
165
|
+
it "should not allow creation when RelativeTime elements which are not on the same TimeLine" do
|
166
|
+
other_timeline = TimeSpan::TimeLine.new "another timeline"
|
167
|
+
time_other = TimeSpan::RelativeTime.new other_timeline, "Time on other timeline"
|
168
|
+
lambda {
|
169
|
+
TimeSpan::TimeSpan.new(time_a, time_other)
|
170
|
+
}.should raise_error
|
171
|
+
end
|
172
|
+
|
173
|
+
end
|
174
|
+
|
175
|
+
context "single time point comparators" do
|
176
|
+
|
177
|
+
context "same end time, time_span starts before other time_span" do
|
178
|
+
|
179
|
+
let (:time_span_2) do
|
180
|
+
TimeSpan::TimeSpan.new(time_b, time_c)
|
45
181
|
end
|
46
182
|
|
47
|
-
|
183
|
+
it "start before other time_span" do
|
184
|
+
time_span.should be_starts_before(time_span_2)
|
185
|
+
end
|
48
186
|
|
49
|
-
|
50
|
-
|
51
|
-
|
187
|
+
it "end with other time_span" do
|
188
|
+
time_span.should be_ends_with(time_span_2)
|
189
|
+
end
|
52
190
|
|
53
|
-
|
54
|
-
|
55
|
-
|
191
|
+
it "not start after other time_span" do
|
192
|
+
time_span.should_not be_starts_after(time_span_2)
|
193
|
+
end
|
56
194
|
|
57
|
-
|
58
|
-
|
59
|
-
|
195
|
+
it "not start with other time_span" do
|
196
|
+
time_span.should_not be_starts_with(time_span_2)
|
197
|
+
end
|
60
198
|
|
61
|
-
|
62
|
-
|
63
|
-
|
199
|
+
it "not end before other time_span" do
|
200
|
+
time_span.should_not be_ends_before(time_span_2)
|
201
|
+
end
|
64
202
|
|
65
|
-
|
66
|
-
|
67
|
-
|
203
|
+
it "end after other time_span" do
|
204
|
+
time_span.should_not be_ends_after(time_span_2)
|
205
|
+
end
|
68
206
|
|
69
|
-
|
70
|
-
|
71
|
-
|
207
|
+
it "end before other time_span starts" do
|
208
|
+
time_span.should_not be_ends_before_other_starts(time_span_2)
|
209
|
+
end
|
72
210
|
|
73
|
-
|
74
|
-
|
75
|
-
|
211
|
+
it "end as other time_span starts" do
|
212
|
+
time_span.should_not be_ends_as_other_starts(time_span_2)
|
213
|
+
end
|
76
214
|
|
77
|
-
|
78
|
-
|
79
|
-
|
215
|
+
it "start after other time_span ends" do
|
216
|
+
time_span.should_not be_starts_after_other_ends(time_span_2)
|
217
|
+
end
|
80
218
|
|
219
|
+
it "start as other time_span ends" do
|
220
|
+
time_span.should_not be_starts_as_other_ends(time_span_2)
|
81
221
|
end
|
82
222
|
|
83
223
|
end
|
@@ -86,63 +226,65 @@ describe "TimeSpan" do
|
|
86
226
|
|
87
227
|
context "time span comparators" do
|
88
228
|
|
89
|
-
let (:
|
90
|
-
|
229
|
+
let (:time_d) do
|
230
|
+
TimeSpan::RelativeTime.new timeline, "Some timepoint d"
|
91
231
|
end
|
92
232
|
|
93
|
-
let (:
|
94
|
-
|
233
|
+
let (:time_e) do
|
234
|
+
TimeSpan::RelativeTime.new timeline, "Some timepoint e"
|
95
235
|
end
|
96
236
|
|
97
|
-
|
98
|
-
|
237
|
+
before(:each) do
|
238
|
+
timeline.append time_a
|
239
|
+
timeline.append time_b
|
240
|
+
timeline.append time_c
|
241
|
+
timeline.append time_d
|
242
|
+
timeline.append time_e
|
99
243
|
end
|
100
244
|
|
101
|
-
let (:
|
102
|
-
|
245
|
+
let (:time_span_mid) do
|
246
|
+
TimeSpan::TimeSpan.new(time_b, time_c)
|
103
247
|
end
|
104
248
|
|
105
|
-
let
|
106
|
-
|
249
|
+
let(:time_span_all) do
|
250
|
+
TimeSpan::TimeSpan.new(time_a, time_e)
|
107
251
|
end
|
108
252
|
|
109
|
-
let (:
|
110
|
-
|
253
|
+
let (:time_span_last) do
|
254
|
+
TimeSpan::TimeSpan.new(time_d, time_e)
|
111
255
|
end
|
112
256
|
|
113
257
|
it "identical start and end times should return == as true" do
|
114
|
-
|
115
|
-
|
258
|
+
time_span_3 = TimeSpan::TimeSpan.new(time_a, time_c)
|
259
|
+
time_span.should == time_span_3
|
116
260
|
end
|
117
261
|
|
118
|
-
it "
|
119
|
-
|
120
|
-
alpha.should < beta
|
262
|
+
it "any differences should return not equal" do
|
263
|
+
time_span_all.should_not == time_span # end times differ
|
121
264
|
end
|
122
265
|
|
123
|
-
it "
|
124
|
-
|
125
|
-
alpha.should > beta
|
266
|
+
it "time_span ends before time_span_last starts, should return time_span < time_span_last" do
|
267
|
+
time_span.should < time_span_last
|
126
268
|
end
|
127
269
|
|
128
|
-
it "
|
129
|
-
|
130
|
-
alpha.should be_contained_fully_inside(beta)
|
270
|
+
it "time_span starts after time_span_last ends, should return time_span_last > time_span" do
|
271
|
+
time_span_last.should > time_span
|
131
272
|
end
|
132
273
|
|
133
|
-
it "
|
134
|
-
|
135
|
-
alpha.should be_contained_inside(beta)
|
274
|
+
it "time_span starting after and ending before time_span_mid should be contained inside time_span_mid" do
|
275
|
+
time_span_mid.should be_contained_fully_inside(time_span_all)
|
136
276
|
end
|
137
277
|
|
138
|
-
it "
|
139
|
-
|
140
|
-
alpha.should be_contains_fully(beta)
|
278
|
+
it "time_span starting with time_span_all but ending before, is contained inside" do
|
279
|
+
time_span.should be_contained_inside(time_span_all)
|
141
280
|
end
|
142
281
|
|
143
|
-
it "
|
144
|
-
|
145
|
-
|
282
|
+
it "time_span starting before and ending after time_span_full contains time_span_mid" do
|
283
|
+
time_span_all.should be_contains_fully(time_span_mid)
|
284
|
+
end
|
285
|
+
|
286
|
+
it "time_span starting before but ending with time_span_mid should contain time_span_mmid" do
|
287
|
+
time_span.should be_contains(time_span_mid)
|
146
288
|
end
|
147
289
|
|
148
290
|
end
|
@@ -151,6 +293,35 @@ describe "TimeSpan" do
|
|
151
293
|
|
152
294
|
end
|
153
295
|
|
296
|
+
context "TimeSpan::RelativeTime" do
|
297
|
+
|
298
|
+
context "instance methods" do
|
299
|
+
|
300
|
+
## comparators make sense only in TimeLine context, not tested here.
|
154
301
|
|
302
|
+
let (:timeline) do
|
303
|
+
TimeSpan::TimeLine.new "Some timeline"
|
304
|
+
end
|
305
|
+
|
306
|
+
let (:time) do
|
307
|
+
TimeSpan::RelativeTime.new timeline, "time point"
|
308
|
+
end
|
309
|
+
|
310
|
+
it "returns a string if using '.to_s'" do
|
311
|
+
time.to_s.should == 'time point'
|
312
|
+
end
|
313
|
+
|
314
|
+
it "is not positioned if not appended" do
|
315
|
+
time.should_not be_positioned
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'is positioned once appended.' do
|
319
|
+
timeline.append time
|
320
|
+
time.should be_positioned
|
321
|
+
end
|
322
|
+
|
323
|
+
end
|
324
|
+
|
325
|
+
end
|
155
326
|
|
156
327
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: time_span
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.
|
4
|
+
version: 0.0.1.alpha04
|
5
5
|
prerelease: 6
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-02-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
16
|
-
requirement: &
|
16
|
+
requirement: &79260830 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *79260830
|
25
25
|
description: Time spans, including many comparators
|
26
26
|
email:
|
27
27
|
- craig.a.cook@gmail.com
|