time_frame 0.0.1

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.
@@ -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