timerage 2.2.0 → 2.4.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 +49 -12
- data/lib/timerage/version.rb +1 -1
- data/lib/timerage.rb +16 -3
- data/spec/timerage/time_interval_spec.rb +85 -19
- data/spec/timerage_spec.rb +16 -0
- metadata +3 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd5297580375d5da0f4f8e0c5565cbe0b5e87bb27e01a3f6ab7b53e322f0836b
|
|
4
|
+
data.tar.gz: 1095aa363d5652855d905dfeeecc74291cdb4399c5769ac115c2bed2ad889043
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: c8114be85281560a2d7c08fad17ab9db72809b3a992cf2fff1433e6e4fe3e04673e9fa53bfa93070c4c1b3643bad913c550ca68d11f1ff2f04c2c4b04a9f8188
|
|
7
|
+
data.tar.gz: 701cd90ba28da27218c23f0346bc15d33c63646bf0a96d8f79ff39dc6a8fe55974fc848b5bca7266bfdf4fe6709fc70448242930e6ffd284f9519b04e40da4bf
|
|
@@ -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
|
|
@@ -54,7 +77,7 @@ module Timerage
|
|
|
54
77
|
# Returns an ISO8601 interval representation of self
|
|
55
78
|
# Takes same args as Time#iso8601
|
|
56
79
|
def iso8601(*args)
|
|
57
|
-
"#{self.begin
|
|
80
|
+
"#{self.begin&.iso8601(*args) || ".."}/#{self.end&.iso8601(*args) || ".."}"
|
|
58
81
|
end
|
|
59
82
|
|
|
60
83
|
def getutc
|
|
@@ -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
|
|
|
@@ -155,7 +182,17 @@ module Timerage
|
|
|
155
182
|
#
|
|
156
183
|
# Currently this only supports `<begin>/<end>` style time intervals.
|
|
157
184
|
def self.iso8601(str, exclusive_end: true)
|
|
158
|
-
|
|
185
|
+
str
|
|
186
|
+
.split("/", 2)
|
|
187
|
+
.tap { |it| fail ArgumentError, "Invalid iso8601 interval: #{str.inspect}" unless it.size == 2 }
|
|
188
|
+
.map {|s|
|
|
189
|
+
if s.strip == ".."
|
|
190
|
+
nil
|
|
191
|
+
else
|
|
192
|
+
Time.iso8601(s)
|
|
193
|
+
end
|
|
194
|
+
}
|
|
195
|
+
.then { |b, e| new(b, e, exclusive_end) }
|
|
159
196
|
|
|
160
197
|
rescue ArgumentError
|
|
161
198
|
raise ArgumentError, "Invalid iso8601 interval: #{str.inspect}"
|
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,18 +6,64 @@ 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
|
-
describe ".iso8601" do
|
|
16
|
-
specify
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
describe ".iso8601" do
|
|
22
|
+
specify do
|
|
23
|
+
expect(described_class.iso8601("2001-01-01T00:00:00Z/2001-01-02T00:00:00-06:00"))
|
|
24
|
+
.to be_kind_of described_class
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
specify do
|
|
28
|
+
expect{described_class.iso8601("2001-01-01T00:00:00Z")}
|
|
29
|
+
.to raise_error ArgumentError
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
specify do
|
|
33
|
+
expect(described_class.iso8601("2001-01-01T00:00:00Z/.."))
|
|
34
|
+
.to (be_kind_of described_class)
|
|
35
|
+
.and (have_attributes(begin: Time.parse("2001-01-01T00:00:00Z"), end: nil))
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
specify do
|
|
39
|
+
expect(described_class.iso8601("\t2001-01-01T00:00:00Z/..\t"))
|
|
40
|
+
.to (be_kind_of described_class)
|
|
41
|
+
.and (have_attributes(begin: Time.parse("2001-01-01T00:00:00Z"), end: nil))
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
specify do
|
|
45
|
+
expect(described_class.iso8601(" 2001-01-01T00:00:00Z/.. "))
|
|
46
|
+
.to (be_kind_of described_class)
|
|
47
|
+
.and (have_attributes(begin: Time.parse("2001-01-01T00:00:00Z"), end: nil))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
specify do
|
|
51
|
+
expect(described_class.iso8601("../2001-01-01T00:00:00Z"))
|
|
52
|
+
.to (be_kind_of described_class)
|
|
53
|
+
.and (have_attributes(begin: nil, end: Time.parse("2001-01-01T00:00:00Z")))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
specify do
|
|
57
|
+
expect(described_class.iso8601("\t../2001-01-01T00:00:00Z\t"))
|
|
58
|
+
.to (be_kind_of described_class)
|
|
59
|
+
.and (have_attributes(begin: nil, end: Time.parse("2001-01-01T00:00:00Z")))
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
specify do
|
|
63
|
+
expect(described_class.iso8601(" ../2001-01-01T00:00:00Z "))
|
|
64
|
+
.to (be_kind_of described_class)
|
|
65
|
+
.and (have_attributes(begin: nil, end: Time.parse("2001-01-01T00:00:00Z")))
|
|
66
|
+
end
|
|
21
67
|
end
|
|
22
68
|
|
|
23
69
|
describe "#getutc" do
|
|
@@ -60,6 +106,18 @@ describe Timerage::TimeInterval do
|
|
|
60
106
|
.to eq "#{interval.begin.iso8601(3)}/#{interval.end.iso8601(3)}" }
|
|
61
107
|
specify { expect( interval.duration).to eq duration }
|
|
62
108
|
|
|
109
|
+
context "open-ended intervals" do
|
|
110
|
+
subject(:interval) { described_class.new(now-duration, nil) }
|
|
111
|
+
specify { expect(interval.iso8601).to eq "#{interval.begin.iso8601}/.." }
|
|
112
|
+
specify { expect(interval.iso8601(3)).to eq "#{interval.begin.iso8601(3)}/.." }
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
context "open-start intervals" do
|
|
116
|
+
subject(:interval) { described_class.new(nil, now) }
|
|
117
|
+
specify { expect(interval.iso8601).to eq "../#{interval.end.iso8601}" }
|
|
118
|
+
specify { expect(interval.iso8601(3)).to eq "../#{interval.end.iso8601(3)}" }
|
|
119
|
+
end
|
|
120
|
+
|
|
63
121
|
specify { expect(interval.cover? interval.begin+1..interval.end-1).to be_truthy }
|
|
64
122
|
specify { expect(interval.cover? interval.begin...interval.end).to be_truthy }
|
|
65
123
|
specify { expect(interval.cover? interval.begin-1..interval.end-1).to be_falsy }
|
|
@@ -86,12 +144,12 @@ describe Timerage::TimeInterval do
|
|
|
86
144
|
specify { expect( interval & (interval.begin...interval.end) ).to eq interval.begin...interval.end }
|
|
87
145
|
specify { expect( interval & (interval.begin...interval.end-1) )
|
|
88
146
|
.to eq interval.begin...interval.end-1 }
|
|
89
|
-
specify { expect( described_class.new(interval.begin
|
|
90
|
-
.to eq described_class.new(interval.begin
|
|
147
|
+
specify { expect( described_class.new(interval.begin, interval.end-1) & interval )
|
|
148
|
+
.to eq described_class.new(interval.begin,interval.end-1) }
|
|
91
149
|
end
|
|
92
150
|
|
|
93
151
|
describe "==" do
|
|
94
|
-
specify { expect( described_class.
|
|
152
|
+
specify { expect( described_class.from_range(interval) == interval ).to be true }
|
|
95
153
|
specify { expect( described_class.new(interval.begin, interval.end) == interval ).to be true }
|
|
96
154
|
end
|
|
97
155
|
|
|
@@ -159,7 +217,7 @@ describe Timerage::TimeInterval do
|
|
|
159
217
|
end
|
|
160
218
|
|
|
161
219
|
context "exclusive end" do
|
|
162
|
-
subject(:interval) { described_class.
|
|
220
|
+
subject(:interval) { described_class.from_range(now-duration...now) }
|
|
163
221
|
|
|
164
222
|
specify { expect(interval.exclude_end?).to be true }
|
|
165
223
|
specify { expect(interval.cover? now).to be false }
|
|
@@ -187,29 +245,29 @@ describe Timerage::TimeInterval do
|
|
|
187
245
|
end
|
|
188
246
|
|
|
189
247
|
context "exclusive 0 length interval" do
|
|
190
|
-
subject(:interval) { described_class.
|
|
248
|
+
subject(:interval) { described_class.from_range(now...now) }
|
|
191
249
|
specify { expect{ |b| subject.step(1, &b) }.not_to yield_control }
|
|
192
250
|
end
|
|
193
251
|
|
|
194
252
|
context "inclusive 0 length interval" do
|
|
195
|
-
subject(:interval) { described_class.
|
|
253
|
+
subject(:interval) { described_class.from_range(now..now) }
|
|
196
254
|
specify { expect{ |b| subject.step(1, &b) }.to yield_control.once }
|
|
197
255
|
end
|
|
198
256
|
|
|
199
257
|
context "includes leap day" do
|
|
200
|
-
subject(:interval) { described_class.new(before_leap_day
|
|
258
|
+
subject(:interval) { described_class.new(before_leap_day, after_leap_day) }
|
|
201
259
|
specify { expect{ |b| subject.step(1.day, &b) }.to yield_control.exactly(3).times }
|
|
202
260
|
specify { expect{ |b| subject.step(86_400, &b) }.to yield_control.exactly(3).times }
|
|
203
261
|
end
|
|
204
262
|
|
|
205
263
|
context "transition into dst with explicit time zone" do
|
|
206
|
-
subject(:interval) { described_class.new(before_dst
|
|
264
|
+
subject(:interval) { described_class.new(before_dst, after_dst) }
|
|
207
265
|
specify { expect{ |b| subject.step(1.hour, &b) }.to yield_control.exactly(2).times }
|
|
208
266
|
specify { expect{ |b| subject.step(3_600, &b) }.to yield_control.exactly(2).times }
|
|
209
267
|
end
|
|
210
268
|
|
|
211
269
|
context "transition into dst without explicit time zone" do
|
|
212
|
-
subject(:interval) { described_class.new(before_dst
|
|
270
|
+
subject(:interval) { described_class.new(before_dst, (before_dst + 1.hour)) }
|
|
213
271
|
specify { expect{ |b| subject.step(1.hour, &b) }.to yield_control.exactly(2).times }
|
|
214
272
|
specify { expect{ |b| subject.step(3_600, &b) }.to yield_control.exactly(2).times }
|
|
215
273
|
end
|
|
@@ -230,6 +288,14 @@ describe Timerage::TimeInterval do
|
|
|
230
288
|
end
|
|
231
289
|
end
|
|
232
290
|
|
|
291
|
+
context "produces complete slices" do
|
|
292
|
+
it "slices a range into intervals without missing any time inbetween" do
|
|
293
|
+
range = Timerage(Time.parse("2023-10-31T00:00:00+00:00")...Time.parse("2024-06-30 00:00:00 UTC"))
|
|
294
|
+
slices = range.slice(1.month)
|
|
295
|
+
expect(slices.map(&:duration).sum).to eq range.duration
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
|
|
233
299
|
let(:leap_day) { Time.parse("2016-02-29 12:00:00 UTC") }
|
|
234
300
|
let(:before_leap_day) { leap_day - 1.day }
|
|
235
301
|
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,15 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: timerage
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.
|
|
4
|
+
version: 2.4.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Peter Williams
|
|
8
8
|
- Chris Schneider
|
|
9
|
-
autorequire:
|
|
10
9
|
bindir: bin
|
|
11
10
|
cert_chain: []
|
|
12
|
-
date:
|
|
11
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
13
12
|
dependencies:
|
|
14
13
|
- !ruby/object:Gem::Dependency
|
|
15
14
|
name: bundler
|
|
@@ -96,7 +95,6 @@ homepage: ''
|
|
|
96
95
|
licenses:
|
|
97
96
|
- MIT
|
|
98
97
|
metadata: {}
|
|
99
|
-
post_install_message:
|
|
100
98
|
rdoc_options: []
|
|
101
99
|
require_paths:
|
|
102
100
|
- lib
|
|
@@ -111,8 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
111
109
|
- !ruby/object:Gem::Version
|
|
112
110
|
version: '0'
|
|
113
111
|
requirements: []
|
|
114
|
-
rubygems_version: 3.2
|
|
115
|
-
signing_key:
|
|
112
|
+
rubygems_version: 3.7.2
|
|
116
113
|
specification_version: 4
|
|
117
114
|
summary: Simple refinement to Range to allow Time or Date as arguments
|
|
118
115
|
test_files:
|