timerage 2.2.0 → 2.3.0
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.
- checksums.yaml +4 -4
- data/lib/timerage/time_interval.rb +37 -10
- data/lib/timerage/version.rb +1 -1
- data/lib/timerage.rb +16 -3
- data/spec/timerage/time_interval_spec.rb +27 -13
- data/spec/timerage_spec.rb +16 -0
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d21955bc8e1453cafb40e1de151e67835964f857728eaa1cf001a7c70a79a1fb
|
4
|
+
data.tar.gz: efc6d71d6fbc442b2fc3fa2066657cc51bc669549ad52a576ef09f14d29aa171
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 58064fee1d615adf865a0de63f0db64d7757af8b837c475d9ccf43018fd25574305250ef6ee525931ef9a7d5cdd1a69e203a13b4de22511ec9cafbc4e07a13e2
|
7
|
+
data.tar.gz: 836ed39bef1dd174aa19e90a9f049a5bff4c88a79419aaf810470a698cdee2fb44aa41f17624a95fb440ee7ed31fad4aa1fdc53ed7fad8bef20e5c00cba8bf15
|
@@ -5,11 +5,34 @@ module Timerage
|
|
5
5
|
class TimeInterval < Range
|
6
6
|
|
7
7
|
class << self
|
8
|
+
# Returns a new TimeInterval
|
9
|
+
#
|
10
|
+
# start_time - the beginning of the interval
|
11
|
+
# end_time - the end of the interval
|
12
|
+
# exclude_end - whether the end time is excluded from the interval
|
8
13
|
def new(*args)
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
14
|
+
return from_range(*args) if args.first.respond_to?(:exclude_end?)
|
15
|
+
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns a new TimeInterval
|
20
|
+
#
|
21
|
+
# time - the beginning or end of the interval
|
22
|
+
# duration - the duration of the interval, if negative the
|
23
|
+
# interval will start before`time`
|
24
|
+
# exclude_end - whether the end time is excluded from the interval
|
25
|
+
def from_time_and_duration(time, duration, exclude_end: true)
|
26
|
+
if duration >= 0
|
27
|
+
new(time, time + duration, exclude_end)
|
28
|
+
else
|
29
|
+
new(time + duration, time, exclude_end)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# Returns a new TimeInterval based on the specified range
|
34
|
+
def from_range(range)
|
35
|
+
new(range.begin, range.end, range.exclude_end?)
|
13
36
|
end
|
14
37
|
end
|
15
38
|
|
@@ -32,12 +55,12 @@ module Timerage
|
|
32
55
|
end
|
33
56
|
end
|
34
57
|
|
35
|
-
def slice(
|
36
|
-
time_enumerator(
|
37
|
-
.map{|
|
38
|
-
|
39
|
-
|
40
|
-
|
58
|
+
def slice(duration)
|
59
|
+
time_enumerator(duration)
|
60
|
+
.each_cons(2).map { |s_begin, s_end| TimeInterval.new(s_begin, s_end, exclusive_end_slice?(s_end)) }
|
61
|
+
.then do |slices|
|
62
|
+
slices << TimeInterval.new(slices.last.end, self.end, exclusive_end_slice?(slices.last.end + duration)) if slices.present?
|
63
|
+
end
|
41
64
|
end
|
42
65
|
|
43
66
|
# Return new TimeInterval that is the concatenation of self and
|
@@ -134,6 +157,10 @@ module Timerage
|
|
134
157
|
an_obj.respond_to?(:end)
|
135
158
|
end
|
136
159
|
|
160
|
+
def exclusive_end_slice?(slice_end)
|
161
|
+
!((slice_end > self.end) && !exclude_end?)
|
162
|
+
end
|
163
|
+
|
137
164
|
def time_enumerator(step)
|
138
165
|
next_offset = step * 0
|
139
166
|
|
data/lib/timerage/version.rb
CHANGED
data/lib/timerage.rb
CHANGED
@@ -16,17 +16,30 @@ module Timerage
|
|
16
16
|
Time.iso8601(str)
|
17
17
|
end
|
18
18
|
|
19
|
+
# Returns a new TimeInterval from a time and duration or two times.
|
20
|
+
#
|
21
|
+
# signature:
|
22
|
+
# Interval(begin_time, end_time, exclude_end: true)
|
23
|
+
# Interval(begin_or_end_time, duration, exclude_end: true)
|
24
|
+
def self.Interval(begin_or_end_time, end_time_or_duration, exclude_end: true)
|
25
|
+
if end_time_or_duration.respond_to?(:since)
|
26
|
+
TimeInterval.from_time_and_duration(begin_or_end_time, end_time_or_duration, exclude_end: exclude_end)
|
27
|
+
else
|
28
|
+
TimeInterval.new(begin_or_end_time, end_time_or_duration, exclude_end)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
19
32
|
refine Range do
|
20
33
|
def step(n, &blk)
|
21
34
|
if self.begin.kind_of?(Time) || self.begin.kind_of?(Date)
|
22
|
-
Timerage::TimeInterval.
|
35
|
+
Timerage::TimeInterval.from_range(self).step(n, &blk)
|
23
36
|
else
|
24
37
|
super
|
25
38
|
end
|
26
39
|
end
|
27
40
|
|
28
41
|
def to_time_interval
|
29
|
-
Timerage::TimeInterval.
|
42
|
+
Timerage::TimeInterval.from_range(self)
|
30
43
|
end
|
31
44
|
end
|
32
45
|
end
|
@@ -40,7 +53,7 @@ module Kernel
|
|
40
53
|
thing
|
41
54
|
|
42
55
|
when ->(x) { x.respond_to? :exclude_end? }
|
43
|
-
Timerage::TimeInterval.
|
56
|
+
Timerage::TimeInterval.from_range(thing)
|
44
57
|
|
45
58
|
when ->(x) { x.respond_to? :to_str }
|
46
59
|
Timerage.parse_iso8601(thing.to_str)
|
@@ -6,10 +6,16 @@ describe Timerage::TimeInterval do
|
|
6
6
|
let(:duration) { 3600 }
|
7
7
|
|
8
8
|
describe "creation" do
|
9
|
-
specify { expect(described_class.new(now-1
|
10
|
-
|
11
|
-
specify { expect(described_class.new(now-1, now, true))
|
12
|
-
|
9
|
+
specify { expect(described_class.new(now-1.day, now))
|
10
|
+
.to be_kind_of(described_class).and eq now-1.day..now }
|
11
|
+
specify { expect(described_class.new(now-1.day, now, true))
|
12
|
+
.to be_kind_of(described_class).and eq now-1.day...now }
|
13
|
+
specify { expect(described_class.new(now-1.day, now, false ))
|
14
|
+
.to be_kind_of(described_class).and eq now-1.day..now }
|
15
|
+
|
16
|
+
it "support new(range) for backwards compatibility" do
|
17
|
+
expect(described_class.new((now-1.day)..now)).to eq (now-1.day)..now
|
18
|
+
end
|
13
19
|
end
|
14
20
|
|
15
21
|
describe ".iso8601" do
|
@@ -86,12 +92,12 @@ describe Timerage::TimeInterval do
|
|
86
92
|
specify { expect( interval & (interval.begin...interval.end) ).to eq interval.begin...interval.end }
|
87
93
|
specify { expect( interval & (interval.begin...interval.end-1) )
|
88
94
|
.to eq interval.begin...interval.end-1 }
|
89
|
-
specify { expect( described_class.new(interval.begin
|
90
|
-
.to eq described_class.new(interval.begin
|
95
|
+
specify { expect( described_class.new(interval.begin, interval.end-1) & interval )
|
96
|
+
.to eq described_class.new(interval.begin,interval.end-1) }
|
91
97
|
end
|
92
98
|
|
93
99
|
describe "==" do
|
94
|
-
specify { expect( described_class.
|
100
|
+
specify { expect( described_class.from_range(interval) == interval ).to be true }
|
95
101
|
specify { expect( described_class.new(interval.begin, interval.end) == interval ).to be true }
|
96
102
|
end
|
97
103
|
|
@@ -159,7 +165,7 @@ describe Timerage::TimeInterval do
|
|
159
165
|
end
|
160
166
|
|
161
167
|
context "exclusive end" do
|
162
|
-
subject(:interval) { described_class.
|
168
|
+
subject(:interval) { described_class.from_range(now-duration...now) }
|
163
169
|
|
164
170
|
specify { expect(interval.exclude_end?).to be true }
|
165
171
|
specify { expect(interval.cover? now).to be false }
|
@@ -187,29 +193,29 @@ describe Timerage::TimeInterval do
|
|
187
193
|
end
|
188
194
|
|
189
195
|
context "exclusive 0 length interval" do
|
190
|
-
subject(:interval) { described_class.
|
196
|
+
subject(:interval) { described_class.from_range(now...now) }
|
191
197
|
specify { expect{ |b| subject.step(1, &b) }.not_to yield_control }
|
192
198
|
end
|
193
199
|
|
194
200
|
context "inclusive 0 length interval" do
|
195
|
-
subject(:interval) { described_class.
|
201
|
+
subject(:interval) { described_class.from_range(now..now) }
|
196
202
|
specify { expect{ |b| subject.step(1, &b) }.to yield_control.once }
|
197
203
|
end
|
198
204
|
|
199
205
|
context "includes leap day" do
|
200
|
-
subject(:interval) { described_class.new(before_leap_day
|
206
|
+
subject(:interval) { described_class.new(before_leap_day, after_leap_day) }
|
201
207
|
specify { expect{ |b| subject.step(1.day, &b) }.to yield_control.exactly(3).times }
|
202
208
|
specify { expect{ |b| subject.step(86_400, &b) }.to yield_control.exactly(3).times }
|
203
209
|
end
|
204
210
|
|
205
211
|
context "transition into dst with explicit time zone" do
|
206
|
-
subject(:interval) { described_class.new(before_dst
|
212
|
+
subject(:interval) { described_class.new(before_dst, after_dst) }
|
207
213
|
specify { expect{ |b| subject.step(1.hour, &b) }.to yield_control.exactly(2).times }
|
208
214
|
specify { expect{ |b| subject.step(3_600, &b) }.to yield_control.exactly(2).times }
|
209
215
|
end
|
210
216
|
|
211
217
|
context "transition into dst without explicit time zone" do
|
212
|
-
subject(:interval) { described_class.new(before_dst
|
218
|
+
subject(:interval) { described_class.new(before_dst, (before_dst + 1.hour)) }
|
213
219
|
specify { expect{ |b| subject.step(1.hour, &b) }.to yield_control.exactly(2).times }
|
214
220
|
specify { expect{ |b| subject.step(3_600, &b) }.to yield_control.exactly(2).times }
|
215
221
|
end
|
@@ -230,6 +236,14 @@ describe Timerage::TimeInterval do
|
|
230
236
|
end
|
231
237
|
end
|
232
238
|
|
239
|
+
context "produces complete slices" do
|
240
|
+
it "slices a range into intervals without missing any time inbetween" do
|
241
|
+
range = Timerage(Time.parse("2023-10-31T00:00:00+00:00")...Time.parse("2024-06-30 00:00:00 UTC"))
|
242
|
+
slices = range.slice(1.month)
|
243
|
+
expect(slices.map(&:duration).sum).to eq range.duration
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
233
247
|
let(:leap_day) { Time.parse("2016-02-29 12:00:00 UTC") }
|
234
248
|
let(:before_leap_day) { leap_day - 1.day }
|
235
249
|
let(:after_leap_day) { leap_day + 1.day}
|
data/spec/timerage_spec.rb
CHANGED
@@ -17,6 +17,22 @@ describe Timerage do
|
|
17
17
|
.to be_kind_of Time }
|
18
18
|
end
|
19
19
|
|
20
|
+
describe ".Interval" do
|
21
|
+
let(:t) { Time.now }
|
22
|
+
let(:one_day) { 1.day }
|
23
|
+
|
24
|
+
specify { expect(described_class.Interval(t, 1.day)).to eq t...(t+1.day) }
|
25
|
+
specify { expect(described_class.Interval(t, 1.day, exclude_end: false)).to eq(t..(t+1.day)) }
|
26
|
+
|
27
|
+
specify { expect(described_class.Interval(t, -1.day)).to eq((t-1.day)...t) }
|
28
|
+
specify { expect(described_class.Interval(t, -1.day, exclude_end: false)).to eq((t+-1.day)..t) }
|
29
|
+
|
30
|
+
specify { expect{ described_class.Interval(t, (t+1.day)).to eq(t...(t+1.day)) } }
|
31
|
+
specify { expect{ described_class.Interval(t, (t+1.day), exclude_end: false).to eq(t..(t+1.day)) } }
|
32
|
+
|
33
|
+
specify { expect{ described_class.Interval(t..(t+1.day)).to eq(t..(t+1.day)) } }
|
34
|
+
end
|
35
|
+
|
20
36
|
context "interval include end" do
|
21
37
|
specify { expect{|b| range.step(1200, &b) }
|
22
38
|
.to yield_successive_args now-duration, now-(duration-1200), now-(duration-2400), now }
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timerage
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Peter Williams
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2024-11-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bundler
|
@@ -111,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
111
111
|
- !ruby/object:Gem::Version
|
112
112
|
version: '0'
|
113
113
|
requirements: []
|
114
|
-
rubygems_version: 3.
|
114
|
+
rubygems_version: 3.4.19
|
115
115
|
signing_key:
|
116
116
|
specification_version: 4
|
117
117
|
summary: Simple refinement to Range to allow Time or Date as arguments
|