time_frame 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
1
+ class TimeFrame
2
+ # Provides a method to split a time frame by a given interval. It returns
3
+ # an array which contains the intervals as TimeFrame instances.
4
+ module Splitter
5
+ def split_by_interval(interval)
6
+ time = @min
7
+ max = @max
8
+ time_frames = []
9
+ until time >= max
10
+ time_old = time
11
+ time += interval
12
+ time_frames << TimeFrame.new(min: time_old, max: [time, max].min)
13
+ end
14
+ time_frames
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,23 @@
1
+ class TimeFrame
2
+ # Creates a union of many time_frame's. You can request a sorted collection by
3
+ # the min Time value.
4
+ class Uniter
5
+ def initialize(time_frames, options = {})
6
+ @time_frames = time_frames
7
+ @sorted = options[:sorted]
8
+ end
9
+
10
+ def unite
11
+ frames = @sorted ? @time_frames : @time_frames.sort_by(&:min)
12
+ frames.reduce([]) do |result, frame|
13
+ last_frame = result.last
14
+ if last_frame && last_frame.cover?(frame.min)
15
+ result[-1] = TimeFrame.new(min: last_frame.min, max: frame.max)
16
+ else
17
+ result << frame
18
+ end
19
+ result
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,4 @@
1
+ # gem version
2
+ class TimeFrame
3
+ VERSION = '0.0.1'
4
+ end
@@ -0,0 +1,13 @@
1
+ require 'time_frame'
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start do
5
+ add_filter '/spec/'
6
+ end
7
+
8
+ Time.zone ||= 'UTC'
9
+
10
+ require 'rspec'
11
+ RSpec.configure do |config|
12
+ config.order = 'random'
13
+ end
@@ -0,0 +1,1075 @@
1
+ require 'spec_helper'
2
+
3
+ describe TimeFrame do
4
+ let(:time) { Time.zone.local(2012) }
5
+ let(:duration) { 2.hours }
6
+
7
+ describe '#initialize' do
8
+ context 'when given two times' do
9
+ context 'and min is smaller than max' do
10
+ subject { TimeFrame.new(min: time, max: time + duration) }
11
+
12
+ describe '#min' do
13
+ subject { super().min }
14
+ it { should eq time }
15
+ end
16
+
17
+ describe '#max' do
18
+ subject { super().max }
19
+ it { should eq time + duration }
20
+ end
21
+ end
22
+ context 'and max is smaller than min' do
23
+ specify do
24
+ expect do
25
+ TimeFrame.new(min: time, max: time - 1.day)
26
+ end.to raise_error(ArgumentError)
27
+ end
28
+ end
29
+ context 'which are equal' do
30
+ subject { TimeFrame.new(min: time, max: time) }
31
+
32
+ describe '#min' do
33
+ subject { super().min }
34
+ it { should eq time }
35
+ end
36
+
37
+ describe '#max' do
38
+ subject { super().max }
39
+ it { should eq time }
40
+ end
41
+ end
42
+ end
43
+
44
+ context 'when time and duration is given' do
45
+ context ' and duration is positive' do
46
+ subject { TimeFrame.new(min: time, duration: 1.hour) }
47
+
48
+ describe '#min' do
49
+ subject { super().min }
50
+ it { should eq time }
51
+ end
52
+
53
+ describe '#max' do
54
+ subject { super().max }
55
+ it { should eq time + 1.hour }
56
+ end
57
+ end
58
+ context 'and duration is negative' do
59
+ let(:invalid_t_r) { TimeFrame.new(min: time, duration: - 1.hour) }
60
+ specify { expect { invalid_t_r }.to raise_error }
61
+ end
62
+ context 'and duration is 0' do
63
+ subject { TimeFrame.new(min: time, duration: 0.hour) }
64
+
65
+ describe '#min' do
66
+ subject { super().min }
67
+ it { should eq time }
68
+ end
69
+
70
+ describe '#max' do
71
+ subject { super().max }
72
+ it { should eq time }
73
+ end
74
+ end
75
+ context 'and time frame covers a DST shift' do
76
+ let(:time) do
77
+ Time.use_zone('Europe/Berlin') { Time.zone.local(2013, 10, 27) }
78
+ end
79
+ subject { TimeFrame.new(min: time, duration: 1.day) }
80
+
81
+ describe '#min' do
82
+ subject { super().min }
83
+ it { should eq time }
84
+ end
85
+
86
+ describe '#max' do
87
+ subject { super().max }
88
+ it { should eq time + 25.hours }
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#duration' do
95
+ context 'when borders are different' do
96
+ subject { TimeFrame.new(min: time, duration: 2.hours).duration }
97
+ it { should eq 2.hours }
98
+ end
99
+ context 'when borders are equal' do
100
+ subject { TimeFrame.new(min: time, max: time).duration }
101
+ it { should eq 0 }
102
+ end
103
+ context 'when time frame containts a DST shift' do
104
+ it 'should gain 1 hour on summer -> winter shifts' do
105
+ Time.use_zone('Europe/Berlin') do
106
+ time_frame = TimeFrame.new(min: Time.zone.local(2013, 10, 27),
107
+ max: Time.zone.local(2013, 10, 28))
108
+ expect(time_frame.duration).to eq 25.hours
109
+ end
110
+ end
111
+ it 'should lose 1 hour on winter -> summer shifts' do
112
+ Time.use_zone('Europe/Berlin') do
113
+ time_frame = TimeFrame.new(min: Time.zone.local(2013, 3, 31),
114
+ max: Time.zone.local(2013, 4, 1))
115
+ expect(time_frame.duration).to eq 23.hours
116
+ end
117
+ end
118
+ end
119
+ end
120
+
121
+ describe '#==' do
122
+ let(:frame) { TimeFrame.new(min: time, duration: 2.hours) }
123
+ context 'when borders are equal' do
124
+ let(:other) { TimeFrame.new(min: time, duration: 2.hours) }
125
+ subject { frame == other }
126
+ it { should be_true }
127
+ end
128
+ context 'when min value is different' do
129
+ let(:other) do
130
+ TimeFrame.new(min: time - 1.hour, max: time + 2.hours)
131
+ end
132
+ subject { frame == other }
133
+ it { should be_false }
134
+ end
135
+ context 'when max value is different' do
136
+ let(:other) { TimeFrame.new(min: time, duration: 3.hours) }
137
+ subject { frame == other }
138
+ it { should be_false }
139
+ end
140
+ end
141
+
142
+ describe '#cover?' do
143
+ let(:frame) { TimeFrame.new(min: time, duration: 4.hours) }
144
+ context 'when argument is a Time instance' do
145
+ context 'and its covered' do
146
+ context 'and equal to min' do
147
+ subject { frame.cover?(frame.min) }
148
+ it { should be_true }
149
+ end
150
+ context 'and equal to max' do
151
+ subject { frame.cover?(frame.max) }
152
+ it { should be_true }
153
+ end
154
+ context 'and is an inner value' do
155
+ subject { frame.cover?(frame.min + 1.hour) }
156
+ it { should be_true }
157
+ end
158
+ end
159
+ context 'and its not covered' do
160
+ context 'and smaller than min' do
161
+ subject { frame.cover?(frame.min - 1.hour) }
162
+ it { should be_false }
163
+ end
164
+ context 'and greater than max' do
165
+ subject { frame.cover?(frame.max + 5.hours) }
166
+ it { should be_false }
167
+ end
168
+ end
169
+ end
170
+ context 'when argument is a TimeFrame' do
171
+ context 'and its covered' do
172
+ context 'and they have the same min value' do
173
+ let(:other) { TimeFrame.new(min: frame.min, duration: 2.hours) }
174
+ subject { frame.cover?(other) }
175
+ it { should be_true }
176
+ end
177
+ context 'and they have the same max value' do
178
+ let(:other) do
179
+ TimeFrame.new(min: frame.min + 1.hour, max: frame.max)
180
+ end
181
+ subject { frame.cover?(other) }
182
+ it { should be_true }
183
+ end
184
+ context 'and it is within the interior of self' do
185
+ let(:other) do
186
+ TimeFrame.new(min: frame.min + 1.hour, max: frame.max - 1.hour)
187
+ end
188
+ subject { frame.cover?(other) }
189
+ it { should be_true }
190
+ end
191
+ context 'and are equal' do
192
+ let(:other) { frame.clone }
193
+ subject { frame.cover?(other) }
194
+ it { should be_true }
195
+ end
196
+ end
197
+ context 'and it is not covered' do
198
+ context 'and other is left of self' do
199
+ let(:other) { frame.shift_by(-5.hours) }
200
+ subject { frame.cover?(other) }
201
+ it { should be_false }
202
+ end
203
+ context 'and other overlaps left hand side' do
204
+ let(:other) { frame.shift_by(-1.hour) }
205
+ subject { frame.cover?(other) }
206
+ it { should be_false }
207
+ end
208
+ context 'and other overlaps left hand side at the border only' do
209
+ let(:other) { frame.shift_by(-frame.duration) }
210
+ subject { frame.cover?(other) }
211
+ it { should be_false }
212
+ end
213
+ context 'and other is right of self' do
214
+ let(:other) { frame.shift_by(5.hours) }
215
+ subject { frame.cover?(other) }
216
+ it { should be_false }
217
+ end
218
+ context 'and other overlaps right hand side' do
219
+ let(:other) { frame.shift_by(1.hours) }
220
+ subject { frame.cover?(other) }
221
+ it { should be_false }
222
+ end
223
+ context 'and other overlaps right hand side at the border only' do
224
+ let(:other) { frame.shift_by(frame.duration) }
225
+ subject { frame.cover?(other) }
226
+ it { should be_false }
227
+ end
228
+ end
229
+ end
230
+ end
231
+
232
+ describe '#deviation_of' do
233
+ let(:time_frame) do
234
+ TimeFrame.new(min: Time.zone.local(2012), duration: 2.days)
235
+ end
236
+ context 'when providing a time object' do
237
+ describe 'when self covers time' do
238
+ context 'and time equals min' do
239
+ let(:time) { time_frame.min }
240
+ subject { time_frame.deviation_of(time) }
241
+ it { should eq 0.minutes }
242
+ end
243
+ context 'and time equals max' do
244
+ let(:time) { time_frame.max }
245
+ subject { time_frame.deviation_of(time) }
246
+ it { should eq 0.minutes }
247
+ end
248
+ context 'and time is an interior point of self' do
249
+ let(:time) { time_frame.min + (time_frame.duration / 2.0) }
250
+ subject { time_frame.deviation_of(time) }
251
+ it { should eq 0.minutes }
252
+ end
253
+ end
254
+ context 'when self do not cover time' do
255
+ context 'and time is smaller than the left bound' do
256
+ let(:time) { time_frame.min - 42.hours - 42.minutes }
257
+ subject { time_frame.deviation_of(time) }
258
+ it { should eq(-42.hours - 42.minutes) }
259
+ end
260
+ context 'and time is greater than the right bound' do
261
+ let(:time) { time_frame.max + 42.hours + 42.minutes }
262
+ subject { time_frame.deviation_of(time) }
263
+ it { should eq 42.hours + 42.minutes }
264
+ end
265
+ end
266
+ end
267
+ context 'when providing a time_frame' do
268
+ describe 'when self overlaps other' do
269
+ context 'and its partly' do
270
+ let(:other) { time_frame.shift_by(time_frame.duration / 2) }
271
+ subject { time_frame.deviation_of(other) }
272
+ it { should eq 0.minutes }
273
+ end
274
+ context 'and time equals max' do
275
+ let(:other) { time_frame }
276
+ subject { time_frame.deviation_of(other) }
277
+ it { should eq 0.minutes }
278
+ end
279
+ context 'and other lies in the interior of self' do
280
+ let(:other) do
281
+ TimeFrame.new(min: time_frame.min + 1.hour, duration: 1.hour)
282
+ end
283
+ subject { time_frame.deviation_of(other) }
284
+ it { should eq 0.minutes }
285
+ end
286
+ end
287
+ context 'when self do not cover time' do
288
+ context 'and time is smaller than the left bound' do
289
+ let(:other) { time_frame.shift_by(-2.days - 42.seconds) }
290
+ subject { time_frame.deviation_of(other) }
291
+ it { should eq(-42.seconds) }
292
+ end
293
+ context 'and time is greater than the right bound' do
294
+ let(:other) { time_frame.shift_by(2.days + 42.seconds) }
295
+ subject { time_frame.deviation_of(other) }
296
+ it { should eq 42.seconds }
297
+ end
298
+ end
299
+ end
300
+ end
301
+
302
+ describe '#split_to_interval' do
303
+ let(:time_frame) do
304
+ TimeFrame.new(min: Time.zone.local(2012), duration: 1.day)
305
+ end
306
+ context 'when time frame duration is divisible by interval' do
307
+ context 'and interval length equals duration' do
308
+ subject { time_frame.split_by_interval(1.day) }
309
+ it { should eq [time_frame] }
310
+ end
311
+ context 'and interval is smaller than duration' do
312
+ let(:first_time_frame) do
313
+ TimeFrame.new(min: time_frame.min, duration: 12.hours)
314
+ end
315
+ subject { time_frame.split_by_interval(12.hours) }
316
+ it do
317
+ should eq [first_time_frame, first_time_frame.shift_by(12.hours)]
318
+ end
319
+ end
320
+ context 'and frame starts at a time, not divisible by interval' do
321
+ let(:other_time_frame) do
322
+ TimeFrame.new(
323
+ min: Time.zone.local(2012) + 1.minute,
324
+ duration: 1.day
325
+ )
326
+ end
327
+ let(:first_time_frame) do
328
+ TimeFrame.new(min: other_time_frame.min, duration: 12.hours)
329
+ end
330
+ subject { other_time_frame.split_by_interval(12.hours) }
331
+ it do
332
+ should eq [first_time_frame, first_time_frame.shift_by(12.hours)]
333
+ end
334
+ end
335
+ end
336
+ context 'when time frame duration is not divisible by interval' do
337
+ let(:expected) do
338
+ [
339
+ TimeFrame.new(min: time_frame.min, duration: 18.hours),
340
+ TimeFrame.new(min: time_frame.min + 18.hours, duration: 6.hours)
341
+ ]
342
+ end
343
+ subject { time_frame.split_by_interval(18.hours) }
344
+ it { should eq expected }
345
+ end
346
+ end
347
+
348
+ describe '#empty?' do
349
+ context 'when min equals max' do
350
+ subject { TimeFrame.new(min: time, max: time) }
351
+ it { should be_empty }
352
+ end
353
+
354
+ context 'when max is greater than min' do
355
+ subject { TimeFrame.new(min: time, duration: 1.day) }
356
+ it { should_not be_empty }
357
+ end
358
+ end
359
+
360
+ describe '.union' do
361
+
362
+ context 'when given an empty array' do
363
+ subject { TimeFrame.union([]) }
364
+ it { should eq [] }
365
+ end
366
+
367
+ context 'when given a single time frame' do
368
+ let(:frame) { TimeFrame.new(min: time, duration: 1.hour) }
369
+ subject { TimeFrame.union([frame]) }
370
+ it { should eq [frame] }
371
+ end
372
+
373
+ context 'when getting single element it returns a dup' do
374
+ let(:frames) { [TimeFrame.new(min: time, duration: 1.hour)] }
375
+ subject { TimeFrame.union(frames) }
376
+ it { should_not equal frames }
377
+ end
378
+
379
+ context 'when given time frames' do
380
+ context 'in order' do
381
+ context 'and no sorted flag is provided' do
382
+ context 'that are overlapping' do
383
+ let(:frame1) { TimeFrame.new(min: time, duration: 2.hours) }
384
+ let(:frame2) { frame1.shift_by(1.hour) }
385
+ let(:expected) do
386
+ [TimeFrame.new(min: frame1.min, max: frame2.max)]
387
+ end
388
+ subject { TimeFrame.union([frame1, frame2]) }
389
+ it { should eq expected }
390
+ end
391
+ context 'that are disjoint' do
392
+ let(:frame1) { TimeFrame.new(min: time, duration: 2.hours) }
393
+ let(:frame2) { frame1.shift_by(3.hours) }
394
+ subject { TimeFrame.union([frame1, frame2]) }
395
+ it { should eq [frame1, frame2] }
396
+ end
397
+ context 'that intersect at their boundaries' do
398
+ let(:frame1) { TimeFrame.new(min: time, duration: + 2.hour) }
399
+ let(:frame2) { frame1.shift_by(frame1.duration) }
400
+ let(:expected) do
401
+ [TimeFrame.new(min: frame1.min, max: frame2.max)]
402
+ end
403
+ subject { TimeFrame.union([frame1, frame2]) }
404
+ it { should eq expected }
405
+ end
406
+ end
407
+ context 'and the sorted flag is provided' do
408
+ context 'that are overlapping' do
409
+ let(:frame1) { TimeFrame.new(min: time, duration: 2.hours) }
410
+ let(:frame2) { frame1.shift_by(1.hour) }
411
+ let(:expected) do
412
+ [TimeFrame.new(min: frame1.min, max: frame2.max)]
413
+ end
414
+ subject { TimeFrame.union([frame1, frame2], sorted: true) }
415
+ it { should eq expected }
416
+ end
417
+ context 'that are disjoint' do
418
+ let(:frame1) { TimeFrame.new(min: time, duration: 2.hours) }
419
+ let(:frame2) { frame1.shift_by(3.hours) }
420
+ subject { TimeFrame.union([frame1, frame2], sorted: true) }
421
+ it { should eq [frame1, frame2] }
422
+ end
423
+ context 'that intersect at their boundaries' do
424
+ let(:frame1) { TimeFrame.new(min: time, duration: + 2.hour) }
425
+ let(:frame2) { frame1.shift_by(frame1.duration) }
426
+ let(:expected) do
427
+ [TimeFrame.new(min: frame1.min, max: frame2.max)]
428
+ end
429
+ subject { TimeFrame.union([frame1, frame2], sorted: true) }
430
+ it { should eq expected }
431
+ end
432
+ end
433
+ end
434
+ context 'not in order' do
435
+ context 'that are overlapping' do
436
+ let(:frame1) { TimeFrame.new(min: time, duration: 2.hours) }
437
+ let(:frame2) { frame1.shift_by(1.hour) }
438
+ subject { TimeFrame.union([frame2, frame1]) }
439
+ it { should eq [TimeFrame.new(min: frame1.min, max: frame2.max)] }
440
+ end
441
+ context 'that are disjoint' do
442
+ let(:frame1) { TimeFrame.new(min: time, duration: 2.hours) }
443
+ let(:frame2) { frame1.shift_by(3.hours) }
444
+ subject { TimeFrame.union([frame2, frame1]) }
445
+ it { should eq [frame1, frame2] }
446
+ end
447
+ context 'that intersect at their boundaries' do
448
+ let(:frame1) { TimeFrame.new(min: time, duration: + 2.hour) }
449
+ let(:frame2) { frame1.shift_by(frame1.duration) }
450
+ subject { TimeFrame.union([frame2, frame1]) }
451
+ it { should eq [TimeFrame.new(min: frame1.min, max: frame2.max)] }
452
+ end
453
+ end
454
+ end
455
+ end
456
+
457
+ describe '.intersection' do
458
+ it 'returns the intersection of all time frames' do
459
+ frame1 = TimeFrame.new(min: Time.zone.local(2012), duration: 3.days)
460
+ frame2 = frame1.shift_by(-1.day)
461
+ frame3 = frame1.shift_by(-2.days)
462
+ expect(TimeFrame.intersection([frame1, frame2, frame3]))
463
+ .to eq TimeFrame.new(min: Time.zone.local(2012), duration: 1.day)
464
+ end
465
+ it 'returns nil if the intersection is empty' do
466
+ frame1 = TimeFrame.new(min: Time.zone.local(2012), duration: 1.days)
467
+ frame2 = frame1.shift_by(-2.day)
468
+ frame3 = frame1.shift_by(-4.days)
469
+ expect(TimeFrame.intersection([frame1, frame2, frame3])).to be_nil
470
+ end
471
+ end
472
+
473
+ describe '#overlaps?' do
474
+ let(:frame) { TimeFrame.new(min: time, duration: 3.hours) }
475
+ context 'when self is equal to other' do
476
+ let(:other) { frame.clone }
477
+ subject { frame.overlaps?(other) }
478
+ it { should be_true }
479
+ end
480
+ context 'when self covers other' do
481
+ let(:other) do
482
+ TimeFrame.new(min: frame.min + 1.hour, max: frame.max - 1.hour)
483
+ end
484
+ subject { frame.overlaps?(other) }
485
+ it { should be_true }
486
+ end
487
+ context 'when other covers self' do
488
+ let(:other) do
489
+ TimeFrame.new(min: frame.min - 1.hour, max: frame.max + 1.hour)
490
+ end
491
+ subject { frame.overlaps?(other) }
492
+ it { should be_true }
493
+ end
494
+ context 'when self begins earlier than other' do
495
+ context 'and they are disjoint' do
496
+ let(:other) { frame.shift_by(-frame.duration - 1.hour) }
497
+ subject { frame.overlaps?(other) }
498
+ it { should be_false }
499
+ end
500
+ context 'and they are overlapping' do
501
+ let(:other) { frame.shift_by(-1.hours) }
502
+ subject { frame.overlaps?(other) }
503
+ it { should be_true }
504
+ end
505
+ context 'and they intersect at their boundaries' do
506
+ let(:other) { frame.shift_by(-frame.duration) }
507
+ subject { frame.overlaps?(other) }
508
+ it { should be_false }
509
+ end
510
+ end
511
+ context 'when other begins earlier than self' do
512
+ context 'and they are disjoint' do
513
+ let(:other) { frame.shift_by(frame.duration + 1.hour) }
514
+ subject { frame.overlaps?(other) }
515
+ it { should be_false }
516
+ end
517
+ context 'and they are overlapping' do
518
+ let(:other) { frame.shift_by(1.hours) }
519
+ subject { frame.overlaps?(other) }
520
+ it { should be_true }
521
+ end
522
+ context 'and they intersect at their boundaries' do
523
+ let(:other) { frame.shift_by(frame.duration) }
524
+ subject { frame.overlaps?(other) }
525
+ it { should be_false }
526
+ end
527
+ end
528
+ end
529
+
530
+ describe '#&' do
531
+ let(:frame) { TimeFrame.new(min: time, duration: 3.hours) }
532
+ context 'when self is equal to other' do
533
+ let(:other) { frame.clone }
534
+ subject { frame & other }
535
+ it { should eq frame }
536
+ end
537
+ context 'when self covers other' do
538
+ let(:other) do
539
+ TimeFrame.new(min: frame.min + 1.hour, max: frame.max - 1.hour)
540
+ end
541
+ subject { frame & other }
542
+ it { should eq other }
543
+ end
544
+ context 'when other covers self' do
545
+ let(:other) do
546
+ TimeFrame.new(min: frame.min - 1.hour, max: frame.max + 1.hour)
547
+ end
548
+ subject { frame & other }
549
+ it { should eq frame }
550
+ end
551
+ context 'when self begins earlier than other' do
552
+ context 'and they are disjoint' do
553
+ let(:other) { frame.shift_by(frame.duration + 1.hour) }
554
+ subject { frame & other }
555
+ it { should be_nil }
556
+ end
557
+ context 'and they are overlapping' do
558
+ let(:other) { frame.shift_by(1.hour) }
559
+ subject { frame & other }
560
+ it { should eq TimeFrame.new(min: other.min, max: frame.max) }
561
+ end
562
+ context 'and they intersect at their boundaries' do
563
+ let(:other) { frame.shift_by(frame.duration) }
564
+ subject { frame & other }
565
+ it { should eq TimeFrame.new(min: frame.max, max: frame.max) }
566
+ end
567
+ end
568
+ context 'when other begins earlier than self' do
569
+ context 'and they are disjoint' do
570
+ let(:other) { frame.shift_by(-frame.duration - 1.hour) }
571
+ subject { frame & other }
572
+ it { should be_nil }
573
+ end
574
+ context 'and they are overlapping' do
575
+ let(:other) { frame.shift_by(-1.hour) }
576
+ subject { frame & other }
577
+ it { should eq TimeFrame.new(min: frame.min, max: other.max) }
578
+ end
579
+ context 'and they intersect at their boundaries' do
580
+ let(:other) { frame.shift_by(-frame.duration) }
581
+ subject { frame & other }
582
+ it { should eq TimeFrame.new(min: frame.min, max: frame.min) }
583
+ end
584
+ end
585
+ end
586
+
587
+ describe '#split_by_interval' do
588
+ context 'when time frame duration is divisible by interval' do
589
+ let(:time) { Time.new(2012, 1, 1) }
590
+ let(:interval) { 1.day }
591
+ let(:time_frame) do
592
+ TimeFrame.new(min: time, duration: 7.days)
593
+ end
594
+ subject do
595
+ time_frame.split_by_interval(interval)
596
+ end
597
+
598
+ describe '#size' do
599
+ subject { super().size }
600
+ it { should eq 7 }
601
+ end
602
+ (0..6).each do |day|
603
+ it "should have the right borders on day #{day}" do
604
+ expected = TimeFrame.new(min: time, duration: interval)
605
+ expect(subject[day]).to eq expected.shift_by(day.days)
606
+ end
607
+ end
608
+ end
609
+
610
+ context 'when time frame duration is not divisible by interval' do
611
+ let(:time) { Time.new(2012, 1, 1) }
612
+ let(:interval) { 1.day }
613
+ let(:time_frame) do
614
+ TimeFrame.new(min: time, duration: 7.days + 12.hours)
615
+ end
616
+ subject do
617
+ time_frame.split_by_interval(interval)
618
+ end
619
+
620
+ describe '#size' do
621
+ subject { super().size }
622
+ it { should eq 8 }
623
+ end
624
+ (0..6).each do |day|
625
+ it "should have the right borders on day #{day}" do
626
+ expected = TimeFrame.new(min: time, duration: interval)
627
+ expect(subject[day]).to eq expected.shift_by(day.days)
628
+ end
629
+ end
630
+ it 'should have a smaller frame at the end' do
631
+ expected = TimeFrame.new(min: time + 7.days, duration: 12.hours)
632
+ expect(subject[7]).to eq expected
633
+ end
634
+ end
635
+ end
636
+
637
+ describe '#shift_by' do
638
+ let(:min) { time }
639
+ let(:max) { time + 2.days }
640
+ let(:frame) { TimeFrame.new(min: min, max: max) }
641
+ context 'when shifting into the future' do
642
+ subject { frame.shift_by(1.day) }
643
+
644
+ describe '#min' do
645
+ subject { super().min }
646
+ it { should eq min + 1.day }
647
+ end
648
+
649
+ describe '#max' do
650
+ subject { super().max }
651
+ it { should eq max + 1.day }
652
+ end
653
+ it { should_not equal frame }
654
+ end
655
+ context 'when shifting into the past' do
656
+ subject { frame.shift_by(-1.day) }
657
+
658
+ describe '#min' do
659
+ subject { super().min }
660
+ it { should eq min - 1.day }
661
+ end
662
+
663
+ describe '#max' do
664
+ subject { super().max }
665
+ it { should eq max - 1.day }
666
+ end
667
+ it { should_not equal frame }
668
+ end
669
+ context 'when shifting by 0' do
670
+ subject { frame.shift_by(0) }
671
+
672
+ describe '#min' do
673
+ subject { super().min }
674
+ it { should eq min }
675
+ end
676
+
677
+ describe '#max' do
678
+ subject { super().max }
679
+ it { should eq max }
680
+ end
681
+ it { should_not equal frame }
682
+ end
683
+ context 'when shifting back and forth' do
684
+ subject { frame.shift_by(-1.day).shift_by(1.day) }
685
+
686
+ describe '#min' do
687
+ subject { super().min }
688
+ it { should eq min }
689
+ end
690
+
691
+ describe '#max' do
692
+ subject { super().max }
693
+ it { should eq max }
694
+ end
695
+ it { should_not equal frame }
696
+ end
697
+ end
698
+
699
+ describe '#shift_to' do
700
+
701
+ let(:duration) { 1.day }
702
+ let(:min) { Time.zone.local(2012, 1, 2) }
703
+ let(:max) { min + duration }
704
+ let(:frame) { TimeFrame.new(min: min, max: max) }
705
+
706
+ context 'when shifting to a future time' do
707
+ let(:destination) { min + duration }
708
+ subject { frame.shift_to(destination) }
709
+ it { should_not equal frame }
710
+
711
+ describe '#min' do
712
+ subject { super().min }
713
+ it { should eq destination }
714
+ end
715
+
716
+ describe '#max' do
717
+ subject { super().max }
718
+ it { should eq destination + duration }
719
+ end
720
+ end
721
+
722
+ context 'when shifting to a past time' do
723
+ let(:destination) { min - duration }
724
+ subject { frame.shift_to(destination) }
725
+ it { should_not equal frame }
726
+
727
+ describe '#min' do
728
+ subject { super().min }
729
+ it { should eq destination }
730
+ end
731
+
732
+ describe '#max' do
733
+ subject { super().max }
734
+ it { should eq destination + duration }
735
+ end
736
+ end
737
+
738
+ context 'when shifting to same time' do
739
+ let(:destination) { min }
740
+ subject { frame.shift_to(destination) }
741
+ it { should_not equal frame }
742
+
743
+ describe '#min' do
744
+ subject { super().min }
745
+ it { should eq destination }
746
+ end
747
+
748
+ describe '#max' do
749
+ subject { super().max }
750
+ it { should eq destination + duration }
751
+ end
752
+ end
753
+ end
754
+
755
+ describe '#without' do
756
+ context 'when providing a single frame' do
757
+ let(:frame) { TimeFrame.new(min: time, duration: 1.hour) }
758
+
759
+ context 'and other is left of self' do
760
+ context 'and they have a common border' do
761
+ let(:other) { frame.shift_by(-frame.duration) }
762
+ subject { frame.without(other) }
763
+ it { should eq [frame] }
764
+ end
765
+ context 'and they do not have a common border' do
766
+ let(:other) { frame.shift_by(-2 * frame.duration) }
767
+ subject { frame.without(other) }
768
+ it { should eq [frame] }
769
+ end
770
+ context 'and they overlap' do
771
+ let(:other) { frame.shift_by(-0.5 * frame.duration) }
772
+ subject { frame.without(other) }
773
+ it { should eq [TimeFrame.new(min: other.max, max: frame.max)] }
774
+ end
775
+ end
776
+
777
+ context 'and other is right of self' do
778
+ context 'and they have a common border' do
779
+ let(:other) { frame.shift_by(frame.duration) }
780
+ subject { frame.without(other) }
781
+ it { should eq [frame] }
782
+ end
783
+ context 'and they do not have a common border' do
784
+ let(:other) { frame.shift_by(2 * frame.duration) }
785
+ subject { frame.without(other) }
786
+ it { should eq [frame] }
787
+ end
788
+ context 'and they overlap' do
789
+ let(:other) { frame.shift_by(0.5 * frame.duration) }
790
+ subject { frame.without(other) }
791
+ it { should eq [TimeFrame.new(min: frame.min, max: other.min)] }
792
+ end
793
+ end
794
+
795
+ context 'and other is contained within self' do
796
+ context 'and other is equal to self' do
797
+ subject { frame.without(frame) }
798
+ it { should eq [] }
799
+ end
800
+ context 'and only left boundaries are equal' do
801
+ let(:other) do
802
+ TimeFrame.new(min: time, duration: frame.duration / 2)
803
+ end
804
+ subject { frame.without(other) }
805
+ it { should eq [TimeFrame.new(min: other.max, max: frame.max)] }
806
+ end
807
+ context 'and only right boundaries are equal' do
808
+ let(:other) do
809
+ TimeFrame.new(min: time + frame.duration / 2, max: frame.max)
810
+ end
811
+ subject { frame.without(other) }
812
+ it { should eq [TimeFrame.new(min: frame.min, max: other.min)] }
813
+ end
814
+ context 'and they have no boundary in common' do
815
+ let(:other) do
816
+ TimeFrame.new(min: time + frame.duration / 3,
817
+ duration: frame.duration / 3)
818
+ end
819
+ subject { frame.without(other) }
820
+ it do
821
+ should eq [
822
+ TimeFrame.new(min: frame.min, max: other.min),
823
+ TimeFrame.new(min: other.max, max: frame.max)
824
+ ]
825
+ end
826
+ end
827
+ end
828
+ end
829
+
830
+ context 'when providing an array' do
831
+ let(:frame) { TimeFrame.new(min: time, duration: 10.hours) }
832
+ context 'and providing one frame' do
833
+ context 'and its equal to self' do
834
+ let(:arg) { [frame] }
835
+ subject { frame.without(*arg) }
836
+ it { should eq [] }
837
+ end
838
+ end
839
+ context 'and providing several frames' do
840
+ context 'and they do not intersect' do
841
+ context 'and do not touch the boundaries' do
842
+ let(:arg) do
843
+ shift = frame.duration + 1.hour
844
+ [
845
+ TimeFrame.new(min: time - 2.hours, duration: 1.hour),
846
+ TimeFrame.new(min: time + shift, duration: 1.hour)
847
+ ]
848
+ end
849
+ subject { frame.without(*arg) }
850
+ it { should eq [frame] }
851
+ end
852
+ context 'and they touch boundaries' do
853
+ let(:arg) do
854
+ [
855
+ TimeFrame.new(min: time - 1.hour, duration: 1.hour),
856
+ TimeFrame.new(min: time + frame.duration, duration: 1.hour)
857
+ ]
858
+ end
859
+ subject { frame.without(*arg) }
860
+ it { should eq [frame] }
861
+ end
862
+ end
863
+ context 'and they intersect' do
864
+ context 'and the argument frames overlaps themself' do
865
+ let(:arg) do
866
+ [
867
+ TimeFrame.new(min: time + 1.hour, duration: 2.hours),
868
+ TimeFrame.new(min: time + 2.hours, duration: 2.hours)
869
+ ]
870
+ end
871
+ let(:expected) do
872
+ [
873
+ TimeFrame.new(min: frame.min, duration: 1.hour),
874
+ TimeFrame.new(min: time + 4.hours, max: frame.max)
875
+ ]
876
+ end
877
+ subject { frame.without(*arg) }
878
+ it { should eq expected }
879
+ end
880
+ context 'and they cover self' do
881
+ let(:arg) do
882
+ duration = 0.5 * frame.duration
883
+ [
884
+ TimeFrame.new(min: time, duration: duration),
885
+ TimeFrame.new(min: time + duration, duration: duration)
886
+ ]
887
+ end
888
+ subject { frame.without(*arg) }
889
+ it { should eq [] }
890
+ end
891
+ context 'and they overlap at the boundaries' do
892
+ let(:arg) do
893
+ shift = frame.duration - 1.hour
894
+ [
895
+ TimeFrame.new(min: time - 1.hour, duration: 2.hour),
896
+ TimeFrame.new(min: time + shift, duration: 2.hour)
897
+ ]
898
+ end
899
+ let(:expected) do
900
+ [
901
+ TimeFrame.new(min: frame.min + 1.hour,
902
+ max: frame.max - 1.hour)
903
+ ]
904
+ end
905
+ subject { frame.without(*arg) }
906
+ it { should eq expected }
907
+ end
908
+ context 'and we have three frames in args overlaped by self' do
909
+ context 'which are sorted' do
910
+ let(:arg) do
911
+ [
912
+ TimeFrame.new(min: time + 1.hour, duration: 2.hour),
913
+ TimeFrame.new(min: time + 4.hours, duration: 2.hour),
914
+ TimeFrame.new(min: time + 7.hours, duration: 2.hour)
915
+ ]
916
+ end
917
+ let(:expected) do
918
+ [
919
+ TimeFrame.new(min: time, max: time + 1.hour),
920
+ TimeFrame.new(min: time + 3.hours, max: time + 4.hour),
921
+ TimeFrame.new(min: time + 6.hours, max: time + 7.hours),
922
+ TimeFrame.new(min: time + 9.hours, max: time + 10.hours)
923
+ ]
924
+ end
925
+ subject { frame.without(*arg) }
926
+ it { should eq expected }
927
+ end
928
+ context 'and they are unsorted' do
929
+ let(:arg) do
930
+ [
931
+ TimeFrame.new(min: time + 4.hours, duration: 2.hour),
932
+ TimeFrame.new(min: time + 1.hour, duration: 2.hour),
933
+ TimeFrame.new(min: time + 7.hours, duration: 2.hour)
934
+ ]
935
+ end
936
+ let(:expected) do
937
+ [
938
+ TimeFrame.new(min: time, max: time + 1.hour),
939
+ TimeFrame.new(min: time + 3.hours, max: time + 4.hour),
940
+ TimeFrame.new(min: time + 6.hours, max: time + 7.hours),
941
+ TimeFrame.new(min: time + 9.hours, max: time + 10.hours)
942
+ ]
943
+ end
944
+ subject { frame.without(*arg) }
945
+ it { should eq expected }
946
+ end
947
+ end
948
+ end
949
+ end
950
+ end
951
+ end
952
+
953
+ describe '.covering_time_frame_for' do
954
+
955
+ context 'for an empty array' do
956
+ subject { TimeFrame.covering_time_frame_for([]) }
957
+ it { should be_nil }
958
+ end
959
+
960
+ context 'for a single time frame' do
961
+ let(:frame) { TimeFrame.new(min: time, duration: 1.hour) }
962
+ subject { TimeFrame.covering_time_frame_for([frame]) }
963
+ it { should eq frame }
964
+ end
965
+
966
+ context 'for multiple time frames' do
967
+ let(:frame1) { TimeFrame.new(min: time, duration: 2.hours) }
968
+ let(:frame2) { frame1.shift_by(-1.hour) }
969
+ let(:frame3) { frame1.shift_by(3.hours) }
970
+ subject do
971
+ TimeFrame.covering_time_frame_for([frame1, frame2, frame3])
972
+ end
973
+
974
+ describe '#min' do
975
+ subject { super().min }
976
+ it { should eq frame2.min }
977
+ end
978
+
979
+ describe '#max' do
980
+ subject { super().max }
981
+ it { should eq frame3.max }
982
+ end
983
+ end
984
+ end
985
+
986
+ describe '.each_overlap' do
987
+
988
+ # Visualization of example input:
989
+ #
990
+ # array1: |---|-------| |-------|-----------|
991
+ # array2: |-----------| |---| |---| |---|
992
+ #
993
+ # 0 1 2 3 4 5 6 7 8 9 10 11
994
+
995
+ let(:array1) do
996
+ [
997
+ TimeFrame.new(min: time, max: time + 1.hour),
998
+ TimeFrame.new(min: time + 1.hour, max: time + 3.hours),
999
+ TimeFrame.new(min: time + 4.hours, max: time + 6.hours),
1000
+ TimeFrame.new(min: time + 6.hours, max: time + 9.hours)
1001
+ ]
1002
+ end
1003
+
1004
+ let(:array2) do
1005
+ [
1006
+ TimeFrame.new(min: time + 2.hours, max: time + 5.hour),
1007
+ TimeFrame.new(min: time + 6.hour, max: time + 7.hours),
1008
+ TimeFrame.new(min: time + 8.hours, max: time + 9.hours),
1009
+ TimeFrame.new(min: time + 10.hours, max: time + 11.hours)
1010
+ ]
1011
+ end
1012
+
1013
+ it 'yields the block for each overlap' do
1014
+ overlaps = []
1015
+ TimeFrame.each_overlap(array1, array2) { |a, b| overlaps << [a, b] }
1016
+ expect(overlaps).to eq [
1017
+ [array1[1], array2[0]],
1018
+ [array1[2], array2[0]],
1019
+ [array1[3], array2[1]],
1020
+ [array1[3], array2[2]]
1021
+ ]
1022
+ end
1023
+
1024
+ it 'still works when switching arguments' do
1025
+ overlaps = []
1026
+ TimeFrame.each_overlap(array2, array1) { |a, b| overlaps << [a, b] }
1027
+ expect(overlaps).to eq [
1028
+ [array2[0], array1[1]],
1029
+ [array2[0], array1[2]],
1030
+ [array2[1], array1[3]],
1031
+ [array2[2], array1[3]]
1032
+ ]
1033
+ end
1034
+
1035
+ it 'works if first array is empty' do
1036
+ overlaps = []
1037
+ TimeFrame.each_overlap([], array2) { |a, b| overlaps << [a, b] }
1038
+ expect(overlaps).to be_empty
1039
+ end
1040
+
1041
+ it 'works if second array is empty' do
1042
+ overlaps = []
1043
+ TimeFrame.each_overlap(array1, []) { |a, b| overlaps << [a, b] }
1044
+ expect(overlaps).to be_empty
1045
+ end
1046
+ end
1047
+
1048
+ describe '#inspect' do
1049
+ it 'works for a TimeFrame with same min and max' do
1050
+ time = Time.now
1051
+ expected = "#{time}..#{time}"
1052
+ tr = TimeFrame.new(min: time, max: time)
1053
+ actual = tr.inspect
1054
+ expect(actual).to eq expected
1055
+ end
1056
+
1057
+ it 'works for a TimeFrame created with min and max' do
1058
+ min = Time.now
1059
+ max = min + 10.minutes
1060
+ expected = "#{min}..#{max}"
1061
+ tr = TimeFrame.new(min: min, max: max)
1062
+ actual = tr.inspect
1063
+ expect(actual).to eq expected
1064
+ end
1065
+
1066
+ it 'works for a TimeFrame created with min and duration' do
1067
+ min = Time.now
1068
+ max = min + 10.minutes
1069
+ expected = "#{min}..#{max}"
1070
+ tr = TimeFrame.new(min: min, duration: 10.minutes)
1071
+ actual = tr.inspect
1072
+ expect(actual).to eq expected
1073
+ end
1074
+ end
1075
+ end