time_span 0.0.1.alpha03 → 0.0.1.alpha04
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.
- 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
|