Ensure intuitive behavior when querying across holidays (#71)

This commit is contained in:
Hakan Ensari 2024-11-20 14:13:17 +01:00
parent e5815737c1
commit 7c99f214e6
No known key found for this signature in database
4 changed files with 60 additions and 16 deletions

File diff suppressed because one or more lines are too long

View File

@ -3,13 +3,24 @@
class Day < Sequel::Model class Day < Sequel::Model
dataset_module do dataset_module do
def latest(date = Date.today) def latest(date = Date.today)
where(date: select(:date).where(Sequel.lit("date <= ?", date)) where(date: select(:date).where(Sequel[:date] <= date)
.order(Sequel.desc(:date)) .order(Sequel.desc(:date))
.limit(1)) .limit(1))
end end
# Returns rates for a given date interval
#
# If the start date falls on a holiday/weekend, rates start from the closest preceding business day.
def between(interval) def between(interval)
where(date: interval) return where(false) if interval.begin > Date.today
previous_date = select(:date)
.where(Sequel[:date] <= interval.begin)
.order(Sequel.desc(:date))
.limit(1)
where(Sequel.expr(:date) >= Sequel.function(:coalesce, previous_date, interval.begin))
.where(Sequel.expr(:date) <= interval.end)
end end
def currencies def currencies

View File

@ -5,29 +5,54 @@ require "day"
describe Day do describe Day do
describe ".latest" do describe ".latest" do
it "returns latest rates before given date" do it "returns latest available rates on given date" do
date = Date.parse("2010-01-04")
data = Day.latest(date)
_(data.to_a.sample.date).must_equal(date)
date = Date.parse("2010-01-01") date = Date.parse("2010-01-01")
data = Day.latest(date) data = Day.latest(date)
_(data.first.date).must_be(:<=, date) _(data.to_a.sample.date).must_equal(Date.parse("2009-12-31"))
end end
it "returns nothing if there are no rates before given date" do it "returns nothing if date predates dataset" do
_(Day.latest(Date.parse("1998-01-01"))).must_be_empty _(Day.latest(Date.parse("1901-01-01"))).must_be_empty
end end
end end
describe ".between" do describe ".between" do
it "returns rates between given dates" do it "returns rates between given working dates" do
start_date = Date.parse("2010-01-01") start_date = Date.parse("2010-01-04")
end_date = Date.parse("2010-01-31") end_date = Date.parse("2010-01-29")
dates = Day.between((start_date..end_date)).map(:date).sort dates = Day.between((start_date..end_date)).map(:date).sort
_(dates.first).must_be(:>=, start_date) _(dates.first).must_equal(start_date)
_(dates.last).must_be(:<=, end_date) _(dates.last).must_equal(end_date)
end end
it "returns nothing if there are no rates between given dates" do it "starts on preceding business day if start date is a holiday" do
interval = (Date.parse("1998-01-01")..Date.parse("1998-01-31")) start_date = Date.parse("2024-11-03")
end_date = Date.parse("2024-11-04")
dates = Day.between((start_date..end_date)).map(:date)
_(dates).must_include(Date.parse("2024-11-01"))
end
it "returns nothing if end date predates dataset" do
interval = (Date.parse("1901-01-01")..Date.parse("1901-01-31"))
_(Day.between(interval)).must_be_empty _(Day.between(interval)).must_be_empty
end end
it "allows start date to predate dataset" do
start_date = Date.parse("1901-01-01")
end_date = Date.parse("2024-01-01")
dates = Day.between((start_date..end_date)).map(:date)
_(dates).wont_be_empty
end
it "returns nothing if queried for the future" do
start_date = Date.today + 1
end_date = start_date + 1
dates = Day.between((start_date..end_date)).map(:date)
_(dates).must_be_empty
end
end end
end end

View File

@ -22,8 +22,16 @@ module Quote
end end
it "quotes given date interval" do it "quotes given date interval" do
_(Date.parse(quote.formatted[:start_date])).must_be(:>=, dates.first) returned_start = Date.parse(quote.formatted[:start_date])
_(Date.parse(quote.formatted[:end_date])).must_be(:<=, dates.last) returned_end = Date.parse(quote.formatted[:end_date])
# The returned start date should be the closest working day (before or on) the requested start
_(returned_start).must_be(:<=, dates.first)
# But it shouldn't be too far back (maybe 10 business days)
_(returned_start).must_be(:>, dates.first - 10)
# End date should still be within the requested range
_(returned_end).must_be(:<=, dates.last)
end end
it "quotes against the Euro" do it "quotes against the Euro" do