How do I get all Sundays between two dates in Ruby?

后端 未结 3 1882
无人及你
无人及你 2020-12-31 01:20

I\'m working on a form where the user enters a date range and selects from a list of checkboxes a day/days of the week i.e Sunday, Monday, Tuesday, Wednesday, Thursday, Frid

相关标签:
3条回答
  • 2020-12-31 01:28

    Another approach is to group your date range by wday and pick off your day of the week:

    datesByWeekday = (start_date..end_date).group_by(&:wday)
    datesByWeekday[0] # All Sundays
    

    for example, to get all Saturdays in March 2019:

    > (Date.new(2019,03,01)..Date.new(2019,04,01)).group_by(&:wday)[0]
    => [Sun, 03 Mar 2019, Sun, 10 Mar 2019, Sun, 17 Mar 2019, Sun, 24 Mar 2019, Sun, 31 Mar 2019]
    

    https://apidock.com/ruby/Date/wday

    0 讨论(0)
  • 2020-12-31 01:34

    Was curious about speed, so here's what I did.

    Here are two approaches to solve the problem:

    • Use a range and filter
    • Find first day and add 1.week to that day until stop

    For the ranged solutions, there are different ways to use the range:

    • Take all the dates and group them by weekday
    • Run the select function on the range
    • Convert range to array and then run select

    How you select dates is also important:

    • Run include? on the days requested
    • Find intersection between two arrays and check if empty

    I made a file to test all these methods. I called it test.rb. I placed it at the root of a rails application. I ran it by typing these commands:

    • rails c
    • load 'test.rb'

    Here's the testing file:

    @days = {
      'Sunday' => 0, 'Monday' => 1, 'Tuesday' => 2, 'Wednesday' => 3,
      'Thursday' => 4, 'Friday' => 5, 'Saturday' => 6,
    }
    @start = Date.today
    @stop = Date.today + 1.year
    
    # use simple arithmetic to count number of weeks and then get all days by adding a week
    def division(args)
      my_days = args.map { |key| @days[key] }
      total_days = (@stop - @start).to_i
      start_day = @start.wday
      my_days.map do |wday|
        total_weeks = total_days / 7
        remaining_days = total_days % 7
        total_weeks += 1 if is_there_wday? wday, remaining_days, @stop
        days_to_add = wday - start_day
        days_to_add = days_to_add + 7 if days_to_add.negative?
        next_day = @start + days_to_add
        days = []
        days << next_day
        (total_weeks - 1).times do
          next_day = next_day + 1.week
          days << next_day
        end
        days
      end.flatten.sort
    end
    
    def is_there_wday?(wday, remaining_days, stop)
      new_start = stop - remaining_days
      (new_start..stop).map(&:wday).include? wday
    end
    
    # take all the dates and group them by weekday
    def group_by(args)
      my_days = args.map { |key| @days[key] }
      grouped = (@start..@stop).group_by(&:wday)
      my_days.map { |wday| grouped[wday] }.flatten.sort
    end
    
    # run the select function on the range
    def select_include(args)
      my_days = args.map { |key| @days[key] }
      (@start..@stop).select { |x| my_days.include? x.wday }
    end
    
    # run the select function on the range
    def select_intersect(args)
      my_days = args.map { |key| @days[key] }
      (@start..@stop).select { |x| (my_days & [x.wday]).any? }
    end
    
    # take all the dates, convert to array, and then select
    def to_a_include(args)
      my_days = args.map { |key| @days[key] }
      (@start..@stop).to_a.select { |k| my_days.include? k.wday }
    end
    
    # take all dates, convert to array, and check if interection is empty
    def to_a_intersect(args)
      my_days = args.map { |key| @days[key] }
      (@start..@stop).to_a.select { |k| (my_days & [k.wday]).any? }
    end
    
    many = 10_000
    Benchmark.bmbm do |b|
      [[], ['Sunday'], ['Sunday', 'Saturday'], ['Sunday', 'Wednesday', 'Saturday']].each do |days|
        str = days.map { |x| @days[x] }
        b.report("#{str} division")       { many.times { division days }}
        b.report("#{str} group_by")       { many.times { group_by days }}
        b.report("#{str} select_include") { many.times { select_include days }}
        b.report("#{str} select_&")       { many.times { select_intersect days }}
        b.report("#{str} to_a_include")   { many.times { to_a_include days }}
        b.report("#{str} to_a_&")         { many.times { to_a_intersect days }}
      end
    end
    

    Sorted results

    [] division               0.017671
    [] select_include         2.459335
    [] group_by               2.743273
    [] to_a_include           2.880896
    [] to_a_&                 4.723146
    [] select_&               5.235843
    
    [0] to_a_include          2.539350
    [0] select_include        2.543794
    [0] group_by              2.953319
    [0] division              4.494644
    [0] to_a_&                4.670691
    [0] select_&              4.897872
    
    [0, 6] to_a_include       2.549803
    [0, 6] select_include     2.553911
    [0, 6] group_by           4.085657
    [0, 6] to_a_&             4.776068
    [0, 6] select_&           5.016739
    [0, 6] division          10.203996
    
    [0, 3, 6] select_include  2.615217
    [0, 3, 6] to_a_include    2.618676
    [0, 3, 6] group_by        4.605810
    [0, 3, 6] to_a_&          5.032614
    [0, 3, 6] select_&        5.169711
    [0, 3, 6] division       14.679557
    

    Trends

    • range.select is slightly faster than range.to_a.select
    • include? is faster than intersect.any?
    • group_by is faster than intersect.any? but slower than include?
    • division is fast when nothing is given, but slows down significantly the more params that are passed

    Conclusion

    If you combine select and include?, you have the fastest and most reliable solution for this problem

    0 讨论(0)
  • 2020-12-31 01:43

    fun one! :D

     start_date = Date.today # your start
     end_date = Date.today + 1.year # your end
     my_days = [1,2,3] # day of the week in 0-6. Sunday is day-of-week 0; Saturday is day-of-week 6.
     result = (start_date..end_date).to_a.select {|k| my_days.include?(k.wday)}
    

    using the data above you'll get an array of all Mon/Tue/Weds between now and next year.

    0 讨论(0)
提交回复
热议问题