How Do I Loop Through a Date Range in Reverse? - ruby-on-rails

I have a date range that I would like to be able to loop through in reverse. Give the following, how would I accomplish this, the standard Range operator doesn't seem t be working properly.
>> sd = Date.parse('2010-03-01')
=> Mon, 01 Mar 2010
>> ed = Date.parse('2010-03-05')
=> Fri, 05 Mar 2010
>> (sd..ed).to_a
=> [Mon, 01 Mar 2010, Tue, 02 Mar 2010, Wed, 03 Mar 2010, Thu, 04 Mar 2010, Fri, 05 Mar 2010]
>> (ed..sd).to_a
=> []
as you can see, the range operator works properly form start to end, but not from end to start.

Try upto/downto :
irb(main):003:0> sd = Date.parse('2010-03-01')
=> #<Date: 4910513/2,0,2299161>
irb(main):004:0> ed = Date.parse('2010-03-15')
=> #<Date: 4910541/2,0,2299161>
irb(main):005:0> sd.upto(ed) { |date| puts date }
2010-03-01
2010-03-02
2010-03-03
2010-03-04
2010-03-05
2010-03-06
2010-03-07
2010-03-08
2010-03-09
2010-03-10
2010-03-11
2010-03-12
2010-03-13
2010-03-14
2010-03-15
=> #<Date: 4910513/2,0,2299161>
irb(main):006:0> ed.downto(sd) { |date| puts date }
2010-03-15
2010-03-14
2010-03-13
2010-03-12
2010-03-11
2010-03-10
2010-03-09
2010-03-08
2010-03-07
2010-03-06
2010-03-05
2010-03-04
2010-03-03
2010-03-02
2010-03-01
=> #<Date: 4910541/2,0,2299161>

I usually just reverse the resulting array:
ruby-1.8.7-p72 > sd = Date.parse('2010-03-01')
=> Mon, 01 Mar 2010
ruby-1.8.7-p72 > ed = Date.parse('2010-03-05')
=> Fri, 05 Mar 2010
ruby-1.8.7-p72 > (sd..ed).to_a
=> [Mon, 01 Mar 2010, Tue, 02 Mar 2010, Wed, 03 Mar 2010, Thu, 04 Mar 2010, Fri, 05 Mar 2010]
ruby-1.8.7-p72 > (sd..ed).to_a.reverse
=> [Fri, 05 Mar 2010, Thu, 04 Mar 2010, Wed, 03 Mar 2010, Tue, 02 Mar 2010, Mon, 01 Mar 2010]
I guess, to make it do the right thing when you don't know if the start date is going to be before or after the end date, you'd want something along the lines of:
def date_range(sd, ed)
sd < ed ? (sd..ed).to_a : (ed..sd).to_a.reverse
end
which will give you the right thing either way:
ruby-1.8.7-p72 > sd = Date.parse('2010-03-01')
=> Mon, 01 Mar 2010
ruby-1.8.7-p72 > ed = Date.parse('2010-03-05')
=> Fri, 05 Mar 2010
ruby-1.8.7-p72 > date_range(sd, ed)
=> [Mon, 01 Mar 2010, Tue, 02 Mar 2010, Wed, 03 Mar 2010, Thu, 04 Mar 2010, Fri, 05 Mar 2010]
ruby-1.8.7-p72 > date_range(ed, sd)
=> [Fri, 05 Mar 2010, Thu, 04 Mar 2010, Wed, 03 Mar 2010, Tue, 02 Mar 2010, Mon, 01 Mar 2010]

Related

Ruby on Rails array group sort by value

I've got an array with with arrays, containing a key and a timestamp.
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 22:00:51 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 22:00:32 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 21:58:33 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 21:58:01 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 21:58:51 CEST +02:00],
["3wyadsrrdxtgieyxx_lgka", Sat, 13 May 2017 01:09:01 CEST +02:00],
["y-5he42vlloggjb_whm8jw", Sat, 22 Apr 2017 22:48:31 CEST +02:00],
["oaxej30u9we17onlug4orw", Sun, 23 Apr 2017 01:46:48 CEST +02:00],
["oaxej30u9we17onlug4orw", Sun, 23 Apr 2017 02:06:56 CEST +02:00],
["rqjwg1ka43mvri0dmrdxvg", Sun, 23 Apr 2017 17:23:34 CEST +02:00],
["ok8nq6tg-kor9jglsuhoyw", Tue, 25 Apr 2017 13:02:16 CEST +02:00],
["riwfm0m-0rmbb6e9kyug2g", Sat, 06 May 2017 06:12:27 CEST +02:00],
["riwfm0m-0rmbb6e9kyug2g", Sat, 06 May 2017 06:17:01 CEST +02:00],
["riwfm0m-0rmbb6e9kyug2g", Sat, 06 May 2017 06:18:04 CEST +02:00],
["gbqfn3_d_tritqoey5khjw", Sat, 06 May 2017 14:14:55 CEST +02:00],
["j___x1oap-veh0u1fo_oua", Sun, 07 May 2017 14:22:37 CEST +02:00],
...
I received this list by ActiveRecord.
MyModel.all.pluck(:token, :created_at)
The Model containing some uniq tokens and some duplicates.
The duplicates are interesting.
I want to group the timestaps by the key and look for the first and the last timestamp for each key.
So I grouped the array as following:
grp = arr.group_by { |key, ts| key}
Now I receive a list like this:
"vwfv8n5obwqmaw8r9fj-yq"=>[
["vwfv8n5obwqmaw8r9fj-yq", Thu, 11 May 2017 10:24:42 CEST +02:00]
],
"kacec6ybetpjdzlfgnnxya"=> [
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 22:00:31 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 22:01:43 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 21:58:17 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 21:59:05 CEST +02:00],
["kacec6ybetpjdzlfgnnxya", Fri, 12 May 2017 21:59:59 CEST +02:00]
],
...
Is it possible to sort the dates to get the first and the last date easily?
Am I too complicated? I think there should be an easier way to handle the raw data.
To get a a hash with the token as the key and the timestamps as values:
# this gives the same MIN and MAX if there is only one created_at in the group
rows = MyModel.group(:token)
.pluck("token, MIN(created_at), MAX(created_at)")
# loop though rows and create a hash
rows.each_with_object({}) do |(token, *t), hash|
hash[token] = t.uniq # removes dupes
end
{
"rqjwg1ka43mvri0dmrdxvg"=>[2017-04-23 15:23:34 UTC],
"riwfm0m-0rmbb6e9kyug2g"=>[2017-05-06 04:12:27 UTC, 2017-05-06 04:18:04 UTC]
# ...
}
If you are simply looking for the records which have duplicates you can just use a WHERE clause that counts the records:
MyModel.where("(SELECT COUNT(*) FROM things t WHERE t.token = things.token) > 1")
You could do this:
# you already have this bit
grp = arr.group_by { |key, ts| key}
# get the minmax values for each group
grp.map { |k, values_array| { k => values_array.minmax } }.reduce Hash.new, :merge
This should yield something that looks like:
{
"vwfv8n5obwqmaw8r9fj-yq"=>[
[Thu, 11 May 2017 10:24:42 CEST +02:00, Thu, 11 May 2017 10:24:42 CEST +02:00]
],
"kacec6ybetpjdzlfgnnxya"=> [
[Fri, 12 May 2017 21:58:17 CEST +02:00, Fri, 12 May 2017 22:01:43 CEST +02:00]
],
...
}
try something like this:
MyModel.order(:created_at).pluck(:token, :created_at).group_by { |key, ts| key }.flat_map{ |k, v| { k => [v.first, v.last] } }

Rails: date format in select tag

In my app I am using a select tag to make users select a month/year.
In my controller:
#months = ((Date.new(2017).beginning_of_year..Date.today.end_of_year).select { |day| day.mday == 1 })
In my from:
<%= f.select :month, #months %>
Which gives me a dropdown with dates like 2017-01-01, 2017-02-01, 2017-03-01 etc.
However in the dropdown I want January 2017, February 2017, March 2017 etc.
How can I achieve that? I tried strftime in the select tag but it does not work.
When you assing #months, you get a array:
#months = ((Date.new(2017).beginning_of_year..Date.today.end_of_year).select { |day| day.mday == 1 })
=> [Sun, 01 Jan 2017, Wed, 01 Feb 2017, Wed, 01 Mar 2017, Sat, 01 Apr 2017, Mon, 01 May 2017, Thu, 01 Jun 2017, Sat, 01 Jul 2017, Tue, 01 Aug 2017, Fri, 01 Sep 2017, Sun, 01 Oct 2017, Wed, 01 Nov 2017, Fri, 01 Dec 2017]
You then can create a new array with the dates in the format that you need, using strftime:
#monthFormat = [] #must create the array first
#months.each { |month| #monthFormat << month.strftime("%B, %Y") }
And then you have an array just like the original, but with the format:
#monthFormat.each { |monthFormat| puts monthFormat }
January, 2017
February, 2017
March, 2017
April, 2017
May, 2017
June, 2017
July, 2017
August, 2017
September, 2017
October, 2017
November, 2017
December, 2017
=> ["January, 2017", "February, 2017", "March, 2017", "April, 2017", "May, 2017", "June, 2017", "July, 2017", "August, 2017", "September, 2017", "October, 2017", "November, 2017", "December, 2017"]
Then you can put load your dropdown with #monthFormat, but select must be the same #month:
<%= f.select :month, #month %>
And it should work the same as with #month.
Here you have the complete list of formats strftime.

How to detect if dates are consecutive in Rails?

I'm looking to check for consecutive dates and display them differently if they are consecutive.
I'm working with Garage Sales that have multiple dates per sale. I'd like to then cycle through each date, and group any consecutive dates to display as a group: Ex: Apr 28 - Apr 30
I also need to account for non-consecutive dates:
Ex: Apr 15, Apr 28 - 30
Additionally, weekly dates need to be recognized as non-consecutive (so basically avoid step checks):
Ex: Apr 16, Apr 23, Apr 30
So far, I'm taking each date that hasn't passed & ordering them properly.
garage_sale_dates.where("date > ?",Time.now).order(:date)
Thanks for any help! Let me know if any other info is needed.
You can use the slice_before method of Enumerable
dates = [Date.yesterday, Date.today, Date.tomorrow, Date.parse('2016-05-01'), Date.parse('2016-05-02'), Date.parse('2016-05-05')]
# => [Wed, 27 Apr 2016, Thu, 28 Apr 2016, Fri, 29 Apr 2016, Sun, 01 May 2016, Mon, 02 May 2016, Thu, 05 May 2016]
prev = dates.first
dates.slice_before { |d| prev, prev2 = d, prev; prev2 + 1.day != d }.to_a
# => [[Wed, 27 Apr 2016, Thu, 28 Apr 2016, Fri, 29 Apr 2016], [Sun, 01 May 2016, Mon, 02 May 2016], [Thu, 05 May 2016]]
Then you can simply join the 2-or-more-element arrays from the result with "-", and leave the single element arrays intact:
prev = dates.first
dates.slice_before { |d| prev, prev2 = d, prev; prev2 + 1.day != d }.
map{|d| d.size > 1 ? "#{d.first.to_s} - #{d.last.to_s}" : d.first.to_s }
# => ["2016-04-27 - 2016-04-29", "2016-05-01 - 2016-05-02", "2016-05-05"]
There is even a commented example in the docs that is technically equivalent to yours (but deals with integers, not dates).
a simple script would do the trick
def date_list(dates)
result = []
dates.each do |date|
if result.empty? || (date - result.last.last).to_i != 1
result << [date]
else
result.last << date
end
end
result
end
# make sure dates is an array of dates
dates = [Thu, 28 Apr 2016, Fri, 29 Apr 2016, Sun, 01 May 2016, Mon, 02 May 2016, Tue, 03 May 2016, Thu, 05 May 2016, Fri, 06 May 2016, Sat, 07 May 2016, Sun, 08 May 2016]
#this would give you an array of date ranges that you wanted
date_list(dates)
=> [
[Thu, 28 Apr 2016, Fri, 29 Apr 2016],
[Sun, 01 May 2016, Mon, 02 May 2016, Tue, 03 May 2016],
[Thu, 05 May 2016, Fri, 06 May 2016, Sat, 07 May 2016, Sun, 08 May 2016]
]
I found a simpler solution using chunk_while method (available from Ruby 2.4.6).
Based on BoraMa answer:
dates = [Date.yesterday, Date.today, Date.tomorrow, Date.parse('2020-05-01'), Date.parse('2020-05-02'), Date.parse('2020-05-05')]
# => [Thu, 26 Mar 2020, Fri, 27 Mar 2020, Sat, 28 Mar 2020, Fri, 01 May 2020, Sat, 02 May 2020, Tue, 05 May 2020]
dates.chunk_while { |date_before, date_after| (date_after - date_before).to_i == 1 }.to_a
# => [[Thu, 26 Mar 2020, Fri, 27 Mar 2020, Sat, 28 Mar 2020], [Fri, 01 May 2020, Sat, 02 May 2020], [Tue, 05 May 2020]]
I think is more readable and it needs less steps to get same result.
Using a Date you can do the subtraction, it would be something like:
date1 = 1.day.ago
date2 = 2.day.ago
(date1.to_date - date2.to_date).to_i
=> 1
Adding onto Matouš Borák's answer, if you want to change the date format such that it would match what was asked in the question i.e. Apr 15, Apr 28 - 30
dates = [Date.yesterday, Date.today, Date.tomorrow, Date.parse('2021-10-01'), Date.parse('2021-10-02'), Date.parse('2021-10-05')]
# => [Wed, 01 Sep 2021, Thu, 02 Sep 2021, Fri, 03 Sep 2021, Fri, 01 Oct 2021, Sat, 02 Oct 2021, Tue, 05 Oct 2021]
prev = dates.first
dates.slice_before { |d| prev, prev2 = d, prev; prev2 + 1.day != d }.
map{|d| d.size > 1 ? "#{d.first.strftime('%b %d')} - #{d.last.strftime('%d')}" : d.first.strftime('%b %d') }
# => ["Sep 01 - 03", "Oct 01 - 02", "Oct 05"]

Calculations on datetime range

I have a problem with calculation on datetime fields.
I have two variables:
a = Sat, 01 Feb 2014 13:00:00 CET +01:00
and
b = Fri, 28 Feb 2014 13:00:00 CET +01:00
And I want to calculate how many days passed from a to b range.
Please Help.
You can use this.
require 'date'
( Date.parse('Fri, 28 Feb 2014 13:00:00 CET +01:00') - Date.parse('Sat, 01 Feb 2014 13:00:00 CET +01:00')).to_i
=> 27
i.e
( Date.parse(b) - Date.parse(a)).to_i
require 'date'
a=Date.parse('Sat, 01 Feb 2014 13:00:00 CET +01:00')
=> #<Date: 2014-02-01 (4913379/2,0,2299161)>
b=Date.parse('Fri, 28 Feb 2014 13:00:00 CET +01:00')
=> #<Date: 2014-02-28 (4913433/2,0,2299161)>
b-a
=> (27/1)
(b-a).to_i
=> 27
You can also use time_diff gem to calculate the difference.
In your gem file add gem 'time_diff'
I have added the code in index.html.erb but, you can use it any where you want.
take a look on following code.
<% require 'time_diff' %>
<% a = Time.diff(Time.parse('Sat, 01 Feb 2014 13:00:00 CET +01:00'), Time.parse('Fri, 28 Feb 2014 13:00:00 CET +01:00'), "%d") %>
<%= a[:diff] %>
and I got the following result
"27 days"
You can also take a look on
https://github.com/abhidsm/time_diff

Change a hash into an array

I have an Rails 4 application that collects attendance at church services. Some weeks there are two services and some weeks there is only one. I need to get the total attendance for each week and show it as a graph.
By calling:
Stat.calculate(:sum, :attendance, group: :date)
in the console I have been able to collect the data in a hash like this:
{Sun, 06 Jan 2013=>66, Sun, 13 Jan 2013=>65, Sun, 20 Jan 2013=>60, Sun, 27 Jan 2013=>67, Sun, 03 Feb 2013=>60, Sun, 10 Feb 2013=>76, Sun, 17 Feb 2013=>65, Sun, 24 Feb 2013=>52, Sun, 03 Mar 2013=>52, Sun, 10 Mar 2013=>45, Sun, 17 Mar 2013=>56, Sun, 24 Mar 2013=>134, Sun, 31 Mar 2013=>76, Sun, 07 Apr 2013=>88, Sun, 14 Apr 2013=>87, Sun, 28 Apr 2013=>93, Sun, 05 May 2013=>93, Sun, 12 May 2013=>95, Sun, 19 May 2013=>90, Sun, 26 May 2013=>87, Sun, 02 Jun 2013=>71, Sun, 09 Jun 2013=>86, Sun, 16 Jun 2013=>109, Sun, 23 Jun 2013=>80, Sun, 30 Jun 2013=>68, Sun, 07 Jul 2013=>75, Sun, 14 Jul 2013=>73}
But What I need for my chart is an array of hashes in the form of:
{date: "Sun, 23 Jun 2013", attendance: 80}, {date: "Sun, 30 Jun 2013", attendance: 68"}
So I am trying to figure out how to convert the first form into the second form.
I'm sure its something pretty easy, but my limited rails knowledge is hitting a wall.
.collect{|key,value| {:date => key, :attendance => value} }
loop through and create a new hash where the original key becomes the value for date and the original value becomes the value for attendance. These new hashes are collected into an array.
You can think as below:
h = {"Sun, 06 Jan 2013"=>66, "Sun, 13 Jan 2013"=>65, "Sun, 20 Jan 2013"=>60 }
h.map{|k,v| Hash[:date,k,:attend,v]}
# => [{:date=>"Sun, 06 Jan 2013", :attend=>66},
# {:date=>"Sun, 13 Jan 2013", :attend=>65},
# {:date=>"Sun, 20 Jan 2013", :attend=>60}]

Resources