timerage 2.1.1 → 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: e2b0e89fc0f24f9a2d8216d867b9d7d8f9477c4b449bc43c9d5287c4e24057c7
4
- data.tar.gz: b6a3376136ad56501316b735da77c3ba8667803fd80f3a8812e941cf0f8dbbc3
3
+ metadata.gz: d21955bc8e1453cafb40e1de151e67835964f857728eaa1cf001a7c70a79a1fb
4
+ data.tar.gz: efc6d71d6fbc442b2fc3fa2066657cc51bc669549ad52a576ef09f14d29aa171
5
5
  SHA512:
6
- metadata.gz: 157b9c3a249183f8e8b4247332b5be02578899bf1c6d67e84853d161950bd09894d87098676eed4f71ced7a1e23d33e0c27168fdc27570a020e2774110664e75
7
- data.tar.gz: 131e7dbf90167eeaf21935d246327283da94371ffa40c27ee36577317daf7a92d91049acde3a4bfe6f48eaa87cdebf2cd596889e68983523fd6992a03e6d1ae9
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,8 +157,12 @@ 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
- next_offset = 0.seconds
165
+ next_offset = step * 0
139
166
 
140
167
  Enumerator.new do |y|
141
168
  while self.cover?(self.begin + next_offset)
@@ -1,3 +1,3 @@
1
1
  module Timerage
2
- VERSION = "2.1.1"
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,33 +193,57 @@ 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
216
222
 
223
+ context "When given date range" do
224
+ it "returns date objects when stepping through the range with a day duration" do
225
+ date_range = Date.new(2020, 1, 1)..Date.new(2020,1,3)
226
+ described_class.new(date_range).step(1.day) do |date|
227
+ expect(date).to be_kind_of Date
228
+ end
229
+ end
230
+
231
+ it "returns Time objects when stepping through the range with an hour duration" do
232
+ date_range = Date.new(2020, 1, 1)..Date.new(2020,1,2)
233
+ described_class.new(date_range).step(6.hours) do |date|
234
+ expect(date).to be_kind_of Time
235
+ end
236
+ end
237
+ end
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
+
217
247
  let(:leap_day) { Time.parse("2016-02-29 12:00:00 UTC") }
218
248
  let(:before_leap_day) { leap_day - 1.day }
219
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,15 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: timerage
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.1
4
+ version: 2.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peter Williams
8
8
  - Chris Schneider
9
- autorequire:
9
+ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2019-10-08 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
@@ -96,7 +96,7 @@ homepage: ''
96
96
  licenses:
97
97
  - MIT
98
98
  metadata: {}
99
- post_install_message:
99
+ post_install_message:
100
100
  rdoc_options: []
101
101
  require_paths:
102
102
  - lib
@@ -111,8 +111,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
111
111
  - !ruby/object:Gem::Version
112
112
  version: '0'
113
113
  requirements: []
114
- rubygems_version: 3.0.3
115
- signing_key:
114
+ rubygems_version: 3.4.19
115
+ signing_key:
116
116
  specification_version: 4
117
117
  summary: Simple refinement to Range to allow Time or Date as arguments
118
118
  test_files: