From 000d346d2a24352de7632ed02ce3650ba22ec205 Mon Sep 17 00:00:00 2001 From: Davor Ocelic Date: Wed, 27 Dec 2023 00:31:26 +0100 Subject: [PATCH] Make `#matches?` more compact --- spec/virtualtime_spec.cr | 366 ++++++++++++++++++++++++++++++++++++++- src/virtualtime.cr | 341 ++++++++++++++++-------------------- 2 files changed, 510 insertions(+), 197 deletions(-) diff --git a/spec/virtualtime_spec.cr b/spec/virtualtime_spec.cr index 1037c3a..ade7777 100644 --- a/spec/virtualtime_spec.cr +++ b/spec/virtualtime_spec.cr @@ -89,6 +89,7 @@ describe VirtualTime do vt.millisecond = 16 vt.matches?(vt2).should be_true + vt.matches?(vt2).should be_true end it "can match Crystal's Times in different locations" do @@ -137,7 +138,7 @@ describe VirtualTime do vt.hour.should eq 10..20 vt.second.should eq true vt.location.should eq Time::Location.load("Europe/Berlin") - #vt.default_match?.should eq false + # vt.default_match?.should eq false end it "does range comparison properly" do @@ -165,10 +166,371 @@ describe VirtualTime do # Other - it "honors default_match?" do + it "honors class-level default_match?" do + dm = VirtualTime.default_match? vt = VirtualTime.new vt.matches?(Time.local).should be_true VirtualTime.default_match = false vt.matches?(Time.local).should be_false + VirtualTime.default_match = dm + end + + it "can adjust timezone for Times" do + time = Time.local year: 2020, month: 1, day: 15, location: Time::Location.load("Europe/Berlin") + vt = VirtualTime.new location: Time::Location.load("America/New_York") + time2 = vt.adjust_location time + time2.should eq time.in vt.location.not_nil! + + vt = VirtualTime.new location: Time::Location.load("Europe/Berlin") + vt2 = VirtualTime.new location: Time::Location.load("America/New_York") + expect_raises(ArgumentError) do + vt.adjust_location vt2 + end + end + + it "Handles comparisons with Time" do + vt = VirtualTime.new + t = Time.local + (vt == t).should be_true + (vt <=> t).should eq 1 + vt.year = 1970 + (vt == t).should be_false + (vt <=> t).should eq -1 + end + + it "matches?(Nil, any, max)" do + vt = VirtualTime.new + {nil, 0, 1, 1000}.each do |max| + vt.matches?(nil, nil, max).should be_true + vt.matches?(nil, false, max).should be_false + vt.matches?(nil, true, max).should be_true + vt.matches?(nil, 0, max).should be_true + vt.matches?(nil, 15, max).should be_true + vt.matches?(nil, [1, 2, 3], max).should be_true + vt.matches?(nil, 1..10, max).should be_true + vt.matches?(nil, (1..10).step(3), max).should be_true + vt.matches?(nil, ->{ false }, max).should be_true + end + end + + it "matches?(Bool, any, max)" do + vt = VirtualTime.new + {nil, 0, 1, 1000}.each do |max| + vt.matches?(true, nil, max).should be_true + vt.matches?(true, false, max).should be_false + vt.matches?(true, true, max).should be_true + vt.matches?(true, 0, max).should be_true + vt.matches?(true, 15, max).should be_true + vt.matches?(true, [1, 2, 3], max).should be_true + vt.matches?(true, 1..10, max).should be_true + vt.matches?(true, (1..10).step(3), max).should be_true + vt.matches?(true, ->{ false }, max).should be_true + + vt.matches?(false, nil, max).should be_false + vt.matches?(false, false, max).should be_false + vt.matches?(false, true, max).should be_false + vt.matches?(false, 0, max).should be_false + vt.matches?(false, 15, max).should be_false + vt.matches?(false, [1, 2, 3], max).should be_false + vt.matches?(false, 1..10, max).should be_false + vt.matches?(false, (1..10).step(3), max).should be_false + vt.matches?(false, ->{ false }, max).should be_false + end + end + + it "matches?(Int, Int, max)" do + vt = VirtualTime.new + max = nil + vt.matches?(9, 13, max).should be_false + vt.matches?(9, 1, max).should be_false + vt.matches?(9, 9, max).should be_true + vt.matches?(9, 0, max).should be_false + vt.matches?(9, 31, max).should be_false + vt.matches?(5, -5, max).should be_false + vt.matches?(-5, -5, max).should be_true + vt.matches?(-5, 5, max).should be_false + vt.matches?(6, -5, max).should be_false + vt.matches?(5, -6, max).should be_false + vt.matches?(0, 0, max).should be_true + + max = 10 + vt.matches?(9, 13, max).should be_false + vt.matches?(9, 1, max).should be_false + vt.matches?(9, 9, max).should be_true + vt.matches?(9, 0, max).should be_false + vt.matches?(9, 31, max).should be_false + vt.matches?(5, -5, max).should be_true + vt.matches?(-5, -5, max).should be_true + vt.matches?(-5, 5, max).should be_true + vt.matches?(6, -5, max).should be_false + vt.matches?(5, -6, max).should be_false + vt.matches?(max, max, max).should be_true + vt.matches?(-max, max, max).should be_false + vt.matches?(max, -max, max).should be_false + vt.matches?(-max, -max, max).should be_true + end + + it "matches?(Array(Int), Int, max)" do + vt = VirtualTime.new + max = nil + vt.matches?([9], 13, max).should be_false + vt.matches?([9], 1, max).should be_false + vt.matches?([9], 9, max).should be_true + vt.matches?([9], 0, max).should be_false + vt.matches?([9], 31, max).should be_false + vt.matches?([5], -5, max).should be_false + vt.matches?([-5], -5, max).should be_true + vt.matches?([-5], 5, max).should be_false + vt.matches?([6], -5, max).should be_false + vt.matches?([5], -6, max).should be_false + vt.matches?([0], 0, max).should be_true + + max = 10 + vt.matches?([9], 13, max).should be_false + vt.matches?([9], 1, max).should be_false + vt.matches?([9], 9, max).should be_true + vt.matches?([9], 0, max).should be_false + vt.matches?([9], 31, max).should be_false + vt.matches?([5], -5, max).should be_true + vt.matches?([-5], -5, max).should be_true + vt.matches?([-5], 5, max).should be_true + vt.matches?([6], -5, max).should be_false + vt.matches?([5], -6, max).should be_false + vt.matches?([max], max, max).should be_true + vt.matches?([-max], max, max).should be_false + vt.matches?([max], -max, max).should be_false + vt.matches?([-max], -max, max).should be_true + + max = nil + vt.matches?([1, 9], 13, max).should be_false + vt.matches?([1, 9], 1, max).should be_true + vt.matches?([9], 9, max).should be_true + vt.matches?([9], 0, max).should be_false + vt.matches?([9], 31, max).should be_false + vt.matches?([5], -5, max).should be_false + vt.matches?([-5], -5, max).should be_true + vt.matches?([-5], 5, max).should be_false + vt.matches?([6, -5], -5, max).should be_true + vt.matches?([5], -6, max).should be_false + vt.matches?([0], 0, max).should be_true + + max = 10 + vt.matches?([9], 13, max).should be_false + vt.matches?([9], 1, max).should be_false + vt.matches?([9], 9, max).should be_true + vt.matches?([9], 0, max).should be_false + vt.matches?([9], 31, max).should be_false + vt.matches?([5], -5, max).should be_true + vt.matches?([-5], -5, max).should be_true + vt.matches?([-5], 5, max).should be_true + vt.matches?([6], -5, max).should be_false + vt.matches?([5], -6, max).should be_false + vt.matches?([max], max, max).should be_true + vt.matches?([-max], max, max).should be_false + vt.matches?([max], -max, max).should be_false + vt.matches?([-max], -max, max).should be_true + end + + it "matches?(Range(Int, Int), Int, max)" do + vt = VirtualTime.new + max = nil + vt.matches?(1..8, -1, max).should be_false + vt.matches?(1..8, 0, max).should be_false + vt.matches?(1..8, 1, max).should be_true + vt.matches?(1..8, -5, max).should be_false + vt.matches?(1..8, 5, max).should be_true + vt.matches?(1..8, 8, max).should be_true + vt.matches?(1..8, 9, max).should be_false + vt.matches?(1..8, -8, max).should be_false + vt.matches?(1..8, -7, max).should be_false + vt.matches?(1..8, -9, max).should be_false + vt.matches?(1..8, 13, max).should be_false + + max = 8 + vt.matches?(1..8, -1, max).should be_true + vt.matches?(1..8, 0, max).should be_false + vt.matches?(1..8, 1, max).should be_true + vt.matches?(1..8, -5, max).should be_true + vt.matches?(1..8, 5, max).should be_true + vt.matches?(1..8, 8, max).should be_true + vt.matches?(1..8, 9, max).should be_false + vt.matches?(1..8, -8, max).should be_false + vt.matches?(1..8, -7, max).should be_true + vt.matches?(1..8, -9, max).should be_false + vt.matches?(1..8, 13, max).should be_false + end + + it "matches?(Steppable(Int, Int), Int, max)" do + vt = VirtualTime.new + max = nil + vt.matches?((1..8).step(2), -1, max).should be_false + vt.matches?((1..8).step(2), 0, max).should be_false + vt.matches?((1..8).step(2), 1, max).should be_true + vt.matches?((1..8).step(2), -5, max).should be_false + vt.matches?((1..8).step(2), 5, max).should be_true + vt.matches?((1..8).step(2), 8, max).should be_false + vt.matches?((1..8).step(2), 9, max).should be_false + vt.matches?((1..8).step(2), -8, max).should be_false + vt.matches?((1..8).step(2), -7, max).should be_false + vt.matches?((1..8).step(2), -9, max).should be_false + vt.matches?((1..8).step(2), 13, max).should be_false + + max = 8 + vt.matches?((1..8).step(2), -1, max).should be_true + vt.matches?((1..8).step(2), 0, max).should be_false + vt.matches?((1..8).step(2), 1, max).should be_true + vt.matches?((1..8).step(2), -5, max).should be_true + vt.matches?((1..8).step(2), 5, max).should be_true + vt.matches?((1..8).step(2), 8, max).should be_false + vt.matches?((1..8).step(2), 9, max).should be_false + vt.matches?((1..8).step(2), -8, max).should be_false + vt.matches?((1..8).step(2), -7, max).should be_true + vt.matches?((1..8).step(2), -9, max).should be_false + vt.matches?((1..8).step(2), 13, max).should be_false + end + + # Enumerable not tested directly + + it "matches?(Array(Int), Array(Int), max)" do + vt = VirtualTime.new + max = nil + vt.matches?([9], [1], max).should be_false + vt.matches?([9], [1, 2, 3, 4, 8, 10, 11], max).should be_false + vt.matches?([9], [9], max).should be_true + vt.matches?([9], [-1, -8, -9, -10], max).should be_false + vt.matches?([9], [0], max).should be_false + vt.matches?([5], [-5], max).should be_false + vt.matches?([-5], [-5], max).should be_true + vt.matches?([-5], [5], max).should be_false + vt.matches?([6], [-1], max).should be_false + vt.matches?([0], [-1, 0, 1], max).should be_true + + max = 10 + vt.matches?([9], [1], max).should be_false + vt.matches?([9], [1, 2, 3, 4, 8, 10, 11], max).should be_false + vt.matches?([9], [9], max).should be_true + vt.matches?([9], [-1, -8, -9, -10], max).should be_true + vt.matches?([9], [0], max).should be_false + vt.matches?([5], [-5], max).should be_true + vt.matches?([-5], [-5], max).should be_true + vt.matches?([-5], [5], max).should be_true + vt.matches?([6], [-1], max).should be_false + vt.matches?([0], [-1, 0, 1], max).should be_true + end + + it "matches?(Range(Int,Int), Range(Int,Int), max)" do + vt = VirtualTime.new + max = nil + vt.matches?(10..23, 1..30, max).should be_true + vt.matches?(10..23, 1..10, max).should be_true + vt.matches?(10..23, 23..30, max).should be_true + vt.matches?(10..23, 5..9, max).should be_false + vt.matches?(10..23, 24..30, max).should be_false + vt.matches?(10..23, 1..12, max).should be_true + vt.matches?(10..23, 21..30, max).should be_true + vt.matches?(1..5, 6..10, max).should be_false + vt.matches?(6..10, 1..5, max).should be_false + vt.matches?(10..-1, 15..20, max).should be_false + vt.matches?(1..-10, 5..-15, max).should be_false + + max = 30 + vt.matches?(10..23, 1..30, max).should be_true + vt.matches?(10..23, 1..10, max).should be_true + vt.matches?(10..23, 23..30, max).should be_true + vt.matches?(10..23, 5..9, max).should be_false + vt.matches?(10..23, 24..30, max).should be_false + vt.matches?(10..23, 1..12, max).should be_true + vt.matches?(10..23, 21..30, max).should be_true + vt.matches?(1..5, 6..10, max).should be_false + vt.matches?(6..10, 1..5, max).should be_false + vt.matches?(10..-1, 15..20, max).should be_true + vt.matches?(1..-10, 5..-15, max).should be_true + vt.matches?(1..-1, 10..-10, max).should be_true + vt.matches?(1..-1, 40..50, max).should be_false + end + + it "matches?(Steppable::StepIterator(Int,Int,Int), Int, max)" do + vt = VirtualTime.new + max = nil + vt.matches?((10..23).step(2), 10, max).should be_true + vt.matches?((10..23).step(2), 11, max).should be_false + vt.matches?((10..23).step(2), 22, max).should be_true + vt.matches?((10..23).step(3), 23, max).should be_false + vt.matches?((10..23).step(2), 9, max).should be_false + vt.matches?((10..23).step(2), 24, max).should be_false + vt.matches?((1..5).step(2), -28, max).should be_false + vt.matches?((6..10).step(2), 3, max).should be_false + vt.matches?((10..-1).step(2), 20, max).should be_false + vt.matches?((1..-10).step(2), 2, max).should be_false + + max = 30 + vt.matches?((10..23).step(2), 10, max).should be_true + vt.matches?((10..23).step(2), 11, max).should be_false + vt.matches?((10..23).step(2), 22, max).should be_true + vt.matches?((10..23).step(3), 23, max).should be_false + vt.matches?((10..23).step(2), 9, max).should be_false + vt.matches?((10..23).step(2), 24, max).should be_false + vt.matches?((1..5).step(3), -26, max).should be_true + vt.matches?((6..10).step(2), 3, max).should be_false + vt.matches?((10..-1).step(2), 20, max).should be_true + vt.matches?((1..-10).step(2), 2, max).should be_false + vt.matches?((10..-1).step(2), 0, max).should be_false + vt.matches?((2..-10).step(2), 0, max).should be_false + vt.matches?((1..-1).step(7), 6, max).should be_false + vt.matches?((1..-1).step(6), 7, max).should be_true + vt.matches?((1..-1).step(7), 15, max).should be_true + end + + it "matches?(Steppable::StepIterator(Int,Int,Int), Steppable::StepIterator(Int,Int,Int), max)" do + vt = VirtualTime.new + max = nil + vt.matches?((10..23).step(2), (10..23).step(2), max).should be_true + vt.matches?((10..23).step(2), (10..23).step(3), max).should be_true + vt.matches?((10..23).step(2), (11..23).step(2), max).should be_false + vt.matches?((10..23).step(3), (5..10).step(2), max).should be_false + vt.matches?((10..23).step(2), (6..10).step(2), max).should be_true + vt.matches?((10..23).step(2), (10..23).step(2), max).should be_true + vt.matches?((1..5).step(2), (10..23).step(2), max).should be_false + vt.matches?((6..10).step(2), (10..23).step(2), max).should be_true + vt.matches?((10..-1).step(2), (10..23).step(2), max).should be_false + vt.matches?((1..-10).step(2), (10..23).step(2), max).should be_false + + max = 30 + vt.matches?((10..23).step(2), (10..23).step(2), max).should be_true + vt.matches?((10..23).step(2), (10..23).step(3), max).should be_true + vt.matches?((10..23).step(2), (11..23).step(2), max).should be_false + vt.matches?((10..23).step(3), (5..10).step(2), max).should be_false + vt.matches?((10..23).step(2), (6..10).step(2), max).should be_true + vt.matches?((10..23).step(2), (10..23).step(2), max).should be_true + vt.matches?((1..5).step(2), (10..23).step(2), max).should be_false + vt.matches?((6..10).step(2), (10..23).step(2), max).should be_true + vt.matches?((10..-1).step(2), (16..23).step(2), max).should be_true + vt.matches?((10..-1).step(2), (17..23).step(2), max).should be_false + vt.matches?((1..-10).step(2), (-20..-10).step(2), max).should be_false + vt.matches?((1..-10).step(3), (-25..-10).step(2), max).should be_true + vt.matches?((2..-10).step(2), (40..-10).step(2), max).should be_false + vt.matches?((-20..-1).step(2), (40..50).step(3), max).should be_false + vt.matches?((1..-1).step(6), (5..23).step(2), max).should be_true + vt.matches?((1..-1).step(7), (5..23).step(2), max).should be_true + end + + it "matches?(VirtualProc, Int, max)" do + vt = VirtualTime.new + v_true = ->(v : Int32) { true } + v_false = ->(v : Int32) { false } + v_ge_10 = ->(v : Int32) { v >= 10 } + + vt.matches?(v_true, 0, nil).should be_true + vt.matches?(v_false, 0, nil).should be_false + vt.matches?(v_ge_10, 0, nil).should be_false + vt.matches?(v_ge_10, 20, nil).should be_true + end + + it "can't do matches?(VirtualProc, VirtualProc, max)" do + vt = VirtualTime.new + v_true = ->(v : Int32) { true } + expect_raises(ArgumentError) { + vt.matches? v_true, v_true + } end end diff --git a/src/virtualtime.cr b/src/virtualtime.cr index d1d5787..979968d 100644 --- a/src/virtualtime.cr +++ b/src/virtualtime.cr @@ -40,12 +40,12 @@ class VirtualTime class_property? default_match : Bool = true # Instance-default match result if one of field values matched is `nil` - #property? default_match : Bool = true + # property? default_match : Bool = true - def initialize(@year = nil, @month = nil, @day = nil, @hour = nil, @minute = nil, @second = nil, *, @millisecond = nil, @nanosecond = nil, @day_of_week = nil, @day_of_year = nil, @week = nil) #, @default_match = @@default_match) + def initialize(@year = nil, @month = nil, @day = nil, @hour = nil, @minute = nil, @second = nil, *, @millisecond = nil, @nanosecond = nil, @day_of_week = nil, @day_of_year = nil, @week = nil, @location = nil) # , @default_match = @@default_match) end - def initialize(*, @year, @week, @day_of_week = nil, @hour = nil, @minute = nil, @second = nil, @millisecond = nil, @nanosecond = nil) #, @default_match = self.class.default_match?) + def initialize(*, @year, @week, @day_of_week = nil, @hour = nil, @minute = nil, @second = nil, @millisecond = nil, @nanosecond = nil, @location = nil) # , @default_match = self.class.default_match?) end def initialize(@year, @month, @day, @week, @day_of_week, @day_of_year, @hour, @minute, @second, @millisecond, @nanosecond, @location) @@ -53,36 +53,15 @@ class VirtualTime # Matching - # :nodoc: - macro adjust_location - if time.is_a? Time - if (l = location) && (time.location != l) - time = time.in l - end - else - if location != time.location - raise ArgumentError.new "Comparing VirtualTimes with different locations/timezones not supported (yet?)" - end - end - end - # Returns whether `VirtualTime` matches the specified time def matches?(time : TimeOrVirtualTime = Time.local) - adjust_location + time = adjust_location time matches_date?(time) && matches_time?(time) end - # :ditto: - # - # Alias for `matches?`. - @[AlwaysInline] - def ==(time : TimeOrVirtualTime = Time.local) - matches? time - end - # Returns whether `VirtualTime` matches the date part of specified time def matches_date?(time : TimeOrVirtualTime = Time.local) - adjust_location + time = adjust_location time matches?(year, time.year, 9999) && matches?(month, time.month, 12) && matches?(day, time.day, TimeHelper.days_in_month(time)) && @@ -93,7 +72,7 @@ class VirtualTime # Returns whether `VirtualTime` matches the time part of specified time def matches_time?(time : TimeOrVirtualTime = Time.local) - adjust_location + time = adjust_location time matches?(hour, time.hour, 23) && matches?(minute, time.minute, 59) && matches?(second, time.second, 59) && @@ -102,166 +81,149 @@ class VirtualTime end # Performs matching between VirtualTime and other supported types - def matches?(a : Nil, b, max = nil) - return false if b == false - self.class.default_match? - end - - # :ditto: - def matches?(a : Bool, b, max = nil) - return false if b == false - a - end - - # :ditto: - def matches?(a : Int, b : Int, max = nil) - if max - a = max + a if a < 0 - b = max + b if b < 0 + def matches?(a, b, max = nil) : Bool + a = adjust_value a, max + b = adjust_value b, max + + case a + in Nil + b == false ? false : self.class.default_match? + in Bool + b == false ? false : a + in Int + case b + in Nil, Bool, Array(Int32), Range(Int32, Int32), Steppable::StepIterator(Int32, Int32, Int32) + matches? b, a, max + in Int + a == b + in VirtualProc + b.call a + end + in Array(Int32), Range(Int32, Int32), Steppable::StepIterator(Int32, Int32, Int32) + a = a.dup if a.is_a? Steppable::StepIterator(Int32, Int32, Int32) + case b + in Nil, Bool + matches? b, a, max + in Int + a.each do |aa| + return true if aa == b + end + false + in Array(Int32), Range(Int32, Int32), Steppable::StepIterator(Int32, Int32, Int32) + a.each do |aa| + bb = b.is_a?(Steppable::StepIterator(Int32, Int32, Int32)) ? b.dup : b + bb.each do |bbb| + return true if aa == bbb + end + end + false + in VirtualProc + a.each do |aa| + return true if b.call aa + end + false + end + in VirtualProc + case b + in Nil, Bool, Array(Int32), Range(Int32, Int32), Steppable::StepIterator(Int32, Int32, Int32) + matches? b, a, max + in Int32 + a.call b + in VirtualProc + raise ArgumentError.new "Proc to Proc comparison not supported (yet?)" + end end - a == b end - # # ###### Possibly enable - # # :ditto: - # def matches?(a : Array(Int), b : Int, max = nil) - # a.each do |aa| - # return true if matches? aa, b, max - # end - # false - # end + # Commutative pairs of above functions: # # :ditto: - # def matches?(a : Range(Int, Int), b : Int, max = nil) - # if max && (a.begin < 0 || a.end < 0) - # ab = a.begin < 0 ? max + a.begin : a.begin - # ae = a.end < 0 ? max + a.end : a.end - # a = ab..ae - # end - # a.each do |aa| - # return true if matches? aa, b, max - # end - # false + # def matches?(a, b : Nil | Bool | Array(Int) | Range(Int, Int) | Steppable::StepIterator(Int, Int, Int) | VirtualProc, max = nil) + # matches? b, a, max # end - # # :ditto: - # def matches?(a : Steppable::StepIterator(Int, Int, Int), b : Int, max = nil) - # if max && (a.current < 0 || a.limit < 0) - # ab = a.current < 0 ? max + a.current : a.current - # ae = a.limit < 0 ? max + a.limit : a.limit - # a = Steppable::StepIterator(Int32, Int32, Int32).new ab, ae, a.step, a.exclusive - # else - # a = a.dup - # end - # a.each do |aa| - # return true if matches? aa, b, max - # end - # false - # end + # Helpers: - # # ###### Possibly enable - - # :ditto: - def matches?(a : Enumerable(Int), b : Int, max = nil) - a.dup.each do |aa| - return true if matches? aa, b, max + @[AlwaysInline] + def adjust_value(a, max) + case a + in Nil, Bool + a + in Int + if max + a < 0 ? max + a : a + else + a + end + in Array(Int32) + if max + a.map { |aa| aa < 0 ? max + aa : aa } + else + a + end.sort + in Range(Int32, Int32) + if max && (a.begin < 0 || a.end < 0) + ab = a.begin < 0 ? max + a.begin : a.begin + ae = a.end < 0 ? max + a.end : a.end + ab..ae + else + a + end + in Steppable::StepIterator(Int32, Int32, Int32) + if max && (a.current < 0 || a.limit < 0) + ab = a.current < 0 ? max + a.current : a.current + ae = a.limit < 0 ? max + a.limit : a.limit + Steppable::StepIterator(Int32, Int32, Int32).new ab, ae, a.step, a.exclusive + else + a + end + in Enumerable(Int32) + if max + a.map { |aa| aa < 0 ? max + aa : aa } + else + a + end.sort + in VirtualProc, Proc(Bool) + a end - false end - # # ###### Possibly enable - # # :ditto: - # def matches?(a : Array(Int), b : Array(Int), max = nil) - # a.each do |aa| - # b.each do |bb| - # return true if matches? aa, bb, max - # end - # end - # false - # end - - # # :ditto: - # def matches?(a : Range(Int, Int), b : Range(Int, Int), max = nil) - # if max - # if (a.begin < 0 || a.end < 0) - # ab = a.begin < 0 ? max + a.begin : a.begin - # ae = a.end < 0 ? max + a.end : a.end - # a = ab..ae - # end - # if (b.begin < 0 || b.end < 0) - # bb = b.begin < 0 ? max + b.begin : b.begin - # be = b.end < 0 ? max + b.end : b.end - # b = bb..be - # end - # end - # a.each do |aa| - # b.each do |bb| - # return true if matches? aa, bb, max - # end - # end - # false - # end - - # # :ditto: - # def matches?(a : Steppable::StepIterator(Int, Int, Int), b : Steppable::StepIterator(Int, Int, Int), max = nil) - # if max - # if a.current < 0 || a.limit < 0 - # ab = a.current < 0 ? max + a.current : a.current - # ae = a.limit < 0 ? max + a.limit : a.limit - # a = Steppable::StepIterator(Int32, Int32, Int32).new ab, ae, a.step, a.exclusive - # else - # a = a.dup - # end - # if b.current < 0 || b.limit < 0 - # bb = b.current < 0 ? max + b.current : b.current - # be = b.limit < 0 ? max + b.limit : b.limit - # b = Steppable::StepIterator(Int32, Int32, Int32).new bb, be, b.step, b.exclusive - # else - # b = b.dup - # end - # end - # a.each do |aa| - # b.each do |bb| - # return true if matches? aa, bb, max - # end - # end - # false - # end - - # # ###### Possibly enable - - # :ditto: - def matches?(a : Enumerable(Int), b : Enumerable(Int), max = nil) - a.dup.each do |aa| - b.dup.each do |bb| - return true if matches? aa, bb, max + # :nodoc: + @[AlwaysInline] + def adjust_location(time) + if time.is_a? Time + if (l = location) && (time.location != l) + time = time.in l + end + else + if location != time.location + raise ArgumentError.new "Comparing VirtualTimes with different locations/timezones not supported (yet?)" end end - false + time end - # :ditto: - def matches?(a : Enumerable(Int), b : VirtualProc, max = nil) - a.dup.each do |aa| - aa = max + aa if max && (aa < 0) - return true if b.call aa + # If `max` is specified, adjusts `hint` in respect to `max`. + # + # Specifically, if `hint` is equal or greater than `max`, it wraps it around + # by increasing `carry` by 1 and reducing `hint` by `max`. + # + # The current implementation does not support wrapping more than once, e.g. + # a wanted of `120` with a max of `60` would produce an error. + # That is because some of `VirtualTime`s fields (like e.g. `day`) do not have + # a fixed max value (it can be 28, 29, 30, or 31, depending on month). + @[AlwaysInline] + macro adjust_wanted_re_max + if max + limit = (2*max-2*min).abs + if wanted.abs >= limit + raise ArgumentError.new "A `wanted.abs` value #{wanted.abs} must not be be >= #{limit} (>= (2*max-2*min).abs)." + end + if wanted >= max + wanted -= max - min + carry += 1 + end end - false - end - - # :ditto: - def matches?(a : VirtualProc, b : Int, max = nil) - b = max + b if max && (b < 0) - a.call b - end - - # :ditto: - def matches?(a : VirtualProc, b : VirtualProc, max = nil) - raise ArgumentError.new "Proc to Proc comparison not supported (yet?)" - end - - def matches?(a, b, max = nil) - matches? b, a, max end # Materializing @@ -289,29 +251,6 @@ class VirtualTime {year: _year, month: _month, day: _day, hour: _hour, minute: _minute, second: _second, nanosecond: _nanosecond} end - # If `max` is specified, adjusts `hint` in respect to `max`. - # - # Specifically, if `hint` is equal or greater than `max`, it wraps it around - # by increasing `carry` by 1 and reducing `hint` by `max`. - # - # The current implementation does not support wrapping more than once, e.g. - # a wanted of `120` with a max of `60` would produce an error. - # That is because some of `VirtualTime`s fields (like e.g. `day`) do not have - # a fixed max value (it can be 28, 29, 30, or 31, depending on month). - @[AlwaysInline] - macro adjust_wanted_re_max - if max - limit = (2*max-2*min).abs - if wanted.abs >= limit - raise ArgumentError.new "A `wanted.abs` value #{wanted.abs} must not be be >= #{limit} (>= (2*max-2*min).abs)." - end - if wanted >= max - wanted -= max - min - carry += 1 - end - end - end - # Materialize a particular value with the help of a wanted/wanted value. # If 'strict' is true and wanted value does not satisfy predefined range or requirements, it is replaced with the first/earliest value from allowed range. def self.materialize(allowed : Nil, wanted : Int32, min, max = nil, strict = true) @@ -352,6 +291,7 @@ class VirtualTime def self.materialize(allowed : Range(Int, Int), wanted : Int32, min, max = nil, strict = true) carry = 0 adjust_wanted_re_max + # XXX adjust_range... if max && (allowed.begin < 0 || allowed.end < 0) ab = allowed.begin < 0 ? max + allowed.begin : allowed.begin ae = allowed.end < 0 ? max + allowed.end : allowed.end @@ -410,9 +350,20 @@ class VirtualTime # Comparison and conversion to and from time + # Compares `VirtualTime` to `Time` instance + # + # Alias for `matches?`. + @[AlwaysInline] + def ==(time : TimeOrVirtualTime = Time.local) + matches? time + end + # Compares `VirtualTime` to `Time` instance def <=>(other : Time) - to_time(other) <=> other + # This is one possible implementation: + # to_time(other) <=> other + # Another could be: + matches?(other) ? 1 : -1 end # "Rewinds" `day` forward enough to reach `acceptable_day`.