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