timerage 2.2.0 → 2.3.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '07846226e71bd754087bd3b54c49d85a70e9d2f9b809d678351fbd685546fcb4'
4
- data.tar.gz: e3be843301a7d7a7ea7cec902c2f4fd8152314a5cbbf4e74d8aad65ede5196ec
3
+ metadata.gz: d21955bc8e1453cafb40e1de151e67835964f857728eaa1cf001a7c70a79a1fb
4
+ data.tar.gz: efc6d71d6fbc442b2fc3fa2066657cc51bc669549ad52a576ef09f14d29aa171
5
5
  SHA512:
6
- metadata.gz: 76acc33ddaa358897021f20b82c4f3faffb1867469e410da6566480880c78c9524a84e39edb6c3468bfd5d49cba4d72dfe526dbd749ad6ecff66be839fcb6bc0
7
- data.tar.gz: 96e281a1b857a002653629b4289574f4488068609f40f74b85832966a402311c88ea16f9db91080a74566ac502a7bd1468d496bb73b5095dcfbe572d09f2ec06
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
- args = [args.first.begin, args.first.end, args.first.exclude_end?] if args.first.respond_to?(:exclude_end?)
10
- new_obj = allocate
11
- new_obj.send(:initialize, *args)
12
- new_obj
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(seconds)
36
- time_enumerator(seconds)
37
- .map{|t|
38
- end_time = [t+seconds, self.end].min
39
- inclusive = (t == end_time || t+seconds > self.end) && !exclude_end?
40
- TimeInterval.new(t, end_time, !inclusive) }
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
 
@@ -1,3 +1,3 @@
1
1
  module Timerage
2
- VERSION = "2.2.0"
2
+ VERSION = "2.3.0"
3
3
  end
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.new(self).step(n, &blk)
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.new(self)
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.new(thing)
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..now)).to be_kind_of described_class }
10
- specify { expect(described_class.new(now-1, now)).to be_kind_of described_class }
11
- specify { expect(described_class.new(now-1, now, true)).to be_kind_of described_class }
12
- specify { expect(described_class.new(now-1, now, false )).to be_kind_of described_class }
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...interval.end-1) & interval )
90
- .to eq described_class.new(interval.begin...interval.end-1) }
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.new(interval) == interval ).to be true }
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.new(now-duration...now) }
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.new(now...now) }
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.new(now..now) }
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..after_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..after_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..(before_dst + 1.hour)) }
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}
@@ -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.2.0
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: 2023-11-10 00:00:00.000000000 Z
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.2.33
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