Ruby expression evaluation: whitespace matters? - ruby-on-rails

Imagine it's Jan 19. This will not be hard if you look at this question today.
Date.today
=> Thu, 19 Jan 2012 # as expected
Date.today + 1
=> Fri, 20 Jan 2012 # as expected
Date.today+1
=> Fri, 20 Jan 2012 # as expected
Date.today +1
=> Thu, 19 Jan 2012 # ?!
What am I missing here?

The difference is that:
Date.today + 1
is an addition of two numerical values and
Date.today +1
is a call to the method today with the parameter sg(day of calendar reform) with value +1
The best way to examine this is to monkey patch the original method with debug output included. See this script as example:
require 'date'
class Date
def self.today(sg=ITALY)
puts "ITALY default("+sg.to_s+")" if sg==ITALY
puts sg unless sg==ITALY
jd = civil_to_jd(*(Time.now.to_a[3..5].reverse << sg))
new0(jd_to_ajd(jd, 0, 0), 0, sg)
end
end
puts "- Addition:"
Date.today + 1
puts "- Parameter:"
Date.today +1
This will print the following console output:
- Addition:
ITALY default(2299161)
- Parameter:
1

Yes, whitespace does matter in Ruby, contrary to popular belief. For example, foo bar is not the same as foobar.
In this particular case,
Date.today + 1
is the same as
Date.today().+(1)
Whereas
Date.today +1
is the same as
Date.today(+1)
which is the same as
Date.today(1.+#())

Related

How to get all 'specific days' within a date range

How can I get let's say All the dates for Saturday and Sunday from X year to Y year and store them as array? Pseudo code would be
(year_today..next_year).get_all_dates_for_saturday_and_sunday
Or perhaps there are gems that cater to this already?
Try this:
(Date.today..Date.today.next_year).select { |date|
date.sunday? or date.saturday?
}
#=> [Sat, 03 Sep 2016,Sun, 04 Sep 2016,Sat, 10 Sep 2016,Sun, 11 Sep 2016...
(Date.today..(Date.today + 1.year)).select do |date|
date.saturday? || date.sunday?
end # => [Sat, 03 Sep 2016, Sun, 04 Sep 2016, Sat, 10 Sep 2016, ...
This will then give you an array of 104 elements containing every date which is a saturday or a sunday between today and today in a year.
The following approach emphasizes efficiency over brevity, by avoiding the need to determine if every day in a range is a given day (or one of two given days) of the week.
Code
require 'date'
def dates_by_years_and_wday(start_year, end_year, wday)
(first_date_by_year_and_wday(start_year, wday)...
first_date_by_year_and_wday(end_year+1, wday)).step(7).to_a
end
def first_date_by_year_and_wday(year, wday)
d = Date.new(year)
d + (wday >= d.wday ? wday - d.wday : 7 + wday - d.wday)
end
Notice that the range is defined with three dots, meaning the first date in end_year is excluded.
Example
SATURDAY = 6
SUNDAY = 0
start_year, end_year = 2015, 2017
dates_by_years_and_wday(start_year, end_year, SATURDAY)
#=> [#<Date: 2015-01-03 ((2457026j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-01-10 ((2457033j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2017-12-30 ((2458118j,0s,0n),+0s,2299161j)>]
dates_by_years_and_wday(start_year, end_year, SATURDAY).size
#=> 157
dates_by_years_and_wday(start_year, end_year, SUNDAY)
#=> [#<Date: 2015-01-04 ((2457027j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-01-11 ((2457034j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2017-12-31 ((2458119j,0s,0n),+0s,2299161j)>]
dates_by_years_and_wday(start_year, end_year, SUNDAY).size
#=> 157

Select object based on date range

Tech specs: ruby 2.1.5p273, Rails 4.2.3.
I have an array of Days that I want to loop through to pick the right Exits (model) that fall within a date range.
#exits has :start_date and :end_date
#days is an array of dates like:
=> [Sun, 06 Sep 2015, Sat, 12 Sep 2015, Tue, 15 Sep 2015, Fri, 18 Sep 2015, Sat, 19 Sep 2015, Sun, 20 Sep 2015, Wed, 23 Sep 2015]
I thought something like this would work:
#days.each do |day|
#exits.where(:start_date..:end_date).include?(day)
end
but I get an error:
TypeError: Cannot visit Range
What is the best way to query an object that has a date range (between two fields) by comparing it against a single date? Thanks!
You can use the following:
#days.each do |day|
exits = Exit.where('? BETWEEN start_date AND end_date', day)
# etc.
end
If you don't want to loop over them then you can do:
Event.where("start_date IN (:days) AND end_date IN (:days)", { days: #days })
or
Event.where(start_date: #days, end_date: #days)
Exit.where(day: #exit.start_date..#exits.end_date)
or
Exit.where('day >= ? AND day <= ?', #exit.start_date, #exits.end_date)
Doing SQL queries in a loop is probably a bad idea, it could be refactored to be be one call most likely. And this should happen in the controller not in the view.

Difference between Rails 2.3 and Rails 3.2 'weeks' method

I've noticed some different behaviour between Rails 2 and Rails 3 when it comes to ActiveSupport date handling.
When I run the following code in a Rails 2.3 application it runs as I expect and outputs the dates one week at a time.
>> first = Date.today
=> Fri, 23 Mar 2012
>> last = Date.today + 2.months
=> Wed, 23 May 2012
>> first.step(last, 1.week) { |date| puts date }
2012-03-23
2012-03-30
2012-04-06
2012-04-13
2012-04-20
2012-04-27
2012-05-04
2012-05-11
2012-05-18
When I try the same code within a Rails 3 application I get the following.
>> first = Date.today
=> Fri, 23 Mar 2012
>> last = Date.today + 2.months
=> Wed, 23 May 2012
>> first.step(last, 1.week) { |date| puts date }
Mar 23, 2012
TypeError: expected numeric
The problems seems to be with how Rails 3 is now handling the .weeks method, Rails 2 outputs the following
>> 1.week
=> 7 days
Where Rails 3 outputs
>> 1.week
=> 604800
Can anyone explain what is going on here and how I can neatly iterate over a date range one week at a time in Rails 3.
No idea why it doesn't work, but this seems to:
(Date.today..(Date.today + 30)).step(7)

How do I calculate the next annual occurrence of a date?

Given a Ruby date, does a one liner exist for calculating the next anniversary of that date?
For example, if the date is May 01, 2011 the next anniversary would be May 01, 2012, however if it is December 01, 2011, the next anniversary is December 01, 2011 (as that date hasn't yet arrived).
If you date variable is an instance of Date then you can use >>:
Return a new Date object that is n months later than the current one.
So you could do this:
one_year_later = date >> 12
The same approach applies to DateTime. If all you have is a string, then you can use the parse method:
next_year = Date.parse('May 01, 2011') >> 12
next_year_string = (Date.parse('May 01, 2011') >> 12).to_s
IMHO you're better off using the date libraries (Date and DateTime) as much as possible but you can use the Rails extensions (such as 1.year) if you know that Rails will always be around or you don't mind manually pulling in active_support as needed.
An excellent gem exists for doing this called recurrence. You can checkout the source code or some samples:
https://github.com/fnando/recurrence
http://blog.plataformatec.com.br/tag/recurrence/
For example, if you have a date set you could try:
date = ...
recurrence = Recurrence.new(every: :year, on: [date.month, date.day])
puts recurrence.next
You can do it using Ruby's Date class:
the_date = Date.parse('jan 1, 2011')
(the_date < Date.today) ? the_date + 365 : the_date # => Sun, 01 Jan 2012
the_date = Date.parse('dec 31, 2011')
(the_date < Date.today) ? the_date.next_year : the_date # => Sat, 31 Dec 2011
Or, for convenience use ActiveSupport's Date class extensions:
require 'active_support/core_ext/date/calculations'
the_date = Date.parse('jan 1, 2011')
(the_date < Date.today) ? the_date.next_year : the_date # => Sun, 01 Jan 2012
the_date = Date.parse('dec 31, 2011')
(the_date < Date.today) ? the_date.next_year : the_date # => Sat, 31 Dec 2011
Try this:
def next_anniversary(d)
Date.today > d ? 1.year.from_now(d) : d
end
Pulling in a gem just to do this is overkill.
your_date > Date.today ? your_date : your_date >> 12

Is it possible to create a list of months between two dates in Rails

I am trying to create a page to display a list of links for each month, grouped into years. The months need to be between two dates, Today, and The date of the first entry.
I am at a brick wall, I have no idea how to create this.
Any help would be massively appriciated
Regards
Adam
Just put what you want inside a range loop and use the Date::MONTHNAMES array like so
(date.year..laterdate.year).each do |y|
mo_start = (date.year == y) ? date.month : 1
mo_end = (laterdate.year == y) ? laterdate.month : 12
(mo_start..mo_end).each do |m|
puts Date::MONTHNAMES[m]
end
end
The following code will add a months_between instance method to the Date class
#!/usr/bin/ruby
require 'date'
class Date
def self.months_between(d1, d2)
months = []
start_date = Date.civil(d1.year, d1.month, 1)
end_date = Date.civil(d2.year, d2.month, 1)
raise ArgumentError unless d1 <= d2
while (start_date < end_date)
months << start_date
start_date = start_date >>1
end
months << end_date
end
end
This is VERY lightly tested, however it returns an Array of dates each date being the 1st day in each affected month.
I don't know if I've completely understood your problem, but some of the following might be useful. I've taken advantage of the extensions to Date provided in ActiveSupport:
d1 = Date.parse("20070617") # => Sun, 17 Jun 2007
d2 = Date.parse("20090529") #=> Fri, 29 May 2009
eom = d1.end_of_month #=> Sat, 30 Jun 2007
mth_ends = [eom] #=> [Sat, 30 Jun 2007]
while eom < d2
eom = eom.advance(:days => 1).end_of_month
mth_ends << eom
end
yrs = mth_ends.group_by{|me| me.year}
The final line uses another handy extension: Array#group_by, which does pretty much exactly what it promises.
d1.year.upto(d2.year) do |yr|
puts "#{yrs[yr].min}, #{yrs[yr].max}"
end
2007-06-30, 2007-12-31
2008-01-31, 2008-12-31
2009-01-31, 2009-05-31
I don't know if the start/end points are as desired, but you should be able to figure out what else you might need.
HTH
Use the date_helper gem which adds the months_between method to the Date class similar to Steve's answer.
xmas = Date.parse("2013-12-25")
hksar_establishment_day = Date.parse("2014-07-01")
Date.months_between(xmas,hksar_establishment_day)
=> [Sun, 01 Dec 2013, Wed, 01 Jan 2014, Sat, 01 Feb 2014, Sat, 01 Mar 2014, Tue, 01 Apr 2014, Thu, 01 May 2014, Sun, 01 Jun 2014, Tue, 01 Jul 2014]

Resources