I'm creating a library-style system in Ruby on Rails, and I'm trying to come up with a way to calculate the overdue days while excluding weekends when a borrowed item is returned. Right now I'm just calculating "dayslate" as the difference between the due date and the date the item was actually returned, but I want to exclude weekends, since items can only be returned on weekdays.
This is my first real experience with Ruby and Rails, so my apologies if I'm missing something obvious. Thanks for any help you all can provide.
Here's the code I have 开发者_如何转开发for the "return" function:
def return
@product = Product.find(params[:id])
today = Date.today
dayslate = today - @product.due_date
if @product.due_date >= today
@product.borrower = @product.check_out = @product.due_date = @product.extended_checkout = nil
@product.save!
flash[:notice] = "Okay, it's checked in!"
redirect_to(products_url)
else
@product.borrower = @product.check_out = @product.due_date = @product.extended_checkout = nil
@product.save!
flash[:notice] = "Checked in, but it was #{dayslate} days late!"
redirect_to(products_url)
end
end
Here's a snippet of code to find the number of weekdays in a Range of Date objects
require 'date'
# Calculate the number of weekdays since 14 days ago
p ( (Date.today - 14)..(Date.today) ).select {|d| (1..5).include?(d.wday) }.size
This is how I would use it in your case.
class Product
def days_late
weekdays_in_date_range( self.due_date..(Date.today) )
end
protected
def weekdays_in_date_range(range)
# You could modify the select block to also check for holidays
range.select { |d| (1..5).include?(d.wday) }.size
end
end
sunday, saturday = 0, 6
weekend = [sunday, saturday]
((start_date..end_date).collect(&:wday) - weekend).count
If holidays matter to you (i.e. you don't want to count a day as a weekday if it is a holiday), you might want to look into http://rubyforge.org/projects/holidays/. If you combine the loop that @md5sum mentioned with a check to see if the weekday is a holiday, you may be golden.
Um.. just one more thing which could be useful since it's your first rails experience - pay attention to that business logic in the controller. Your model, that's where it belongs.
You might check out this page and see if you can add a counter into the loop and check against the current date through each iteration.
Regarding @Marini's post it is possible to eliminate the overhead of generating a range of dates:
def weekdays(date1, date2)
wday = date1.wday
(0..(date2-date1)).count {|offset| ((wday + offset) % 7 % 6) > 0}
end
% 7
turns each offset into a weekday, and % 6
returns 0
for Saturday (6) or Sunday (0)
calculate week days of the current_month
start_date = Date.today.beginning_of_month
end_date = Date.today.end_of_month
(start_date..end_date).select{|a| a.wday < 6 && a.wday > 0}.count
I think this is simpler and more readable than the accepted answer:
require 'date'
to = Date.parse('2017-02-02')
from = to - 14
(from..to).count { |d| !d.sunday? && !d.saturday? }
#=> 11
I found a faster way to counting weekend days, you can get days count excluding weekends by (start_date..end_date).size - weekend_count
:
def weekend_count(start_date, end_date)
size = (end_date - start_date).to_i
count = 0
if start_date.wday != 0
size -= (7 - start_date.wday).to_i
count += 1
end
left_over = size % 7
if left_over == 0
count = (count / 7) * 2
else
size -= left_over
count += (size / 7) * 2 + 1
end
count
end
Compare with [0, 6].include? date.wday
, see my benchmark result:
Distance 10
Optimized result: 4
Normal result: 4
user system total real
Optimized 0.000000 0.000000 0.000000 ( 0.000018)
Normal 0.000000 0.000000 0.000000 ( 0.000032)
Distance 100
Optimized result: 29
Normal result: 29
user system total real
Optimized 0.000000 0.000000 0.000000 ( 0.000006)
Normal 0.000000 0.000000 0.000000 ( 0.000094)
Distance 1000
Optimized result: 286
Normal result: 286
user system total real
Optimized 0.000000 0.000000 0.000000 ( 0.000010)
Normal 0.000000 0.000000 0.000000 ( 0.000650)
Distance 10000
Optimized result: 2858
Normal result: 2858
user system total real
Optimized 0.000000 0.000000 0.000000 ( 0.000013)
Normal 0.000000 0.000000 0.000000 ( 0.004995)
Distance 100000
Optimized result: 28572
Normal result: 28572
user system total real
Optimized 0.000000 0.000000 0.000000 ( 0.000011)
Normal 0.060000 0.000000 0.060000 ( 0.064223)
neutrino is quite right about moving all the logic that isnt involved in the display/render/redirect to the models.
E.g.
class ProductLoan
def due_date_passed?
days_late > 0
end
def days_late
Date.today - due_date
end
def complete!
self.borrower = self.check_out = self.due_date = self.extended_checkout = nil
save
end
end
class ReturnsController
def create
@product = ProductLoan.find(params[:id])
if @product.due_date_passed?
flash[:notice] = "Okay, it's checked in!"
else
flash[:notice] = "Checked in, but it was #{@product.days_late} days late!"
end
@product.complete!
redirect_to products_url
end
end
Ive just typed this without testing it but, something along these lines could calculate the days passed excluding weekends:
class DateOffsetCalculator
DAYS_IN_A_WEEK = 7
DAYS_IN_A_WEEKEND = 2
def initialize(date)
@date = date
end
def compute
days_passed - weekend_days_passed
end
def days_passed
(Date.today - @date).to_i
end
def weekend_days_passed
weekends_passed * DAYS_IN_A_WEEKEND
end
def weekends_passed
rounded_up_week_days / DAYS_IN_A_WEEK
end
def rounded_up_week_days
days_passed + commercial_working_day_correction
end
def commercial_working_day_correction
@date.cwday - 1
end
end
The top solution is fine and I know I shouldn't worry about performance, but there's a part of me that's bothered about counting the number of weekdays by generating a collection and iterating over it because the time to calculate is a linear function of the difference between the two dates.
The answer can be mathematically calculated in fixed time if you think it through. You can think about it in two parts:
PART 1 - NUMBER OF WEEKS BETWEEN
If the days difference is greater than a week, you can count each 7 day week as having 5 weekdays and "remove" the full weeks from consideration.
PART 2 - DAYS IN THE PARTIAL WEEK
Having subtracted the full weeks you can look at the simplified problem of how many weekdays lie between two days on a 7 day span. If you build a table you'll see there's a pretty simple rule or you can repeat the logic used in the winning solution but only over a interval of at most 7 days.
精彩评论