Group by month and week - ruby-on-rails

I would like to know what is the best way to group my hash of
{date->value} in month and week.
{Sat, 23 Apr 2016=>6.0, Mon, 06 Mar 2017=>9.0, Tue, 04 Apr 2017=>13.0, Tue, 11 Apr 2017=>25.0}
I would like a result like:
{Apr 2016=>6, Mar 2017=>9, Apr 2017=>38}
I tried with Groupdate/Chartkick but I am not getting a good result.
Thanks

Given the desired result for the OP's example, I've assumed that the OP means "month and year" rather than by "week and month".
I also assumed that the keys of the input hash are intended to be strings (rather than date objects) and the keys of the hash returned are intended to be strings. In any event, I would use Hash::new with a default value of zero (a counting hash). See the doc for details.
dates = {"Sat, 23 Apr 2016"=>6.0, "Mon, 06 Mar 2017"=>9.0,
"Tue, 04 Apr 2017"=>13.0, "Tue, 11 Apr 2017"=>25.0}
dates.each_with_object(Hash.new(0)) { |(k,v),h| h[k[-8..-1]] += v.to_i }
#=> {"Apr 2016"=>6, "Mar 2017"=>9, "Apr 2017"=>38}
If the dates of the input hash are date objects, we can do the following. First create the date objects.
require 'date'
dates = {"Sat, 23 Apr 2016"=>6.0, "Mon, 06 Mar 2017"=>9.0,
"Tue, 04 Apr 2017"=>13.0, "Tue, 11 Apr 2017"=>25.0}.
map { |str, v| [Date.parse(str), v] }.to_h
#=> {#<Date: 2016-04-23 ((2457502j,0s,0n),+0s,2299161j)>=>6.0,
# #<Date: 2017-03-06 ((2457819j,0s,0n),+0s,2299161j)>=>9.0,
# #<Date: 2017-04-04 ((2457848j,0s,0n),+0s,2299161j)>=>13.0,
# #<Date: 2017-04-11 ((2457855j,0s,0n),+0s,2299161j)>=>25.0}
Then we can write
dates.each_with_object(Hash.new(0)) { |(k,v),h| h[k.strftime("%b %Y")] += v.to_i }
#=> {"Apr 2016"=>6, "Mar 2017"=>9, "Apr 2017"=>38}
See DateTime#strftime.
This is a pure-Ruby solution.

If you have the hash of data (as a hash) already an option would be to use reduce
for the month it would be
data.reduce({}) do |memo, (date, value)|
new_date = date.beginning_of_month.strftime("%B %Y")
memo[new_date] ||= 0
memo[new_date] += value
memo
end
for the week it would be
data.reduce({}) do |memo, (date, value)|
new_date = date.beginning_of_week.strftime("%B %e %Y")
memo[new_date] ||= 0
memo[new_date] += value
memo
end
You can use this document to modify the strftime format however you want it.

Even easier: data.transform_keys! { |k| k.strftime("%B %Y") }

Related

How to get all 'specific days' within a date range

How can I get let's say All the dates for Saturday and Sunday from X year to Y year and store them as array? Pseudo code would be
(year_today..next_year).get_all_dates_for_saturday_and_sunday
Or perhaps there are gems that cater to this already?
Try this:
(Date.today..Date.today.next_year).select { |date|
date.sunday? or date.saturday?
}
#=> [Sat, 03 Sep 2016,Sun, 04 Sep 2016,Sat, 10 Sep 2016,Sun, 11 Sep 2016...
(Date.today..(Date.today + 1.year)).select do |date|
date.saturday? || date.sunday?
end # => [Sat, 03 Sep 2016, Sun, 04 Sep 2016, Sat, 10 Sep 2016, ...
This will then give you an array of 104 elements containing every date which is a saturday or a sunday between today and today in a year.
The following approach emphasizes efficiency over brevity, by avoiding the need to determine if every day in a range is a given day (or one of two given days) of the week.
Code
require 'date'
def dates_by_years_and_wday(start_year, end_year, wday)
(first_date_by_year_and_wday(start_year, wday)...
first_date_by_year_and_wday(end_year+1, wday)).step(7).to_a
end
def first_date_by_year_and_wday(year, wday)
d = Date.new(year)
d + (wday >= d.wday ? wday - d.wday : 7 + wday - d.wday)
end
Notice that the range is defined with three dots, meaning the first date in end_year is excluded.
Example
SATURDAY = 6
SUNDAY = 0
start_year, end_year = 2015, 2017
dates_by_years_and_wday(start_year, end_year, SATURDAY)
#=> [#<Date: 2015-01-03 ((2457026j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-01-10 ((2457033j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2017-12-30 ((2458118j,0s,0n),+0s,2299161j)>]
dates_by_years_and_wday(start_year, end_year, SATURDAY).size
#=> 157
dates_by_years_and_wday(start_year, end_year, SUNDAY)
#=> [#<Date: 2015-01-04 ((2457027j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-01-11 ((2457034j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2017-12-31 ((2458119j,0s,0n),+0s,2299161j)>]
dates_by_years_and_wday(start_year, end_year, SUNDAY).size
#=> 157

Finding days between 2 days in Ruby on Rails

I am facing some problem in finding the days between 2 dates.
The scenario is as follow :
time = Time.new
enddate_timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
startdate = #logInfo.updated_at #here updated_at is the column in the db .
What is the best way to find the days ?
Post.where(["date(created_at) BETWEEN ? AND ?", Date.yesterday, Date.tomorrow]
More details: http://api.rubyonrails.org/classes/ActiveRecord/QueryMethods.html#method-i-where
There are several possible solutions. A possibility is to create a Range with the dates, then convert the range into an array
# set the boundaries
today = Time.current
past = 5.days.ago
Note that both boundaries are time instances. We should cast them into dates. I used time(s) because your column is a time.
range = past.to_date..today.to_date
# => Sun, 29 Dec 2013..Fri, 03 Jan 2014
Use to_a to expand the range getting all the days
range.to_a
# => [Sun, 29 Dec 2013, Mon, 30 Dec 2013, Tue, 31 Dec 2013, Wed, 01 Jan 2014, Thu, 02 Jan 2014, Fri, 03 Jan 2014]
range.count
# => 6
You can also enumerate them
range.each { |day| puts day.day }
29
30
31
1
2
3
now = Time.now
future = Time.now + 100 days
while now < future
now = now + 1.day
puts now
end
This will give you the dates, not the days count.
(startdate.beginning_of_day..enddate_timestamp.to_time.beginning_of_day).step(1.day) do |day|
puts day
end
P.S: Performance wise it's not good.

rails helper function to return the most recent date

I need to Write rails helper method to return the most recent date.
So far this is my method
def latest_date(value_dates)
value_dates.each |value_date| do
my_dates << value_date
end
I need to sort the above array and return just the latest date.
The date is in the following format:
2012-10-10T22:11:52.000Z
Is there a sort method for date?
The .max method will do it for you ;)
> [Date.today, (Date.today + 2.days) ].max
#=> Fri, 05 Jul 2013
Documentation about it (Ruby 2.0):
http://ruby-doc.org/core-2.0/Enumerable.html#method-i-max
You may need to parse the data into Dates, if they are strings, you can use:
dates = ["2012-10-10T22:11:52.000Z", "2012-11-10T22:11:52.000Z", "2013-10-10T22:11:52.000Z"]
dates = dates.map{ |date_str| Date.parse(date_str) }
dates.max #=> returns the maximum date of the Array
See in my irb console (Ruby 1.9.3):
> dates = ["2012-10-10T22:11:52.000Z", "2012-11-10T22:11:52.000Z", "2013-10-10T22:11:52.000Z"]
#=> ["2012-10-10T22:11:52.000Z", "2012-11-10T22:11:52.000Z", "2013-10-10T22:11:52.000Z"]
> dates = dates.map{ |date_str| Date.parse(date_str) }
#=> [Wed, 10 Oct 2012, Sat, 10 Nov 2012, Thu, 10 Oct 2013]
> dates.max
#=> Thu, 10 Oct 2013
(use DateTime.parse(date_str) if you want to keep the Time also)

Given a DateTime, how do I get the range of times with the same date in a particular time zone?

I have a DateTime object representing a particular date, as in "2011-01-15 00:00:00 UTC" to represent January 15th. I would like to produce the range of times in a particular time zone that have the same date.
The method signature would probably be something like
def day_range_for(date, tz)
# ...
end
For example, if I have range_for(DateTime.parse('2011-01-15'), 'CST'), then I want the result to be a range like 2011-01-15 00:00:00 -0600 .. 2011-01-15 23:59:59 -0600.
Can you take the input string instead of the object? If you pass in a DateTime object, its a tad counter intuitive because the time the object represents isn't the actual time you are looking for. It would conform to my expectations if you either passed in the correct, absolute DateTime, or the string itself. The actual meat of the problem is entirely handled by ActiveSupport, which I think you are using since you tagged this question with Rails. How does this look?
def range_for(input, tz=nil)
if tz.is_a?(String)
tz = ActiveSupport::TimeZone.new(tz)
else
tz = Time.zone
end
if input.acts_like?(:date) || input.acts_like?(:time)
d = input.in_time_zone(tz)
else
d = tz.parse(input)
end
return d.beginning_of_day..d.end_of_day
end
Have a look:
ruby-1.9.2-p0 > range_for('2011-01-15', 'Alaska')
=> Sat, 15 Jan 2011 00:00:00 AKST -09:00..Sat, 15 Jan 2011 23:59:59 AKST -09:00
ruby-1.9.2-p0 > range_for(Time.zone.now)
=> Mon, 10 Jan 2011 00:00:00 EST -05:00..Mon, 10 Jan 2011 23:59:59 EST -05:00
ruby-1.9.2-p0 > range_for('2011-01-15', 'EST')
=> Sat, 15 Jan 2011 00:00:00 EST -05:00..Sat, 15 Jan 2011 23:59:59 EST -05:00
How about:
def day_range_for date, zone
(DateTime.new(date.year,date.month,date.day,0,0,0,zone)..DateTime.new(date.year,date.month,date.day,23,59,59,zone))
end
day_range_for(DateTime.parse('2011-01-15'), 'CST')
#=> #<DateTime: 2011-01-15T00:00:00-06:00 (9822307/4,-1/4,2299161)>..#<DateTime: 2011-01-15T23:59:59-06:00 (212161917599/86400,-1/4,2299161)>

Is it possible to create a list of months between two dates in Rails

I am trying to create a page to display a list of links for each month, grouped into years. The months need to be between two dates, Today, and The date of the first entry.
I am at a brick wall, I have no idea how to create this.
Any help would be massively appriciated
Regards
Adam
Just put what you want inside a range loop and use the Date::MONTHNAMES array like so
(date.year..laterdate.year).each do |y|
mo_start = (date.year == y) ? date.month : 1
mo_end = (laterdate.year == y) ? laterdate.month : 12
(mo_start..mo_end).each do |m|
puts Date::MONTHNAMES[m]
end
end
The following code will add a months_between instance method to the Date class
#!/usr/bin/ruby
require 'date'
class Date
def self.months_between(d1, d2)
months = []
start_date = Date.civil(d1.year, d1.month, 1)
end_date = Date.civil(d2.year, d2.month, 1)
raise ArgumentError unless d1 <= d2
while (start_date < end_date)
months << start_date
start_date = start_date >>1
end
months << end_date
end
end
This is VERY lightly tested, however it returns an Array of dates each date being the 1st day in each affected month.
I don't know if I've completely understood your problem, but some of the following might be useful. I've taken advantage of the extensions to Date provided in ActiveSupport:
d1 = Date.parse("20070617") # => Sun, 17 Jun 2007
d2 = Date.parse("20090529") #=> Fri, 29 May 2009
eom = d1.end_of_month #=> Sat, 30 Jun 2007
mth_ends = [eom] #=> [Sat, 30 Jun 2007]
while eom < d2
eom = eom.advance(:days => 1).end_of_month
mth_ends << eom
end
yrs = mth_ends.group_by{|me| me.year}
The final line uses another handy extension: Array#group_by, which does pretty much exactly what it promises.
d1.year.upto(d2.year) do |yr|
puts "#{yrs[yr].min}, #{yrs[yr].max}"
end
2007-06-30, 2007-12-31
2008-01-31, 2008-12-31
2009-01-31, 2009-05-31
I don't know if the start/end points are as desired, but you should be able to figure out what else you might need.
HTH
Use the date_helper gem which adds the months_between method to the Date class similar to Steve's answer.
xmas = Date.parse("2013-12-25")
hksar_establishment_day = Date.parse("2014-07-01")
Date.months_between(xmas,hksar_establishment_day)
=> [Sun, 01 Dec 2013, Wed, 01 Jan 2014, Sat, 01 Feb 2014, Sat, 01 Mar 2014, Tue, 01 Apr 2014, Thu, 01 May 2014, Sun, 01 Jun 2014, Tue, 01 Jul 2014]

Resources