Related
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
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
I need to generate next month date from today. Should I manuallt check if month == 12 and add +1 to d.year or there is easy solution?
Time class did strange math:
>> d = Time.now
=> 2015-12-03 15:15:36 +0300
>> d.month
=> 12
>> d.month.next
=> 13
Date has a method next_month:
require 'date'
Date.today #=> #<Date: 2015-12-03 ((2457360j,0s,0n),+0s,2299161j)>
Date.today.next_month #=> #<Date: 2016-01-03 ((2457391j,0s,0n),+0s,2299161j)>
Date.today.next_month.month #=> 1
To convert a Time instance to a Date, use to_date.
Yes add+1
but do (this month + 1)%12
if ( thisMonth == 12)
nextMonth = 1;
else
nextMonth = thisMonth+1;
This is just to give an idea
I did not see for any edge cases yet
there are two commonly used possible solutions:
1: Date.today+1.month OR for month only (Date.today+1.month).month
2: Date.today.next_month OR for month only Date.today.next_month.month
In ruby, how can I get every 14th day of the year, going backwards and forwards from a date.
So consider I'm billed for 2 weeks of recycling on today, 6-16-2015. How can I get an array of every recycling billing day this year based on that date.
Date has a step method:
require 'date'
d = Date.strptime("6-16-2015", '%m-%d-%Y') # strange date format
end_year = Date.new(d.year, -1, -1)
p d.step(end_year, 14).to_a
# =>[#<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>, #<Date: 2015-06-30 ((2457204j,0s,0n),+0s,2299161j)>, ...
# Going backward:
begin_year = Date.new(d.year, 1, 1)
p d.step(begin_year,-14).to_a
# =>[#<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>, #<Date: 2015-06-02 ((2457176j,0s,0n),+0s,2299161j)>,...
A more descriptive and easy to understand solution:
require 'date'
current_date = Date.parse "16-june-15"
start_date = Date.parse '1-jan-15'
end_date = Date.parse '31-dec-15'
interval = 14
result = current_date.step(start_date, -interval).to_a
result.sort!.pop
result += current_date.step(end_date, interval).to_a
You could do that as follows:
require 'date'
date_str = "6-16-2015"
d = Date.strptime(date_str, '%m-%d-%Y')
f = Date.new(d.year)
((f + (f-d).abs % 14)..Date.new(d.year,-1,-1)).step(14).to_a
#=> [#<Date: 2015-01-13 ((2457036j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-01-27 ((2457050j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-06-16 ((2457190j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-12-29 ((2457386j,0s,0n),+0s,2299161j)>]
Based on the second sentence of your question, I assume you simply want an array of all dates in the given year that are two-weeks apart and include the given day.
I attempted a mathy modulus biased approach which turned out unexpectedly confusing.
require 'date'
a_recycle_date_string = "6-17-2015"
interval = 14
a_recycle_date = Date.strptime(a_recycle_date_string, '%m-%d-%Y')
current_year = a_recycle_date.year
end_of_year = Date.new(current_year, -1, -1)
# Find out which index of the first interval's days is the first recycle day
# of the year the (1 indexed)
remainder = (a_recycle_date.yday) % interval
# => 0
# make sure remainder 0 is treated as interval-1 so it doesn't louse
# the equation up
n_days_from_first_recycling_yday_of_year = (remainder - 1) % interval
first_recycle_date_this_year = Date.new(current_year,
1,
1 + n_days_from_first_recycling_yday_of_year)
first_recycle_date_this_year.step(end_of_year, interval).to_a
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