Get the last Sunday of the Month - ruby

I'm using Chronic to get the last Sunday of the month of any given year. It will gladly give me the n‌th Sunday, but not the last.
This works, but is not what I need:
Chronic.parse('4th sunday in march', :now => Time.local(2015,1,1))
This is what I need, but doesn't work:
Chronic.parse('last sunday in march', :now => Time.local(2015,1,1))
Is there any way around this apparent limitation?
UPDATE: I'm upvoting the two answers below because they're both good, but I've already implemented this in "pure Ruby" (in 2 lines of code, besides the require 'date' line), but I'm trying to demonstrate to management that Ruby is the right language to use to replace a Java codebase that is going away (and which had dozens of lines of code to compute this), and I told one manager that I probably could do it in one line of Ruby, and it would be readable and easy to maintain.

I am not sure about Chronic (I haven't heared about it before), but we can implement this in pure ruby :)
##
# returns a Date object being the last sunday of the given month/year
# month: integer between 1 and 12
def last_sunday(month,year)
# get the last day of the month
date = Date.new year, month, -1
#subtract number of days we are ahead of sunday
date -= date.wday
end
The last_sunday method can be used like this:
last_sunday 07, 2013
#=> #<Date: 2013-07-28 ((2456502j,0s,0n),+0s,2299161j)>

Reading the update in your question, I tried to come up with another answer using only a single line of ruby code (without using gems). How about this one?
##
# returns a Date object being the last sunday of the given month/year
# month: integer between 1 and 12
def last_sunday(month,year)
# get the last day of the month, go back until we have a sunday
Date.new(year, month, -1).downto(0).find(&:sunday?)
end
last_sunday 07, 2013
#=> #<Date: 2013-07-28 ((2456502j,0s,0n),+0s,2299161j)>

What about
Chronic.parse('last sunday', now: Chronic.parse('last day of march'))

This works, and is as readable as I can get:
Chronic.parse('last sunday', now: Date.new(year,3,31))
Thanks to Ismael Abreu for the idea to just parse 'last sunday' and control the rest via the :now option.
UPDATE: Please also upvote Ismael's answer.

It's a bit ugly, but you could simply try the 5th or 4th in that order:
d = [5,4].each do |i|
try = Chronic.parse("#{i}th sunday in march", :now => Time.local(2015,1,1))
break try unless try.nil?
end
=> Sun Mar 29 12:30:00 +0100 2015
d = [5,4].each do |i|
try = Chronic.parse("#{i}th sunday in april", :now => Time.local(2015,1,1))
break try unless try.nil?
end
=> Sun Apr 26 12:00:00 +0100 2015

Related

Ruby: how to find a date by the year day

How is it possible to find the date based on the current year's day?
For example, you have the year's day 235, which date does it corresponds to?
For example, you can find a year's day as follows:
Date.today.yday
#=> 126 (for May 6, 2021)
How to achieve the opposite?
Date.some_method(126)
#=> 2021-05-06
I tried as follows:
now = Date.today
#=> Thu, 06 May 2021
start_year = now.beginning_of_year
#=> Fri, 01 Jan 2021
start.advance(days: (start.yday + 126))
#=> Sat, 08 May 2021
Why there is a difference of 2 days?
This should work (pure ruby):
def day_of_year(num)
Date.new(Date.today.year) + num - 1
end
day_of_year(1) #=> 01/01/2021
day_of_year(235) #=> 23/08/2021
Another option:
def day_of_year(num)
Date.strptime(num.to_s, "%j")
end
day_of_year(1) #=> 01/01/2021
day_of_year(235) #=> 23/08/2021
Note: #steenslag posted the actually correct answer - please go and upvote.
Date.ordinal gives you the date, given the year and the number. You can even specify a negative number, which would calculate the date counting backwards from the end of the year. doc
Try This.
Date.today - (Date.today.yday - 126)

Iterate over days, starting from x date through an end date

I need to start from, for example, January 1 2013, and "do some things" for each date, resulting in a JSON file for each date.
I have the "do some things" part worked out for a single date, but I'm having a hard time starting at a date and looping through to another end date.
You can use ranges :
(Date.new(2012, 01, 01)..Date.new(2012, 01, 30)).each do |date|
# Do stuff with date
end
or (see #awendt answer)
Date.new(2012, 01, 01).upto(Date.new(2012, 01, 30)) do |date|
# Do stuff with date
end
You could use:
first.upto(last) do |date|
where first and last are Date objects.
See what I did here in a project of mine, for example.

How to Test an Array of Date Objects Is Correct

I'm trying to
I take an array like this:
MY_ARRAY = ["Jan 31, 2013", "Feb 28, 2013", "Mar 31, 2013"]
and then turn it into dates:
def array_as_date_objects
MY_ARRAY.map { |month| Date.parse(month)}
end
which yields this:
[#<Date: 2013-01-31 (4912645/2,0,2299161)>, #<Date: 2013-02-28 (4912703/2,0,2299161)>, #<Date: 2013-03-31 (4912763/2,0,2299161)>]
Great. But I also want to test that this array of date objects is what I expect it to be. How would I do that?
I had hoped something like this would work, but the hashtag comments out the remaining info.
MyClass.new.refined_schedule.should == :[#<Date: 2013-01-31 (4912645/2,0,2299161)>, #<Date: 2013-02-28 (4912703/2,0,2299161)>, #<Date: 2013-03-31 (4912763/2,0,2299161)>]
Edit: So just to give more info on my situation, my main concern is really how to stub this array. I don't need to trust that Ruby created the array correctly, I just need to stub the method correctly. Sorry for not being clear.
describe "#final_schedule" do
it "generates an array of pay dates that do not fall on any weekend day" do
test_schedule = MyClass.new
test_schedule.stub(:refined_schedule) { [#<Date: 2013-01-31 (4912645/2,0,2299161)>, #<Date: 2013-02-28 (4912703/2,0,2299161)>, #<Date: 2013-03-31 (4912763/2,0,2299161)> ] }
test_schedule.final_schedule.should == [ "Thursday, Jan 31, 2013", "Thursday, Feb 28, 2013","Sunday, Mar 31, 2013"]
end
end
#<Date: 2013-01-31 (4912645/2,0,2299161)>
This is a printable representation of an object. You cannot just write it and hope that ruby understands it as a date. Date object is created with constructor:
Date.new( 2013, 1, 31 )
So if you want to test if parsing result was right (which sometimes is necessary, for example for these formats: mm/dd/yy vs dd/mm/yy), you should do something like that:
MyClass.new.refined_schedule.should == [Date.new( 2013, 1, 31 ), Date.new( 2013, 2, 28 ),..]
When I write a test of my code, I look to see if I got an array back, and that all the contents of that array are of a certain type.
Whether those individual elements are correctly converted from strings to DateTime objects via parsing, doesn't concern me because that particular test occurs during the tests of the language when it's compiled and is part of the unit tests for DateTime.
Sure, we can be completely paranoid, or anal, and test every aspect of the code from our methods down to the atomic level, and be convinced everything works correctly, or we can trust that the language's developers are doing their part, and build upon that. So far I haven't had a reason to suspect they are failing on their part, so I can take the easier path.

Finding the date for a given week number

I am trying to do some date math based on the week number of a given year. For example:
date = Date.today # Monday, March 5, 2012
puts date.cwyear # 2012
puts date.cweek # 10 (10th week of 2012)
Now that I know what the current week is, I want to figure out what the next week and previous week are. I need to take the year (2012) and the week number (10) and turn it back into a date object so I can calculate the value for the next/previous week. How can I do this?
You want Date.commercial:
require 'date'
now = Date.today #=> 2012-03-05
monday_next_week = Date.commercial(now.cwyear,now.cweek+1) #=> 2012-03-12
next_sunday_or_today = monday_next_week - 1 #=> 2012-03-11
Note that weeks start on Monday, so if you are on a Sunday and ask for next monday - 1 you'll get the same day.
Note also that if you don't want Mondays you can also specify the day number in the method:
thursday_next_week = Date.commercial(now.cwyear,now.cweek+1,4) #=> 2012-03-15
Calculating on a day basis is pretty simple with Date objects. If you just want to get the previous / next week from a given Date object use the following:
date = Date.today
previous_week = (date - 7).cweek
next_week = (date + 7).cweek
In ActiveSupport you have helper to convert Fixnum to time http://as.rubyonrails.org/classes/ActiveSupport/CoreExtensions/Numeric/Time.html use:
date = Date.today
week_ago = date - 1.week
next_week = date + 1.week
I have created some methods to get week number of a given date
something like this:
def self.get_week(date)
year = date.year
first_monday_of_the_year = self.get_first_monday_of_the_year(year)
# The first days of January may belong to the previous year!
if date < first_monday_of_the_year
year -= 1
first_monday_of_the_year = self.get_first_monday_of_the_year(year)
end
day_difference = date - first_monday_of_the_year
week = (day_difference / 7).to_i + 1
week
end
def self.get_monday_of_year_week(year, week)
d = self.get_first_monday_of_the_year(year)
d + ((week - 1) * 7).days
end
def self.get_first_monday_of_the_year(year)
d = Date.new(year, 1, 7) # 7 Jan is always in the first week
self.get_monday_of_week(d)
end
def self.get_monday_of_week(date)
wday = (date.wday + 6) % 7
date - wday.days
end
Assuming you mean "a given week number in the current year", you can do the following:
2.weeks.since(Time.gm(Time.now.year))
=> Fri Jan 15 00:00:00 UTC 2010
Substitute (week_number - 1) for the 1 in the above, and you'll get a date in the desired week.

How to get the number of days in a given month in Ruby, accounting for year?

I'm sure there's a good simple elegant one-liner in Ruby to give you the number of days in a given month, accounting for year, such as "February 1997". What is it?
If you're working in Rails, chances are you'll get hamstrung eventually if you switch among Time, Date, and DateTime, especially when it comes to dealing with UTC/time zones, daylight savings, and the like. My experience has been it's best to use Time, and stick with it everywhere.
So, assuming you're using Rails's Time class, there are two good options, depending on context:
If you have a month m and year y, use the class method on Time:
days = Time.days_in_month(m, y)
If you have a Time object t, cleaner to ask the day number of the last day of the month:
days = t.end_of_month.day
require 'date'
def days_in_month(year, month)
Date.new(year, month, -1).day
end
# print number of days in February 2012
puts days_in_month(2012, 2)
This is the implementation from ActiveSupport (a little adapted):
COMMON_YEAR_DAYS_IN_MONTH = [nil, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
def days_in_month(month, year = Time.now.year)
return 29 if month == 2 && Date.gregorian_leap?(year)
COMMON_YEAR_DAYS_IN_MONTH[month]
end
How about:
require 'date'
def days_in_month(year, month)
(Date.new(year, 12, 31) << (12-month)).day
end
# print number of days in Feburary 2009
puts days_in_month(2009, 2)
You may also want to look at Time::days_in_month in Ruby on Rails.
In Rails project for current date
Time.days_in_month(Time.now.month, Time.now.year)
For any date t which is instance of Time
Time.days_in_month(t.month, t.year)
or
t.end_of_month.day
.
If you have UTC seconds, you need to get an instance of Time first
Time.at(seconds).end_of_month.day
For a given Date object I feel like the easiest is:
Date.today.all_month.count
Use Time.days_in_month(month) where month = 1 for January, 2 for Feb, etc.
revise the input for other format
def days_in_a_month(date = "2012-02-01")
date.to_date.end_of_month.day
end
as of rails 3.2... there's a built in version:
http://api.rubyonrails.org/classes/Time.html#method-c-days_in_month
(alas, it shows up after this answer, which takes folks on a long hike)
Time.now.end_of_month.day - for current month
Date.parse("2014-07-01").end_of_month.day - use date of first day in month.
Depends on ActiveSupport
A simple way using Date:
def days_of_month(month, year)
Date.new(year, month, -1).day
end
I think it's the simplest way to get it
def get_number_of_days(date = Date.today)
Date.new(date.year, date.month, -1).mday
end
Since the time is irrelevant for this purpose then you just need a date object:
[*Date.today.all_month].size

Resources