Ruby: how to find a date by the year day - ruby

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)

Related

In Ruby, is it possible to do arithmetic with symbols?

In Ruby, I'd like to be able to do something like this:
days_of_week = %i(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
today = :Sunday
today = today + 1
if today > :Saturday
today = :Sunday
end
This gives me
'undefined method '+' for :Sunday:Symbol (NoMethodError)
Can I define a method somehow?
I've looked at various stack overflow questions on enums, and didn't see what I'm looking for, but it's a large volume of information to sort through.
This code, as it is, does not work in ruby (as you noticed). Symbol :Sunday does not know that it's placed in an array, so adding a 1 to it does not make any sense.
You can compare symbols like that, but the results will surprise you. :Wednesday is greater than :Thursday, for example.
Why not just operate on indexes in the array. With integer indexes you can increment them and compare with expected results. Could look like this, for example:
today = days_of_week.index(:Friday)
puts "tomorrow is #{days_of_week[today + 1]}" # >> tomorrow is Saturday
This gives me undefined method `+' for :Sunday:Symbol (NoMethodError)
Can I define a method somehow?
In Ruby, you can open existing classes and add methods to them. Although it's not recommended to patch classes you don't own – let alone core classes – it's absolutely possible:
class Symbol
WEEKDAYS = %i(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
def +(offset)
wday = WEEKDAYS.index(self)
return unless wday
WEEKDAYS[(wday + offset) % WEEKDAYS.length]
end
end
:Sunday + 2
#=> :Tuesday
A less invasive approach is to define a custom class:
class WeekDay
attr_reader :name, :wday
def initialize(name, wday)
#name = name
#wday = wday
end
alias to_s name
alias inspect name
end
In addition to a name, each instance also knows its wday (0-6) which will simplify the lookup later on (so we don't have to scan the array to find the day's index).
For each day of the week I'd define a constant and also put all days in an array: (you could also use a loop and define them via const_set, I prefer to set them explicitly)
class WeekDay
module DayConstants
Sunday = WeekDay.new('Sunday', 0)
Monday = WeekDay.new('Monday', 1)
Tuesday = WeekDay.new('Tuesday', 2)
Wednesday = WeekDay.new('Wednesday', 3)
Thursday = WeekDay.new('Thursday', 4)
Friday = WeekDay.new('Friday', 5)
Saturday = WeekDay.new('Saturday', 6)
end
include DayConstants
WEEKDAYS = [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday].freeze
def +(offset)
WEEKDAYS[(wday + offset) % WEEKDAYS.length]
end
end
You might have wondered why I put the day constants in a separate module. It's so you can easily include them in your current namespace: (this is mostly cosmetic – you could just as well omit the include and write out today = WeekDay::Sunday)
include WeekDay::DayConstants
today = Sunday
today += 1
today #=> Monday
today += 4
today #=> Friday
today += 2
today #=> Sunday
To add comparison operators, you can include Comparable and implement <=>:
class WeekDay
include Comparable
def <=>(other)
wday <=> other.wday if other.is_a?(WeekDay)
end
# ...
end
Friday > Wednesday
#=> true
Wednesday.between?(Monday, Friday)
#=> true
I think I would create an index with as many entries as necessary to describe what you are trying to do.
Here is a hash of hashes to hold the index and next/prev logical day:
days_of_week = %i(Sunday Monday Tuesday Wednesday Thursday Friday Saturday)
idx=Hash.new {|h, k| h[k] = {} }
days_of_week.each_with_index{ |e,i|
idx[e][:idx]=i
idx[e][:next_day]=
(i+1)==days_of_week.length ? days_of_week[0] : days_of_week[i+1]
idx[e][:prev_day]=
i==0 ? days_of_week[-1] : days_of_week[i-1]
}
# {:Sunday=>{:idx=>0, :next_day=>:Monday, :prev_day=>:Saturday}, :Monday=>{:idx=>1, :next_day=>:Tuesday, :prev_day=>:Sunday}, :Tuesday=>{:idx=>2, :next_day=>:Wednesday, :prev_day=>:Monday}, :Wednesday=>{:idx=>3, :next_day=>:Thursday, :prev_day=>:Tuesday}, :Thursday=>{:idx=>4, :next_day=>:Friday, :prev_day=>:Wednesday}, :Friday=>{:idx=>5, :next_day=>:Saturday, :prev_day=>:Thursday}, :Saturday=>{:idx=>6, :next_day=>:Sunday, :prev_day=>:Friday}}
Then you can get the next day or previous day:
idx[:Tuesday][:next_day]
# Wednesday
idx[:Saturday][:next_day]
# Wraps around to Sunday
You can create a compare:
def cmp(x,y, idx)
idx[x][:idx]<=>idx[y][:idx]
end
So that :Wednesday is properly less than :Thursday
cmp(:Wednesday, :Thursday, idx)
-1
Or sort another array of symbols into the order of the index:
%i(Saturday Sunday Tuesday Thursday Wednesday Friday Monday).
sort_by{ |day| idx[day][:idx] }
# [:Sunday, :Monday, :Tuesday, :Wednesday, :Thursday, :Friday, :Saturday]
Those types of function can be added to a class to represent the type of symbols you want to deal with.

Get the last Sunday of the Month

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

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.

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