Iterate every month with date objects - ruby

So I have two ruby Date objects, and I want to iterate them every month. For example if I have Date.new(2008, 12) and Date.new(2009, 3), it would yield me 2008-12, 2009-1, 2009-2, 2009-3 (as Date objects of course). I tried using range, but it yields every day. I saw step method for Date however it only allows me to pass number of days (and each month has different number of those). Anyone have any ideas?

Here is something very Ruby:
first day of each month
(Date.new(2008, 12)..Date.new(2011, 12)).select {|d| d.day == 1}
It will give you an array of the first day for each month within the range.
last day of each month
(Date.new(2008, 12)..Date.new(2012, 01)).select {|d| d.day == 1}.map {|d| d - 1}.drop(1)
Just note that the end date needs to be the month after your end range.

I find that I need to do this sometimes when generating select lists of months. The key is the >> operator on Date, which advances the Date forward one month.
def months_between(start_month, end_month)
months = []
ptr = start_month
while ptr <= end_month do
months << ptr
ptr = ptr >> 1
end
months
end
results = months_between(Date.new(2008,12), Date.new(2009,3))
Of course, you can format the results however you like in the loop.
months << "#{Date::MONTHNAMES[ptr.month]} #{ptr.year}"
Will return the month name and year ("March 2009"), instead of the Date object. Note that the Date objects returned will be set on the 1st of the month.

I have added following method to Date class:
class Date
def all_months_until to
from = self
from, to = to, from if from > to
m = Date.new from.year, from.month
result = []
while m <= to
result << m
m >>= 1
end
result
end
end
You use it like:
>> t = Date.today
=> #<Date: 2009-11-12 (4910295/2,0,2299161)>
>> t.all_months_until(t+100)
=> [#<Date: 2009-11-01 (4910273/2,0,2299161)>, #<Date: 2009-12-01 (4910333/2,0,2299161)>, #<Date: 2010-01-01 (4910395/2,0,2299161)>, #<Date: 2010-02-01 (4910457/2,0,2299161)>]
Ok, so, more rubyish approach IMHO would be something along:
class Month<Date
def succ
self >> 1
end
end
and
>> t = Month.today
=> #<Month: 2009-11-13 (4910297/2,0,2299161)>
>> (t..t+100).to_a
=> [#<Month: 2009-11-13 (4910297/2,0,2299161)>, #<Month: 2009-12-13 (4910357/2,0,2299161)>, #<Month: 2010-01-13 (4910419/2,0,2299161)>, #<Month: 2010-02-13 (4910481/2,0,2299161)>]
But you would need to be careful to use first days of month (or implement such logic in Month)...

I came up with the following solution. It's a mixin for date ranges that adds an iterator for both years and months. It yields sub-ranges of the complete range.
require 'date'
module EnumDateRange
def each_year
years = []
if block_given?
grouped_dates = self.group_by {|date| date.year}
grouped_dates.each_value do |dates|
years << (yield (dates[0]..dates[-1]))
end
else
return self.enum_for(:each_year)
end
years
end
def each_month
months = []
if block_given?
self.each_year do |range|
grouped_dates = range.group_by {|date| date.month}
grouped_dates.each_value do |dates|
months << (yield (dates[0]..dates[-1]))
end
end
else
return self.enum_for(:each_month)
end
months
end
end
first = Date.parse('2009-01-01')
last = Date.parse('2011-01-01')
complete_range = first...last
complete_range.extend EnumDateRange
complete_range.each_year {|year_range| puts "Year: #{year_range}"}
complete_range.each_month {|month_range| puts "Month: #{month_range}"}
Will give you:
Year: 2009-01-01..2009-12-31
Year: 2010-01-01..2010-12-31
Month: 2009-01-01..2009-01-31
Month: 2009-02-01..2009-02-28
Month: 2009-03-01..2009-03-31
Month: 2009-04-01..2009-04-30
Month: 2009-05-01..2009-05-31
Month: 2009-06-01..2009-06-30
Month: 2009-07-01..2009-07-31
Month: 2009-08-01..2009-08-31
Month: 2009-09-01..2009-09-30
Month: 2009-10-01..2009-10-31
Month: 2009-11-01..2009-11-30
Month: 2009-12-01..2009-12-31
Month: 2010-01-01..2010-01-31
Month: 2010-02-01..2010-02-28
Month: 2010-03-01..2010-03-31
Month: 2010-04-01..2010-04-30
Month: 2010-05-01..2010-05-31
Month: 2010-06-01..2010-06-30
Month: 2010-07-01..2010-07-31
Month: 2010-08-01..2010-08-31
Month: 2010-09-01..2010-09-30
Month: 2010-10-01..2010-10-31
Month: 2010-11-01..2010-11-30
Month: 2010-12-01..2010-12-31

MonthRange.new(date1..date2).each { |month| ... }
MonthRange.new(date1..date2).map { |month| ... }
You can use all the Enumerable methods if you use this iterator class. I make it handle strings too so that it can take form inputs.
# Iterate over months in a range
class MonthRange
include Enumerable
def initialize(range)
#start_date = range.first
#end_date = range.last
#start_date = Date.parse(#start_date) unless #start_date.respond_to? :month
#end_date = Date.parse(#end_date) unless #end_date.respond_to? :month
end
def each
current_month = #start_date.beginning_of_month
while current_month <= #end_date do
yield current_month
current_month = (current_month + 1.month).beginning_of_month
end
end
end

Date.new(2014,1,1).upto(Date.today).map {|date| "#{date.to_s[0..-4]}"}.uniq
Will give you a string representation of each month including it's year.

As a helper method:
def iterate(d1, d2)
date = d1
while date <= d2
yield date
date = date >> 1
end
end
Usage:
start_date = Date.new(2008, 12)
end_date = Date.new(2009, 3)
iterate(start_date, end_date){|date| puts date}
Or, if you prefer to monkey patch Date:
class Date
def upto(end_date)
date = self
while date <= end_date
yield date
date = date >> 1
end
end
end
Usage:
start_date = Date.new(2008, 12)
end_date = Date.new(2009, 3)
start_date.upto(end_date){|date| puts date}

Welp, after lurking 15 years nearly this is my first stack overflow answer, I think.
start_date = Date.new(2000,12,15) # day is irrelevant and can be omitted
end_date = Date.new(2001,2,1). #same
(start_date.to_datetime..end_date.to_datetime).map{|d| [d.year, d.month]}.uniq.sort
# returns [[2000,12],[2001,1],[2001,2]]
(start_date.to_datetime..end_date.to_datetime).map{|d| Date.new(d.year, d.month)}.uniq.sort
# returns an array of date objects for the first day of any month in the span

def each_month(date, end_date)
ret = []
(ret << date; date += 1.month) while date <= end_date
ret
end

Related

How to return an array of dates based on an interval within a date range [duplicate]

So I have two ruby Date objects, and I want to iterate them every month. For example if I have Date.new(2008, 12) and Date.new(2009, 3), it would yield me 2008-12, 2009-1, 2009-2, 2009-3 (as Date objects of course). I tried using range, but it yields every day. I saw step method for Date however it only allows me to pass number of days (and each month has different number of those). Anyone have any ideas?
Here is something very Ruby:
first day of each month
(Date.new(2008, 12)..Date.new(2011, 12)).select {|d| d.day == 1}
It will give you an array of the first day for each month within the range.
last day of each month
(Date.new(2008, 12)..Date.new(2012, 01)).select {|d| d.day == 1}.map {|d| d - 1}.drop(1)
Just note that the end date needs to be the month after your end range.
I find that I need to do this sometimes when generating select lists of months. The key is the >> operator on Date, which advances the Date forward one month.
def months_between(start_month, end_month)
months = []
ptr = start_month
while ptr <= end_month do
months << ptr
ptr = ptr >> 1
end
months
end
results = months_between(Date.new(2008,12), Date.new(2009,3))
Of course, you can format the results however you like in the loop.
months << "#{Date::MONTHNAMES[ptr.month]} #{ptr.year}"
Will return the month name and year ("March 2009"), instead of the Date object. Note that the Date objects returned will be set on the 1st of the month.
I have added following method to Date class:
class Date
def all_months_until to
from = self
from, to = to, from if from > to
m = Date.new from.year, from.month
result = []
while m <= to
result << m
m >>= 1
end
result
end
end
You use it like:
>> t = Date.today
=> #<Date: 2009-11-12 (4910295/2,0,2299161)>
>> t.all_months_until(t+100)
=> [#<Date: 2009-11-01 (4910273/2,0,2299161)>, #<Date: 2009-12-01 (4910333/2,0,2299161)>, #<Date: 2010-01-01 (4910395/2,0,2299161)>, #<Date: 2010-02-01 (4910457/2,0,2299161)>]
Ok, so, more rubyish approach IMHO would be something along:
class Month<Date
def succ
self >> 1
end
end
and
>> t = Month.today
=> #<Month: 2009-11-13 (4910297/2,0,2299161)>
>> (t..t+100).to_a
=> [#<Month: 2009-11-13 (4910297/2,0,2299161)>, #<Month: 2009-12-13 (4910357/2,0,2299161)>, #<Month: 2010-01-13 (4910419/2,0,2299161)>, #<Month: 2010-02-13 (4910481/2,0,2299161)>]
But you would need to be careful to use first days of month (or implement such logic in Month)...
I came up with the following solution. It's a mixin for date ranges that adds an iterator for both years and months. It yields sub-ranges of the complete range.
require 'date'
module EnumDateRange
def each_year
years = []
if block_given?
grouped_dates = self.group_by {|date| date.year}
grouped_dates.each_value do |dates|
years << (yield (dates[0]..dates[-1]))
end
else
return self.enum_for(:each_year)
end
years
end
def each_month
months = []
if block_given?
self.each_year do |range|
grouped_dates = range.group_by {|date| date.month}
grouped_dates.each_value do |dates|
months << (yield (dates[0]..dates[-1]))
end
end
else
return self.enum_for(:each_month)
end
months
end
end
first = Date.parse('2009-01-01')
last = Date.parse('2011-01-01')
complete_range = first...last
complete_range.extend EnumDateRange
complete_range.each_year {|year_range| puts "Year: #{year_range}"}
complete_range.each_month {|month_range| puts "Month: #{month_range}"}
Will give you:
Year: 2009-01-01..2009-12-31
Year: 2010-01-01..2010-12-31
Month: 2009-01-01..2009-01-31
Month: 2009-02-01..2009-02-28
Month: 2009-03-01..2009-03-31
Month: 2009-04-01..2009-04-30
Month: 2009-05-01..2009-05-31
Month: 2009-06-01..2009-06-30
Month: 2009-07-01..2009-07-31
Month: 2009-08-01..2009-08-31
Month: 2009-09-01..2009-09-30
Month: 2009-10-01..2009-10-31
Month: 2009-11-01..2009-11-30
Month: 2009-12-01..2009-12-31
Month: 2010-01-01..2010-01-31
Month: 2010-02-01..2010-02-28
Month: 2010-03-01..2010-03-31
Month: 2010-04-01..2010-04-30
Month: 2010-05-01..2010-05-31
Month: 2010-06-01..2010-06-30
Month: 2010-07-01..2010-07-31
Month: 2010-08-01..2010-08-31
Month: 2010-09-01..2010-09-30
Month: 2010-10-01..2010-10-31
Month: 2010-11-01..2010-11-30
Month: 2010-12-01..2010-12-31
MonthRange.new(date1..date2).each { |month| ... }
MonthRange.new(date1..date2).map { |month| ... }
You can use all the Enumerable methods if you use this iterator class. I make it handle strings too so that it can take form inputs.
# Iterate over months in a range
class MonthRange
include Enumerable
def initialize(range)
#start_date = range.first
#end_date = range.last
#start_date = Date.parse(#start_date) unless #start_date.respond_to? :month
#end_date = Date.parse(#end_date) unless #end_date.respond_to? :month
end
def each
current_month = #start_date.beginning_of_month
while current_month <= #end_date do
yield current_month
current_month = (current_month + 1.month).beginning_of_month
end
end
end
Date.new(2014,1,1).upto(Date.today).map {|date| "#{date.to_s[0..-4]}"}.uniq
Will give you a string representation of each month including it's year.
As a helper method:
def iterate(d1, d2)
date = d1
while date <= d2
yield date
date = date >> 1
end
end
Usage:
start_date = Date.new(2008, 12)
end_date = Date.new(2009, 3)
iterate(start_date, end_date){|date| puts date}
Or, if you prefer to monkey patch Date:
class Date
def upto(end_date)
date = self
while date <= end_date
yield date
date = date >> 1
end
end
end
Usage:
start_date = Date.new(2008, 12)
end_date = Date.new(2009, 3)
start_date.upto(end_date){|date| puts date}
Welp, after lurking 15 years nearly this is my first stack overflow answer, I think.
start_date = Date.new(2000,12,15) # day is irrelevant and can be omitted
end_date = Date.new(2001,2,1). #same
(start_date.to_datetime..end_date.to_datetime).map{|d| [d.year, d.month]}.uniq.sort
# returns [[2000,12],[2001,1],[2001,2]]
(start_date.to_datetime..end_date.to_datetime).map{|d| Date.new(d.year, d.month)}.uniq.sort
# returns an array of date objects for the first day of any month in the span
def each_month(date, end_date)
ret = []
(ret << date; date += 1.month) while date <= end_date
ret
end

How to get current and previous financial year Ruby?

How to get current and previous financial year datewise in ruby based on current date ?
current_month = Time.current.month
current_year = current_month > 3 ? Time.current.year :Time.current.year - 1
start_date = '/04/01'
end_date = '/03/31'
current_financial_year = [current_year.to_s + start_date , (current_year + 1).to_s + end_date]
previous_financial_year = [(current_year-1).to_s + start_date, current_year.to_s + end_date]
Financial years are defined differently in different countries (see: Wikipedia). In your country, the following might work. Keep in mind that you need to adjust the calculation when you want to support multiple jurisdictions.
def current_financial_year
year_range(current_financial_year_start)
end
def previous_financial_year
year_range(current_financial_year_start - 1.year)
end
private
def current_financial_year_start
date = Date.today
date.change(year: date.year - 1) if date.month < 4
date.change(month: 4).beginning_of_month
end
def year_range(date)
(date .. date + 1.year - 1.day)
end
Note that my methods return ranges instead of arrays which define the financial period.
The method will return fiscal year start ans end date
def get_fiscal_year_start(date)
date = date.change(year: date.year - 1) if date.month < 4
date.change(month: 4).beginning_of_month
end
def get_fiscal_year_end(date)
date = date.change(year: date.year + 1) if date.month > 3
date.change(month: 3).end_of_month
end

How to get start and end dates from a time/date object created using a month or year in ruby?

I have ISO 8601 compliant date strings like "2016" or "2016-09" representing year or months. How can I get start end dates from this.
for example:
2016 -> ["2016-01-01", "2016-12-31"]
2016-09 -> ["2016-09-01", "2016-09-30"]
Thank you
Try this
require 'date'
def iso8601_range(str)
parts = str.scan(/\d+/).map(&:to_i)
date = Date.new(*parts)
case parts.size
when 1
date .. date.next_year - 1
when 2
date .. date.next_month - 1
else
date .. date
end
end
iso8601_range('2016') # => 2016-01-01..2016-12-31
iso8601_range('2016-09') # => 2016-09-01..2016-09-30
iso8601_range('2016-09-20') # => 2016-09-20..2016-09-20
If you are cool with using send you can replace the case statement with
date .. date.send([:next_year,:next_month,:next_day][parts.size - 1]) - 1
require 'date'
def create_start_end(string)
year, month = string.split('-').map(&:to_i)
if month && !month.zero?
[Date.new(year, month, 1).to_s, Date.new(year, month, -1).to_s]
else
[Date.new(year, 1, 1).to_s, Date.new(year, 12, -1).to_s]
end
end
create_start_end('2016')
#=> ["2016-01-01", "2016-12-31"]
create_start_end('2016-01')
#=> ["2016-01-01", "2016-01-31"]
create_start_end('2016-09')
#=> ["2016-09-01", "2016-09-30"]
One more solution in according to #AndreyDeineko :)
require 'date'
def create_date date
date = date.split('-').map(&:to_i)
[Date.new(*date, 1, 1), Date.new(*date, -1, -1)].map(&:to_s)
end

Convert year and day of year to calender date in Ruby

Need to convert "15307" in to something like "2015-11-03"
I have the following code
def juliantoregular(date1)
temp = "20" + date1[0,2];
year = temp.to_i;
if date1[2,1] == "0"
temp1 = date1[3,2];
else
temp1 = date1[2,3];
end
juliannumber = temp1.to_i;
date1 = Date.ordinal(year,juliannumber)
return date1;
end
Input to the code is String "15307" is there a better way of doing this?
Date.strptime is perfect for this:
str = "15307"
puts Date.strptime(str, "%y%j")
# => 2015-11-03
In the format string "%y%j", %y indicates a two-digit year and %j indicates day of the year.
y, d = "15307".to_i.divmod 1_000 #⇒ [15, 307]
Date.parse("20#{y}-01-01") + (d - 1) #⇒ add days to Jan, 1st
#⇒ #<Date: 2015-11-03 ((2457330j,0s,0n),+0s,2299161j)>

get no of months, years between two dates in ruby

I'm trying to write web ui tests to choose date from jquery calender based on user input (watir-webdriver), how can find no of months years between two give dates, i searched few solution couldn't get what i want
date1 = Date::strptime("2013-09-19", "%Y-%m-%d")
date2 = Date::strptime("2013-09-25", "%Y-%m-%d")
date3 = Date::strptime("2013-10-01", "%Y-%m-%d")
date4 = Date::strptime("2014-01-20", "%Y-%m-%d")
date5 = Date::strptime("2014-12-01", "%Y-%m-%d")
desired output
diff between date1,date2 -- 0 yrs, 0 month(s)
diff between date1,date3 -- 0 yrs, 1 month(s)
diff between date1,date4 -- 0 yrs, 4 month(s)
diff between date1,date5 -- 1 yrs, 3 month(s)
i checked time_diff gem also
I'd calculate the difference in months (be aware that we ignore day differences here) and then calculate the number of years by dividing that number by 12:
##
# Calculates the difference in years and month between two dates
# Returns an array [year, month]
def date_diff(date1,date2)
month = (date2.year * 12 + date2.month) - (date1.year * 12 + date1.month)
month.divmod(12)
end
date_diff date1, date4 #=> [0, 4]
date_diff date1, date2 #=> [0, 0]
date_diff date1, date3 #=> [0, 1]
date_diff date1, date5 #=> [1, 3]
Here is my attempt. Works in multiple units:
def date_diff(date1, date2, units=:months)
seconds_between = (date2.to_i - date1.to_i).abs
days_between = seconds_between / 60 / 60 / 24
case units
when :days
days_between.floor
when :months
(days_between / 30).floor
when :years
(days_between / 365).floor
else
seconds_between.floor
end
end
Usage:
date_diff(Time.now, 10.years.ago - 77.days, :years) #=> 10
date_diff(10.years.ago - 77.days, Time.now, :months) #=> 124
date_diff(10.years.ago - 77.days, Time.now, :days) #=> 3730
I took this from the TimeDifference gem but it works so nicely that I thought I'd share. If you're using Rails, make a class called TimeDifference with the following code:
class TimeDifference
private_class_method :new
def self.between(start_time:, end_time:)
new(start_time, end_time)
end
def in_seconds
#time_diff
end
def in_minutes
in_component(:minutes)
end
def in_hours
in_component(:hours)
end
def in_days
in_component(:days)
end
def in_weeks
in_component(:weeks)
end
def in_months
(#time_diff / (1.day * 30.42)).round(2)
end
def in_years
in_component(:years)
end
private
def initialize(start_time, end_time)
start_time = time_in_seconds(start_time)
end_time = time_in_seconds(end_time)
#time_diff = (end_time - start_time).abs
end
def time_in_seconds(time)
time.to_time.to_f
end
def in_component(component)
(#time_diff / 1.send(component)).round(2)
end
end
And then simply call:
start_time = DateTime.parse('2 June, 1999 9:00:00')
end_time = DateTime.parse('19 April, 2021 9:00:00')
time_difference = TimeDifference.between(
start_time: start_time,
end_time: end_time
)
time_difference.in_days
=> 7992.0
time_difference.in_months
=> 262.72
time_difference.in_years
=> 21.88
Note: if you're not using Rails you might have to require ActiveSupport.

Resources