duranged 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,3 @@
1
+ module Duranged
2
+ VERSION = '0.0.1'
3
+ end
@@ -0,0 +1,6 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '..', 'shared/base_examples.rb')
3
+
4
+ RSpec.describe Duranged::Base do
5
+ it_behaves_like "the base class", Duranged::Base
6
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '..', 'shared/base_examples.rb')
3
+
4
+ RSpec.describe Duranged::Duration do
5
+ it_behaves_like "the base class", Duranged::Duration
6
+
7
+ describe '#duration' do
8
+ subject { Duranged::Duration.new(30.seconds) }
9
+
10
+ it 'returns the duration value' do
11
+ expect(subject.duration).to eq(subject.value)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+ require File.join(File.dirname(__FILE__), '..', 'shared/base_examples.rb')
3
+
4
+ RSpec.describe Duranged::Interval do
5
+ it_behaves_like "the base class", Duranged::Interval
6
+
7
+ describe '#interval' do
8
+ subject { Duranged::Interval.new(30.seconds) }
9
+
10
+ it 'returns the interval value' do
11
+ expect(subject.interval).to eq(subject.value)
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,245 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Duranged::Occurrence do
4
+ subject { Duranged::Occurrence.new(5, 36.hours) }
5
+
6
+ describe 'dump' do
7
+ it 'dumps the occurrence as a JSON hash' do
8
+ expect(Duranged::Occurrence.dump(subject)).to eq subject.to_json
9
+ end
10
+ end
11
+
12
+ describe 'load' do
13
+ context 'without a range' do
14
+ it 'creates a new occurrence from a JSON hash' do
15
+ expect(Duranged::Occurrence.load(subject.to_json)).to be_an_instance_of Duranged::Occurrence
16
+ expect(Duranged::Occurrence.load(subject.to_json).as_json).to eq subject.as_json
17
+ end
18
+ end
19
+
20
+ context 'with a range' do
21
+ subject { Duranged::Occurrence.new(5, 36.hours, 10.minutes, Time.now, Time.now + 3.days) }
22
+
23
+ it 'creates a new occurrence from a JSON hash' do
24
+ expect(Duranged::Occurrence.load(subject.to_json)).to be_an_instance_of Duranged::Occurrence
25
+ expect(Duranged::Occurrence.load(subject.to_json).as_json).to eq subject.as_json
26
+ end
27
+ end
28
+ end
29
+
30
+ describe '#initialize' do
31
+ context 'with occurrences' do
32
+ it 'sets occurrences' do
33
+ expect(subject.occurrences).to eq 5
34
+ end
35
+ end
36
+
37
+ context 'without occurrences' do
38
+ subject { Duranged::Occurrence.new }
39
+
40
+ it 'sets occurrences to 1' do
41
+ expect(subject.occurrences).to eq 1
42
+ end
43
+ end
44
+
45
+ context 'with interval' do
46
+ it 'sets interval' do
47
+ expect(subject.interval).to eq 36.hours
48
+ end
49
+ end
50
+
51
+ context 'without interval' do
52
+ subject { Duranged::Occurrence.new }
53
+
54
+ it 'sets interval to 0' do
55
+ expect(subject.interval).to eq nil
56
+ end
57
+ end
58
+
59
+ context 'with duration' do
60
+ subject { Duranged::Occurrence.new(5, 36.hours, 20.minutes) }
61
+
62
+ it 'sets duration' do
63
+ expect(subject.duration).to eq 20.minutes
64
+ end
65
+ end
66
+
67
+ context 'without duration' do
68
+ subject { Duranged::Occurrence.new }
69
+
70
+ it 'sets duration to 0' do
71
+ expect(subject.duration).to eq nil
72
+ end
73
+ end
74
+ end
75
+
76
+ describe '#occurrences_string' do
77
+ context 'with zero occurrences' do
78
+ subject { Duranged::Occurrence.new(0, 1.minute) }
79
+
80
+ it "returns 'never'" do
81
+ expect(subject.occurrences_string).to eq 'never'
82
+ end
83
+ end
84
+
85
+ context 'with a single occurrence' do
86
+ subject { Duranged::Occurrence.new(1, 1.minute) }
87
+
88
+ it "returns 'once'" do
89
+ expect(subject.occurrences_string).to eq 'once'
90
+ end
91
+ end
92
+
93
+ context 'with two occurrences' do
94
+ subject { Duranged::Occurrence.new(2, 1.minute) }
95
+
96
+ it "returns 'twice'" do
97
+ expect(subject.occurrences_string).to eq 'twice'
98
+ end
99
+ end
100
+
101
+ context 'with multiple occurrences' do
102
+ subject { Duranged::Occurrence.new([3,4,5].sample, 1.minute) }
103
+
104
+ it "returns 'X times'" do
105
+ expect(subject.occurrences_string).to eq "#{subject.occurrences} times"
106
+ end
107
+ end
108
+ end
109
+
110
+ describe '#as_json' do
111
+ context 'without a range' do
112
+ it 'returns an occurrences hash' do
113
+ expect(subject.as_json).to eq({occurrences: subject.occurrences, interval: subject.interval.as_json})
114
+ end
115
+ end
116
+
117
+ context 'with a range' do
118
+ subject { Duranged::Occurrence.new(5, 36.hours, 10.minutes, Time.now, Time.now + 3.days) }
119
+
120
+ it 'returns an occurrences hash' do
121
+ expect(subject.as_json).to eq({occurrences: subject.occurrences, duration: subject.duration.as_json, interval: subject.interval.as_json, range: subject.range.as_json})
122
+ end
123
+ end
124
+ end
125
+
126
+ describe '#to_s' do
127
+ subject { Duranged::Occurrence.new(3, 1.day, 10.minutes) }
128
+
129
+ it "returns a string matching 'OCCURRENCES for DURATION every INTERVAL'" do
130
+ expect(subject.to_s).to eq "#{subject.occurrences_string} for #{subject.duration.to_s} every #{subject.interval.to_s}"
131
+ end
132
+
133
+ context 'when occurrences is 0' do
134
+ subject { Duranged::Occurrence.new(0, 30.seconds) }
135
+
136
+ it 'returns the occurrences_string' do
137
+ expect(subject.to_s).to eq subject.occurrences_string
138
+ end
139
+
140
+ it "returns 'never'" do
141
+ expect(subject.to_s).to eq 'never'
142
+ end
143
+ end
144
+
145
+ context 'when interval is 0' do
146
+ subject { Duranged::Occurrence.new(3, 0, 30.seconds) }
147
+
148
+ it "returns a string matching 'OCCURRENCES for DURATION'" do
149
+ expect(subject.to_s).to eq "#{subject.occurrences_string} for #{subject.duration.to_s}"
150
+ end
151
+
152
+ context 'when duration is 0' do
153
+ subject { Duranged::Occurrence.new(3) }
154
+
155
+ it 'returns the occurrences string' do
156
+ expect(subject.to_s).to eq subject.occurrences_string
157
+ end
158
+ end
159
+ end
160
+
161
+ context 'when duration is 0' do
162
+ subject { Duranged::Occurrence.new(3, 1.day) }
163
+
164
+ it "returns a string matching 'OCCURRENCES every INTERVAL'" do
165
+ expect(subject.to_s).to eq "#{subject.occurrences_string} every #{subject.interval.to_s}"
166
+ end
167
+
168
+ context 'when interval is 0' do
169
+ subject { Duranged::Occurrence.new(3) }
170
+
171
+ it 'returns the occurrences string' do
172
+ expect(subject.to_s).to eq subject.occurrences_string
173
+ end
174
+ end
175
+ end
176
+ end
177
+
178
+ describe '#strfocc' do
179
+ context 'using the :occurence token' do
180
+ it 'returns the formatted string' do
181
+ expect(subject.strfocc(':occurrence')).to eq subject.occurrences.to_s
182
+ end
183
+ end
184
+
185
+ context 'using the :occurences token' do
186
+ it 'returns the formatted string' do
187
+ expect(subject.strfocc(':occurrences')).to eq subject.occurrences.to_s
188
+ end
189
+ end
190
+
191
+ context 'using the :duration token' do
192
+ subject { Duranged::Occurrence.new(5, 10.minutes, 30.seconds) }
193
+
194
+ it 'requires nested formatters' do
195
+ expect(subject.strfocc(':duration')).to eq ':duration'
196
+ end
197
+
198
+ it 'returns the formatted string' do
199
+ expect(subject.strfocc(':duration(%d:%h:%m:%s)')).to eq subject.duration.strfdur('%d:%h:%m:%s')
200
+ end
201
+ end
202
+
203
+ context 'using the :interval token' do
204
+ it 'requires nested formatters' do
205
+ expect(subject.strfocc(':interval')).to eq ':interval'
206
+ end
207
+
208
+ it 'returns the formatted string' do
209
+ expect(subject.strfocc(':interval(%d:%h:%m:%s)')).to eq subject.interval.strfdur('%d:%h:%m:%s')
210
+ end
211
+ end
212
+
213
+ context 'using the :range token' do
214
+ subject { Duranged::Occurrence.new(5, 10.minutes, 30.seconds, Time.now, Time.now + 1.hour) }
215
+
216
+ context 'without nested formatters' do
217
+ it 'returns the default formatted range string' do
218
+ expect(subject.strfocc(':range')).to eq subject.range.to_s
219
+ end
220
+ end
221
+
222
+ context 'with nested formatters' do
223
+ it 'returns the formatted string' do
224
+ expect(subject.strfocc(':range(:start_at(%l:%M%P) :end_at(%l:%M%P) :duration(%D%H%M%S))')).to eq "#{subject.range.start_at('%l:%M%P')} #{subject.range.end_at('%l:%M%P')} #{subject.range.strfdur('%D%H%M%S')}"
225
+ end
226
+ end
227
+ end
228
+
229
+ context "using complex format string ':occurrences times for :duration(%-s) seconds every :interval(%-m) minutes between :range(:start_at(%l:%M%P) and :end_at(%l:%M%P) (:duration(%-m minutes)))'" do
230
+ subject { Duranged::Occurrence.new(3, 5.minutes, 30.seconds, DateTime.parse('2015-01-01T06:00:00-07:00'), DateTime.parse('2015-01-01T06:20:00-07:00')) }
231
+
232
+ it "returns '3 times for 30 seconds every 5 minutes between 6:00am and 6:20am'" do
233
+ expect(subject.strfocc(':occurrences times for :duration(%-s) seconds every :interval(%-m) minutes between :range(:start_at(%l:%M%P) and :end_at(%l:%M%P) (:duration(%-m minutes)))')).to eq '3 times for 30 seconds every 5 minutes between 6:00am and 6:20am (20 minutes)'
234
+ end
235
+ end
236
+
237
+ context "using complex format string 'between :range(:start_at(%l:%M%P) and :end_at(%l:%M%P) (:duration(%-m minutes))), do something :occurrences times for :duration(%-s) seconds every :interval(%-m) minutes'" do
238
+ subject { Duranged::Occurrence.new(3, 5.minutes, 30.seconds, DateTime.parse('2015-01-01T06:00:00-07:00'), DateTime.parse('2015-01-01T06:20:00-07:00')) }
239
+
240
+ it "returns 'between 6:00am and 6:20am (20 minutes), do something 3 times for 30 seconds every 5 minutes'" do
241
+ expect(subject.strfocc('between :range(:start_at(%l:%M%P) and :end_at(%l:%M%P) (:duration(%-m minutes))), do something :occurrences times for :duration(%-s) seconds every :interval(%-m) minutes')).to eq 'between 6:00am and 6:20am (20 minutes), do something 3 times for 30 seconds every 5 minutes'
242
+ end
243
+ end
244
+ end
245
+ end
@@ -0,0 +1,324 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.shared_examples "a format token" do |token, format, meth|
4
+ it 'requires nested formatters' do
5
+ expect(subject.strfrange("#{token}")).to eq "#{token}"
6
+ end
7
+
8
+ it "returns the formatted #{token}" do
9
+ expect(subject.strfrange("#{token}(#{format})")).to eq subject.send(meth, format)
10
+ end
11
+ end
12
+
13
+ RSpec.describe Duranged::Range do
14
+ now = Time.now
15
+ subject { Duranged::Range.new(now, 60.minutes) }
16
+
17
+ describe 'dump' do
18
+ it 'dumps the time range as a JSON hash' do
19
+ expect(Duranged::Range.dump(subject)).to eq subject.to_json
20
+ end
21
+ end
22
+
23
+ describe 'load' do
24
+ it 'creates a new time range from a JSON hash' do
25
+ expect(Duranged::Range.load(subject.to_json)).to be_an_instance_of Duranged::Range
26
+ expect(Duranged::Range.load(subject.to_json).as_json).to eq subject.as_json
27
+ end
28
+ end
29
+
30
+ describe '#initialize' do
31
+ context 'with a start_at and end_at' do
32
+ subject { Duranged::Range.new(now, (now + 60.minutes)) }
33
+
34
+ it 'sets start_at' do
35
+ expect(subject.start_at).to eq now
36
+ end
37
+
38
+ it 'sets end_at' do
39
+ expect(subject.end_at).to eq(now + 60.minutes)
40
+ end
41
+ end
42
+
43
+ context 'with a start_at and duration' do
44
+ subject { Duranged::Range.new(now, 60.minutes) }
45
+
46
+ it 'sets start_at' do
47
+ expect(subject.start_at).to eq now
48
+ end
49
+
50
+ context 'when the duration is an ActiveSupport::Duration' do
51
+ subject { Duranged::Range.new(now, 60.minutes) }
52
+
53
+ it 'sets end_at to start_at + duration' do
54
+ expect(subject.end_at).to eq(now + 60.minutes)
55
+ end
56
+ end
57
+
58
+ context 'when the duration is an integer' do
59
+ subject { Duranged::Range.new(now, 3600) }
60
+
61
+ it 'sets end_at to start_at + duration' do
62
+ expect(subject.end_at).to eq(now + 60.minutes)
63
+ end
64
+ end
65
+
66
+ context 'when the duration is a hash' do
67
+ subject { Duranged::Range.new(now, {minutes: 60}) }
68
+
69
+ it 'sets end_at to start_at + duration' do
70
+ expect(subject.end_at).to eq(now + 60.minutes)
71
+ end
72
+ end
73
+
74
+ context 'when the duration is a string' do
75
+ subject { Duranged::Range.new(now, "1 day, 2 hours and 30 minutes") }
76
+
77
+ it 'sets end_at to start_at + duration' do
78
+ expect(subject.end_at).to eq(now + (26.hours + 30.minutes))
79
+ end
80
+ end
81
+ end
82
+
83
+ context 'with just a duration' do
84
+ subject { Duranged::Range.new(60.minutes) }
85
+
86
+ it 'sets start_at to now' do
87
+ expect(subject.start_at).to be_between((Time.now - 1.seconds), Time.now)
88
+ end
89
+
90
+ context 'when the duration is an ActiveSupport::Duration' do
91
+ subject { Duranged::Range.new(60.minutes) }
92
+
93
+ it 'sets end_at to start_at + duration' do
94
+ expect(subject.end_at).to eq(subject.start_at + 60.minutes)
95
+ end
96
+ end
97
+
98
+ context 'when the duration is an integer' do
99
+ subject { Duranged::Range.new(3600) }
100
+
101
+ it 'sets end_at to start_at + duration' do
102
+ expect(subject.end_at).to eq(subject.start_at + 60.minutes)
103
+ end
104
+ end
105
+
106
+ context 'when the duration is a hash' do
107
+ subject { Duranged::Range.new({minutes: 60}) }
108
+
109
+ it 'sets end_at to start_at + duration' do
110
+ expect(subject.end_at).to eq(subject.start_at + 60.minutes)
111
+ end
112
+ end
113
+ end
114
+
115
+ context 'with just an end_at' do
116
+ subject { Duranged::Range.new(now + 60.minutes) }
117
+
118
+ it 'sets start_at to now' do
119
+ expect(subject.start_at).to be_between((Time.now - 1.seconds), Time.now)
120
+ end
121
+
122
+ it 'sets end_at' do
123
+ expect(subject.end_at).to eq(now + 60.minutes)
124
+ end
125
+ end
126
+ end
127
+
128
+ describe '#start_at' do
129
+ subject { Duranged::Range.new(now, 60.minutes) }
130
+
131
+ context 'without a format argument' do
132
+ it 'returns the start_at DateTime object' do
133
+ expect(subject.start_at).to eq now
134
+ expect(subject.start_at).to be_an_instance_of DateTime
135
+ end
136
+ end
137
+
138
+ context 'with a format argument' do
139
+ it 'returns a formatted string' do
140
+ expect(subject.start_at('%A, %b %e, %Y %l:%M%P')).to eq now.strftime('%A, %b %e, %Y %l:%M%P')
141
+ end
142
+ end
143
+ end
144
+
145
+ describe '#end_at' do
146
+ subject { Duranged::Range.new(now, 60.minutes) }
147
+
148
+ context 'without a format argument' do
149
+ it 'returns the end_at DateTime object' do
150
+ expect(subject.end_at).to eq(now + 60.minutes)
151
+ expect(subject.end_at).to be_an_instance_of DateTime
152
+ end
153
+ end
154
+
155
+ context 'with a format argument' do
156
+ it 'returns a formatted string' do
157
+ expect(subject.end_at('%A, %b %e, %Y %l:%M%P')).to eq((now + 60.minutes).strftime('%A, %b %e, %Y %l:%M%P'))
158
+ end
159
+ end
160
+ end
161
+
162
+ describe '#+' do
163
+ context 'when passed an integer' do
164
+ it 'returns an instance of the same class' do
165
+ expect(subject + 20).to be_an_instance_of subject.class
166
+ end
167
+
168
+ it 'adds the integer to the value' do
169
+ expect((subject + 20).value).to eq (subject.value + 20)
170
+ end
171
+
172
+ it 'recalculates the end_at' do
173
+ end_at = subject.end_at.to_i
174
+ expect((subject + 20).end_at.to_i).to eq (end_at + 20)
175
+ end
176
+ end
177
+
178
+ context 'when passed a duration' do
179
+ it 'returns an instance of the same class' do
180
+ expect(subject + Duranged::Duration.new(20)).to be_an_instance_of subject.class
181
+ end
182
+
183
+ it 'adds the duration to the value' do
184
+ expect((subject + Duranged::Duration.new(20)).value).to eq (subject.value + 20)
185
+ end
186
+
187
+ it 'recalculates the end_at' do
188
+ end_at = subject.end_at.to_i
189
+ expect((subject + Duranged::Duration.new(20)).end_at.to_i).to eq (end_at + 20)
190
+ end
191
+ end
192
+
193
+ context 'when passed an interval' do
194
+ it 'returns an instance of the same class' do
195
+ expect(subject + Duranged::Interval.new(20)).to be_an_instance_of subject.class
196
+ end
197
+
198
+ it 'adds the interval to the value' do
199
+ expect((subject + Duranged::Interval.new(20)).value).to eq (subject.value + 20)
200
+ end
201
+
202
+ it 'recalculates the end_at' do
203
+ end_at = subject.end_at.to_i
204
+ expect((subject + Duranged::Interval.new(20)).end_at.to_i).to eq (end_at + 20)
205
+ end
206
+ end
207
+ end
208
+
209
+ describe '#-' do
210
+ context 'when passed an integer' do
211
+ it 'returns an instance of the same class' do
212
+ expect(subject - 20).to be_an_instance_of subject.class
213
+ end
214
+
215
+ it 'adds the integer to the value' do
216
+ expect((subject - 20).value).to eq (subject.value - 20)
217
+ end
218
+
219
+ it 'recalculates the end_at' do
220
+ end_at = subject.end_at.to_i
221
+ expect((subject - 20).end_at.to_i).to eq (end_at - 20)
222
+ end
223
+ end
224
+
225
+ context 'when passed a duration' do
226
+ it 'returns an instance of the same class' do
227
+ expect(subject - Duranged::Duration.new(20)).to be_an_instance_of subject.class
228
+ end
229
+
230
+ it 'adds the duration to the value' do
231
+ expect((subject - Duranged::Duration.new(20)).value).to eq (subject.value - 20)
232
+ end
233
+
234
+ it 'recalculates the end_at' do
235
+ end_at = subject.end_at.to_i
236
+ expect((subject - Duranged::Duration.new(20)).end_at.to_i).to eq (end_at - 20)
237
+ end
238
+ end
239
+
240
+ context 'when passed an interval' do
241
+ it 'returns an instance of the same class' do
242
+ expect(subject - Duranged::Interval.new(20)).to be_an_instance_of subject.class
243
+ end
244
+
245
+ it 'adds the interval to the value' do
246
+ expect((subject - Duranged::Interval.new(20)).value).to eq (subject.value - 20)
247
+ end
248
+
249
+ it 'recalculates the end_at' do
250
+ end_at = subject.end_at.to_i
251
+ expect((subject - Duranged::Interval.new(20)).end_at.to_i).to eq (end_at - 20)
252
+ end
253
+ end
254
+ end
255
+
256
+ describe '#to_h' do
257
+ it 'returns a hash with start_at and end_at' do
258
+ expect(subject.to_h).to eq({start_at: subject.start_at.as_json, end_at: subject.end_at.as_json})
259
+ end
260
+ end
261
+
262
+ describe '#as_json' do
263
+ it 'returns a hash with start_at and end_at' do
264
+ expect(subject.as_json).to eq({start_at: subject.start_at.as_json, end_at: subject.end_at.as_json})
265
+ end
266
+ end
267
+
268
+ describe '#to_duration' do
269
+ it 'returns a duration object' do
270
+ expect(subject.to_duration).to be_an_instance_of Duranged::Duration
271
+ end
272
+
273
+ it 'returns a duration object that represents the duration value' do
274
+ expect(subject.to_duration.duration).to eq subject.duration
275
+ end
276
+ end
277
+
278
+ describe '#to_s' do
279
+ subject { Duranged::Range.new((now.beginning_of_day), 60.minutes) }
280
+ let(:format) { '%-l:%M%P' }
281
+
282
+ it 'uses the default format' do
283
+ expect(subject.to_s).to eq "#{subject.start_at('%b. %-d %Y ' + format)} to #{subject.end_at(format)}"
284
+ end
285
+
286
+ context 'when start and end fall on the same day' do
287
+ subject { Duranged::Range.new((now.beginning_of_day + 30.minutes), (now.beginning_of_day + 5.hours)) }
288
+
289
+ it 'returns the start and end times' do
290
+ expect(subject.to_s).to eq "#{subject.start_at('%b. %-d %Y ' + format)} to #{subject.end_at(format)}"
291
+ end
292
+ end
293
+
294
+ context 'when start and end fall on different days' do
295
+ subject { Duranged::Range.new((now.utc.end_of_day - 20.minutes), 30.minutes) }
296
+ let(:format) { '%b. %-d %Y %-l:%M%P' }
297
+
298
+ it 'returns the start date/time and duration' do
299
+ expect(subject.to_s).to eq "#{subject.start_at(format)} (#{subject.to_duration.to_s})"
300
+ end
301
+ end
302
+ end
303
+
304
+ describe '#strfrange' do
305
+ context 'when using the :start_at token' do
306
+ it_behaves_like "a format token", ':start_at', '%b. %-d %Y %-l:%M%P', :start_at
307
+ end
308
+
309
+ context 'when using the :end_at token' do
310
+ it_behaves_like "a format token", ':end_at', '%b. %-d %Y %-l:%M%P', :end_at
311
+ end
312
+
313
+ context 'when using the :duration token' do
314
+ it_behaves_like "a format token", ':duration', '%D:%H:%M:%S', :strfdur
315
+ end
316
+
317
+ context "using complex format string ':start_at(%b. %-d %Y) :start_at(%l:%M%P) - :end_at(%l:%M%P) (:duration(%-h hours, %-m minutes))'" do
318
+ subject { Duranged::Range.new(DateTime.parse('2015-01-01T00:00:00-07:00'), DateTime.parse('2015-01-01T06:30:00-07:00')) }
319
+ it "returns 'Jan. 1 2015 12:00am - 6:30am (6 hours, 30 minutes)'" do
320
+ expect(subject.strfrange(':start_at(%b. %-d %Y) :start_at(%l:%M%P) - :end_at(%l:%M%P) (:duration(%-h hours, %-m minutes))')).to eq 'Jan. 1 2015 12:00am - 6:30am (6 hours, 30 minutes)'
321
+ end
322
+ end
323
+ end
324
+ end