Get the last Sunday of the Month
Asked Answered
A

5

7

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.

Achates answered 31/7, 2013 at 14:31 Comment(9)
A pretty cheesy idea, but you could start at the first day of April and then just keep walking backwards in a loop of "yesterday" and break out when you get the first Sunday...Abigailabigale
@Abigailabigale just implemented that idea while you wrote your comment :)Tshombe
@aardvarkk: For that matter, you can write Chronic.parse('1 week before 1st sunday in april').Shastashastra
@ruakh: I would have suggested you make that an answer, because I would have accepted it, but it returns nil.Achates
@iconoclast: Oops, sorry. The documentation gives the example 7 hours before tomorrow at noon, so I assumed the [interval] before [...] notation would work more generally. My bad.Shastashastra
@ruakh: I think you've uncovered yet another limitation of Chronic... I think we need to submit patches to fix these! :)Achates
About your update: You can (or may) write this with Chronic in a single line, because you just call a function. However, using that argument you are basically saying that you can call a ruby function with a single line of code. Chronic probably need more than one line to implement this ;)Tshombe
@Tshombe Yes I realize it's not an apples-to-apples comparison, but I'm looking for a way to dramatize to management the fact that Ruby is clean, readable, and concise. A single line of Ruby to replace a mountain of Java can do that. It's a single line of our code (they're paranoid about the supposed cost of Rubyists), so as long as it uses a well-maintained library, I think it's still a reasonable point to make.Achates
@Achates I am counting three one-line solutions now (2 of them without a gem). I hope this (and the feedback you get here from other devs) can convince your management :)Tshombe
A
3

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.

Achates answered 31/7, 2013 at 20:37 Comment(2)
I know this is an old post, but it fails if the last day of month is a sunday. Chronic.parse('last sunday', now: Date.new(2017,12,31))Greeting
From my previous comment, if you know the end of the month, just add 1 day: Chronic.parse('last sunday', now: Date.new(2017,12,31)+1)Greeting
T
10

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)>
Tshombe answered 31/7, 2013 at 14:53 Comment(4)
Rather than while date.wday != 0 date -= 1 end, why not just date -= date.wday?Shastashastra
(d = Date.new(2013, 7, -1)) - d.wday would be the one line versionTuttifrutti
Thanks @Tuttifrutti - I am not sure if this is more readable (=maintainable), but the OP wished one-line solutions - so this is great :)Tshombe
Hmm... I liked the 2 line version better.Tuttifrutti
T
4

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)>
Tshombe answered 31/7, 2013 at 19:17 Comment(3)
quite brilliant, but I'm not going to be able to convince them that a non-Rubyist will be able to maintain this, and (e.g.) change it to "first monday in January" if the need arises.Achates
Let non-Rubyists maintain (more than a couple of lines of) ruby code is like parsing HTML using regexes. It's like "asking Paris Hilton to write an operating system"Tshombe
Brilliant comparison. But you see what I'm up against. :)Achates
D
3

What about

Chronic.parse('last sunday', now: Chronic.parse('last day of march'))
Damar answered 31/7, 2013 at 19:16 Comment(5)
Nice idea to use Chronic to get :now, but this doesn't work. First of all Chronic parses last day of march as 2013-03-01 12:00:00 -0500, and secondly, I still need to specify an arbitrary year. The second problem is solved by passing a :now value to the embedded Chronic expression.Achates
You could also set the year with Chronic. And I just sent a pull request to be able to pass a string for the :now option github.com/mojombo/chronic/pull/199Damar
so one could do Chronic.parse('last sunday', now: '31 march, 2015')Damar
Yes, that will be a big improvement when they accept your pull request. It's ironic that a library whose whole point is to parse strings to interpret dates won't do that on one of its own parameters.Achates
Well, maybe it's because no one else needed it. I guess you know upfront the date for the now. I guess this is a special case, but I think it looks great just passing a string.Damar
A
3

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.

Achates answered 31/7, 2013 at 20:37 Comment(2)
I know this is an old post, but it fails if the last day of month is a sunday. Chronic.parse('last sunday', now: Date.new(2017,12,31))Greeting
From my previous comment, if you know the end of the month, just add 1 day: Chronic.parse('last sunday', now: Date.new(2017,12,31)+1)Greeting
H
1

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
Hardshell answered 31/7, 2013 at 14:58 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.