From 42a0fd3fc4496f5ac142847b091b78a1d57d8605 Mon Sep 17 00:00:00 2001 From: Aram Visser Date: Sun, 19 Feb 2023 16:23:29 +0700 Subject: [PATCH] Add Faker::Date.day_of_week_between --- doc/default/date.md | 7 ++++ lib/faker/default/date.rb | 48 +++++++++++++++++++++++++++ test/faker/default/test_faker_date.rb | 37 +++++++++++++++++++++ 3 files changed, 92 insertions(+) diff --git a/doc/default/date.md b/doc/default/date.md index 79c7a7ebe1..8addda6a25 100644 --- a/doc/default/date.md +++ b/doc/default/date.md @@ -13,6 +13,13 @@ Faker::Date.between_except(from: '2014-09-23', to: '2015-09-25', excepted: '2015 # If used with Rails (the Active Support gem), additional options are available: Faker::Date.between_except(from: 1.year.ago, to: 1.year.from_now, excepted: Date.today) #=> # +# Random date at given day(s) of week between dates +# Keyword arguments: day, from, to +Faker::Date.on_day_of_week_between(day: :tuesday, from: '2023-01-01', to: '2023-02-01') #=> "Tue, 10 Jan 2023" +Faker::Date.on_day_of_week_between(day: [:saturday, :sunday], from: '2023-01-01', to: '2023-02-01') #=> "Sun, 22 Jan 2023" +# If used with Rails (the Active Support gem), additional options are available: +Faker::Date.on_day_of_week_between(day: [:monday, :wednesday, :friday], from: 1.year.ago, to: 1.year.from_now) #=> "Mon, 20 Feb 2023" + # Random date in the future (up to maximum of N days) # Keyword arguments: days Faker::Date.forward(days: 23) # => "Fri, 03 Oct 2014" diff --git a/lib/faker/default/date.rb b/lib/faker/default/date.rb index 6d1997c1af..e1ae258238 100644 --- a/lib/faker/default/date.rb +++ b/lib/faker/default/date.rb @@ -2,6 +2,8 @@ module Faker class Date < Base + DAYS_OF_WEEK = %i[sunday monday tuesday wednesday thursday friday saturday].freeze + class << self ## # Produce a random date between two dates. @@ -128,6 +130,52 @@ def in_date_period(month: nil, year: ::Date.today.year) between(from: from, to: to).to_date end + ## + # Produce a random date at given day(s) of the week between two dates. + # + # @param day [Symbol, Array] # The day(s) of the week. See {DAYS_OF_WEEK}. + # @param from [Date, String] The start of the usable date range. + # @param to [Date, String] The end of the usable date range. + # @return [Date] + # + # @example if used with or without Rails (Active Support) + # Faker::Date.on_day_of_week_between(day: :tuesday, from: '2023-01-01', to: '2023-02-01') #=> # + # + # @example if used with Rails (Active Support) + # Faker::Date.on_day_of_week_between(day: [:saturday, :sunday], from: 1.month.ago, to: Date.today) #=> # + # + # @faker.version next + def on_day_of_week_between(day:, from:, to:) + days = [day].flatten + raise ArgumentError, 'Day of week cannot be empty' if days.empty? + + # Convert given days of the week to numbers used by `Date#wday` method + numeric_weekdays = days.map do |d| + DAYS_OF_WEEK.index(d.to_sym.downcase) || raise(ArgumentError, "#{d} is not a valid day of the week") + end + + from = get_date_object(from) + to = get_date_object(to) + date = Faker::Base.rand_in_range(from, to) + + # If the initial date is not on one of the wanted days of the week... + unless numeric_weekdays.include? date.wday + # ...pick a date nearby that is on one of the wanted days of the week instead + date += sample(numeric_weekdays) - date.wday + + # Move date 1 week earlier or later if the adjusted date is now outside the date range + date += 7 if date < from + date -= 7 if date > to + + if date > to || date < from + raise ArgumentError, + "There is no #{DAYS_OF_WEEK[date.wday].capitalize} between #{from} and #{to}. Increase the from/to date range or choose a different day of the week." + end + end + + date + end + private def birthday_date(date, age) diff --git a/test/faker/default/test_faker_date.rb b/test/faker/default/test_faker_date.rb index d509bd85dc..a7575709e0 100644 --- a/test/faker/default/test_faker_date.rb +++ b/test/faker/default/test_faker_date.rb @@ -191,4 +191,41 @@ def test_in_date_period_date assert_equal date.year, year end end + + def test_on_day_of_week_between + days = %i[tuesday saturday] + from = Date.parse('2012-01-01') + to = Date.parse('2012-02-01') + + deterministically_verify -> { @tester.on_day_of_week_between(day: days, from: from, to: to) } do |date| + assert date >= from, "Expected >= \"#{from}\", but got #{date}" + assert date <= to, "Expected <= \"#{to}\", but got #{date}" + assert date.tuesday? || date.saturday?, "Expected #{date} to be Tuesday or Saturday, but was #{Faker::Date::DAYS_OF_WEEK[date.wday].capitalize}" + end + end + + def test_unknown_day_of_week + error = assert_raise ArgumentError do + @tester.on_day_of_week_between(day: :unknown, from: '2012-01-01', to: '2013-01-01') + end + + assert_equal 'unknown is not a valid day of the week', error.message + end + + def test_empty_day_of_week + error = assert_raise ArgumentError do + @tester.on_day_of_week_between(day: [], from: '2012-01-01', to: '2013-01-01') + end + + assert_equal 'Day of week cannot be empty', error.message + end + + def test_day_of_week_outside_date_range + error = assert_raise ArgumentError do + @tester.on_day_of_week_between(day: :friday, from: '2012-01-01', to: '2012-01-03') + end + + assert_equal 'There is no Friday between 2012-01-01 and 2012-01-03. Increase the from/to date range or choose a different day of the week.', + error.message + end end