I have the following variables
INPUT
start_date = 1 JUN 2020
session_days = [Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday]
sessions_per_week_count = 7 # from above data
total sesions = 20
OUTPUT
20 JUN 2020
from the above data, we can calculate that end_date should be 20 Jun 2018
I need help writing algorithm/formula to calculate the end date with variable input data based on the calender.
any suggestions will be appreciated.
Finally, I came up with a pretty simple method in ruby
def session_dates(date, weekdays_arr, number_of_working_days)
result = []
while result.size < number_of_working_days
if weekdays_arr.include? date.wday
result << date
end
date += 1
end
result
end
by calling a method like this
start_date = Date.today.beginning_of_month
session_days_in_week = [0,1,2,3,4,5,6] # week starts from 0(Sunday)
result = session_dates(start_date, session_days_in_week, 20)
I can get all dates on the weekdays in output
[
[ 0] Mon, 01 Jun 2020,
[ 1] Tue, 02 Jun 2020,
[ 2] Wed, 03 Jun 2020,
[ 3] Thu, 04 Jun 2020,
[ 4] Fri, 05 Jun 2020,
[ 5] Sat, 06 Jun 2020,
[ 6] Sun, 07 Jun 2020,
[ 7] Mon, 08 Jun 2020,
[ 8] Tue, 09 Jun 2020,
[ 9] Wed, 10 Jun 2020,
[10] Thu, 11 Jun 2020,
[11] Fri, 12 Jun 2020,
[12] Sat, 13 Jun 2020,
[13] Sun, 14 Jun 2020,
[14] Mon, 15 Jun 2020,
[15] Tue, 16 Jun 2020,
[16] Wed, 17 Jun 2020,
[17] Thu, 18 Jun 2020,
[18] Fri, 19 Jun 2020,
[19] Sat, 20 Jun 2020
]
and obviously the last element in result array is ending_date.
I have a list of transactions in an array.
=> [Wed, 23 Oct 2013, Mon, 18 Nov 2013, Fri, 22 Nov 2013, Mon, 13 Jan 2014, Tue, 28 Jan 2014, Mon, 03 Feb 2014, Mon, 10 Feb 2014, Tue, 18 Feb 2014, Fri, 07 Mar 2014, Mon, 31 Mar 2014, Mon, 07 Apr 2014, Tue, 10 Jun 2014, Mon, 30 Jun 2014, Mon, 22 Sep 2014, Mon, 06 Oct 2014, Fri, 14 Nov 2014, Tue, 18 Nov 2014, Fri, 26 Dec 2014, Thu, 15 Jan 2015, Mon, 23 Mar 2015, Mon, 20 Apr 2015]
I need to compare the dates of each transaction and list any months that are missing in the list of months and year. Here is what I have now...
#find_transactions = (#user.transactions.find_all { |t| (t.name 'name' })
#trans_dates = #find_transactions.map(&:date).sort!.map { |s| Date.strptime(s, '%Y-%m') }.each_cons(2).map{ |d1,d2| d1.next_month == d2 }
This method currently gives me a true or false if each month is there but I need to actually have the method print a list of months that are missing. I would like to have it print the month and year together.
Here is the response this method gives me...
=> [true, false, true, false, false, true, false, true, false, false, false, true, true]
I want a response like this...
=> [March 2015, December 2014, September 2014]
Thanks in advance!
Edit: For array already being composed of date objects you can do:
require 'date'
dates = [Wed, 23 Oct 2013, Mon, 18 Nov 2013, Fri, 22 Nov 2013, Mon, 13 Jan 2014, Tue, 28 Jan 2014, Mon, 03 Feb 2014, Mon, 10 Feb 2014, Tue, 18 Feb 2014, Fri, 07 Mar 2014, Mon, 31 Mar 2014, Mon, 07 Apr 2014, Tue, 10 Jun 2014, Mon, 30 Jun 2014, Mon, 22 Sep 2014, Mon, 06 Oct 2014, Fri, 14 Nov 2014, Tue, 18 Nov 2014, Fri, 26 Dec 2014, Thu, 15 Jan 2015, Mon, 23 Mar 2015, Mon, 20 Apr 2015]
all_dates = []
dates.first.upto(dates.last) {|x| all_dates << x.strftime('%b %Y') if x.day == 1 || x == dates.first}
d = dates.map {|x| x.strftime('%b %Y')}.uniq
p (all_dates - d)
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Edit: Below methods are for an array of date strings
You can try this:
require 'date'
dates = ["Wed, 23 Oct 2013", "Mon, 18 Nov 2013", "Fri, 22 Nov 2013", "Mon, 13 Jan 2014", "Tue, 28 Jan 2014", "Mon, 03 Feb 2014", "Mon, 10 Feb 2014", "Tue, 18 Feb 2014", "Fri, 07 Mar 2014", "Mon, 31 Mar 2014", "Mon, 07 Apr 2014", "Tue, 10 Jun 2014", "Mon, 30 Jun 2014", "Mon, 22 Sep 2014", "Mon, 06 Oct 2014", "Fri, 14 Nov 2014", "Tue, 18 Nov 2014", "Fri, 26 Dec 2014", "Thu, 15 Jan 2015", "Mon, 23 Mar 2015", "Mon, 20 Apr 2015"]
all_dates = []
d = dates.map {|x| Date.parse(x[8..-1])}.uniq
counter = d.first
until counter == d.last
all_dates << counter
counter = counter.next_month
end
p (all_dates - d).map {|x| x.strftime('%b %Y')}
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Another (more concise) way would be:
require 'date'
dates = ["Wed, 23 Oct 2013", "Mon, 18 Nov 2013", "Fri, 22 Nov 2013", "Mon, 13 Jan 2014", "Tue, 28 Jan 2014", "Mon, 03 Feb 2014", "Mon, 10 Feb 2014", "Tue, 18 Feb 2014", "Fri, 07 Mar 2014", "Mon, 31 Mar 2014", "Mon, 07 Apr 2014", "Tue, 10 Jun 2014", "Mon, 30 Jun 2014", "Mon, 22 Sep 2014", "Mon, 06 Oct 2014", "Fri, 14 Nov 2014", "Tue, 18 Nov 2014", "Fri, 26 Dec 2014", "Thu, 15 Jan 2015", "Mon, 23 Mar 2015", "Mon, 20 Apr 2015"]
all_dates = []
d = dates.map {|x| Date.parse(x[8..-1])}.uniq
d.first.upto(d.last) {|x| all_dates << x if x.day == 1}
p (all_dates - d).map {|x| x.strftime('%b %Y')}
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
This is one way you could do that.
Code
require 'date'
def missing_months(dates)
a = dates.map { |s| d = Date.strptime(s, '%a, %d %b %Y'); d - d.day + 1 }
(all_months_in_range(*a.minmax) -a).map { |d| d.strftime('%b %Y') }
end
def all_months_in_range(f,l)
(12*(l.year-f.year)+l.month-f.month+1).times.map do |i|
y,m = (f.month+i).divmod(12)
y += f.year
(m=12; y-=1) if m ==0
Date.new(y,m)
end
end
Example
dates = ['Wed, 23 Oct 2013', 'Mon, 18 Nov 2013', 'Fri, 22 Nov 2013',
'Fri, 14 Nov 2014', 'Tue, 18 Nov 2014', 'Fri, 26 Dec 2014',
'Mon, 13 Jan 2014', 'Tue, 28 Jan 2014', 'Mon, 03 Feb 2014',
'Mon, 31 Mar 2014', 'Mon, 07 Apr 2014', 'Tue, 10 Jun 2014',
'Mon, 30 Jun 2014', 'Mon, 22 Sep 2014', 'Mon, 06 Oct 2014',
'Mon, 10 Feb 2014', 'Tue, 18 Feb 2014', 'Fri, 07 Mar 2014',
'Thu, 15 Jan 2015', 'Mon, 23 Mar 2015', 'Mon, 20 Apr 2015']
missing_months(dates)
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Notice that the dates needn't be sorted.
Explanation
For the example above:
a = dates.map { |s| d = Date.strptime(s, '%a, %d %b %Y'); d - d.day + 1 }
#=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
# #<Date: 2013-11-01 ((2456598j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>]
Notice that each of these dates is on the first of the month. Next, obtain the first and last of these dates:
f,l = a.minmax
f #=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
l #=> #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>]
Now pass f and l to all_months_in_range to create an array that contains a date object for the first day of each month between f and l.
b = all_months_in_range(f,l)
#=> [#<Date: 2013-10-01 ((2456567j,0s,0n),+0s,2299161j)>,
# #<Date: 2013-11-01 ((2456598j,0s,0n),+0s,2299161j)>,
# ...
# #<Date: 2015-04-01 ((2457114j,0s,0n),+0s,2299161j)>]
b.size #=> 19
I will skip an explanation of this helper method, as it is quite straightforward.
Compute that difference between arrays b and a to obtain the missing beginning-of-month dates:
c = b-a
#=> [#<Date: 2013-12-01 ((2456628j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-05-01 ((2456779j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-07-01 ((2456840j,0s,0n),+0s,2299161j)>,
# #<Date: 2014-08-01 ((2456871j,0s,0n),+0s,2299161j)>,
# #<Date: 2015-02-01 ((2457055j,0s,0n),+0s,2299161j)>]
Lastly, convert these dates to the desired format:
c.map { |d| d.strftime('%b %Y') }
#=> ["Dec 2013", "May 2014", "Jul 2014", "Aug 2014", "Feb 2015"]
Addendum: after reading #Sid's answer, I see I could have saved myself some trouble in my helper method by using Date#next_month:
def all_months_in_range(f,l)
(12*(l.year-f.year)+l.month-f.month+1).times.map { |i| f.next_month(i) }
end
This isn't very elegant, but it worked. I started with your original code, #SupremeA, and built off of that.
require 'date'
dates = ['Wed, 23 Oct 2013', 'Mon, 18 Nov 2013', 'Fri, 22 Nov 2013', 'Mon, 13 Jan 2014', 'Tue, 28 Jan 2014', 'Mon, 03 Feb 2014', 'Mon, 10 Feb 2014', 'Tue, 18 Feb 2014', 'Fri, 07 Mar 2014', 'Mon, 31 Mar 2014', 'Mon, 07 Apr 2014', 'Tue, 10 Jun 2014', 'Mon, 30 Jun 2014', 'Mon, 22 Sep 2014', 'Mon, 06 Oct 2014', 'Fri, 14 Nov 2014', 'Tue, 18 Nov 2014', 'Fri, 26 Dec 2014', 'Thu, 15 Jan 2015', 'Mon, 23 Mar 2015', 'Mon, 20 Apr 2015']
new_dates = []
dates.each { |d| new_dates.push(Date.parse(d).strftime('%B %Y')) }
sorted_dates = new_dates.map { |s| Date.strptime(s, '%B %Y') }.sort.uniq
missing_months = []
sorted_dates.each_cons(2) do |d1,d2|
d = d1
while d.next_month != d2
missing_months.push(d.next_month.strftime('%B %Y'))
d = d >> 1
end
end
p missing_months
=> ["December 2013", "May 2014", "July 2014", "August 2014", "February 2015"]
I am struggling to sort this hash listed below:
{
17=>{:id=>17, :count=>1, :created_at=>Wed, 05 Sep 2012 19:02:34 UTC +00:00},
14=>{:id=>14, :count=>2, :created_at=>Sun, 02 Sep 2012 19:20:28 UTC +00:00},
9=>{:id=>9, :count=>0, :created_at=>Sun, 02 Sep 2012 17:09:35 UTC +00:00},
10=>{:id=>10, :count=>2, :created_at=>Sat, 01 Sep 2012 17:09:56 UTC +00:00},
11=>{:id=>11, :count=>0, :created_at=>Fri, 31 Aug 2012 19:13:57 UTC +00:00},
12=>{:id=>12, :count=>2, :created_at=>Thu, 30 Aug 2012 19:19:32 UTC +00:00},
13=>{:id=>13, :count=>0, :created_at=>Thu, 23 Aug 2012 19:20:09 UTC +00:00}
}
The above hash should be sorted on count and created_of and look like this hash:
{
12=>{:id=>12, :count=>2, :created_at=>Thu, 30 Aug 2012 19:19:32 UTC +00:00},
10=>{:id=>10, :count=>2, :created_at=>Sat, 01 Sep 2012 17:09:56 UTC +00:00},
14=>{:id=>14, :count=>2, :created_at=>Sun, 02 Sep 2012 19:20:28 UTC +00:00},
17=>{:id=>17, :count=>1, :created_at=>Wed, 05 Sep 2012 19:02:34 UTC +00:00},
13=>{:id=>13, :count=>0, :created_at=>Thu, 23 Aug 2012 19:20:09 UTC +00:00},
11=>{:id=>11, :count=>0, :created_at=>Fri, 31 Aug 2012 19:13:57 UTC +00:00},
9=>{:id=>9, :count=>0, :created_at=>Sun, 02 Sep 2012 17:09:35 UTC +00:00}
}
Assuming you are using Ruby 1.9 where hashes have order:
Hash[data.sort_by { |key, h| [-h[:count], h[:created_at]] }]
In Ruby 1.8, you cannot sort a hash as a hash has no order. In Ruby 1.9, a hash iteration order is defined by the insertion order. You therefore have to create a new hash in which you will insert the elements in the right order.
sorted_keys = hash.keys.sort
sorted_hash = Hash.new
sorted_keys.each do |k|
sorted_hash[k] = hash[k]
end
I need this
require 'date'
DateTime.parse "Mon, Dec 27 6:30pm"
to return a DateTime for 6:30pm in the EDT timezone, but it returns one in UTC. How can I get a EST DateTime or convert the UTC one into an EDT DateTime with a 6:30pm value?
OK I'm going to offer an answer to my own question
require 'time'
ENV["TZ"] = "US/Eastern"
Time.parse("Mon, Dec 27 6:30pm").to_datetime
=> #<DateTime: 2011-12-27T18:30:00-05:00 (117884327/48,-5/24,2299161)>
In Rails, this worked nicely for me
DateTime.parse "Mon, Dec 27 6:30pm #{Time.zone}"
It won't work in vanilla Ruby though.
Final answer ;-)
require 'date'
estHoursOffset = -5
estOffset = Rational(estHoursOffset, 24)
date = (DateTime.parse("Mon, Dec 27 6:30pm") - (estHoursOffset/24.0)).new_offset(estOffset)
(or -4 for EDT)
DateTime#change()
You can try using change() after parsing it to alter the timezone offset:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: '-0400' )
# => Wed, 27 Dec 2017 18:30:00 -0400
You can also just use the hours:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: '-4' )
# => Wed, 27 Dec 2017 18:30:00 -0400
But, be careful, you cannot use an integer:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: -4 )
# => Wed, 27 Dec 2017 18:30:00 +0000
If you need to determine the correct offset to use based on a time zone you can do something like this:
offset = ( Time.zone_offset('EDT') / 1.hour ).to_s
# => "-4"
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: offset )
# => Wed, 27 Dec 2017 18:30:00 -0400
You can also use change() to manually set other parts of the DateTime as well, like setting the hour to noon:
DateTime.parse( "Mon, Dec 27 6:30pm" ).change( offset: '-4', hour: 12 )
# => Wed, 27 Dec 2017 12:00:00 -0400
Be careful with that one because you can see that it's cleared the minutes as well.
Here's the docs for the change() method: http://api.rubyonrails.org/v5.1/classes/DateTime.html#method-i-change
If you're using Rails' ActiveSupport:
"Mon, Dec 27 6:30pm".in_time_zone(-4.hours).to_datetime
# => Mon, 27 Dec 2021 18:30:00 -0400
Time.find_zone(-4.hours).parse("Mon, Dec 27 6:30pm").to_datetime
# => Mon, 27 Dec 2021 18:30:00 -0400
If you want to use the local daylight saving time (DST) rules, you could use:
"Mon, Dec 27 6:30pm".in_time_zone("Eastern Time (US & Canada)")
# => Mon, 27 Dec 2021 18:30:00 EST -05:00
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Dec 27 6:30pm")
# => Mon, 27 Dec 2021 18:30:00 EST -05:00
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Dec 27 6:30pm").to_datetime
# => Mon, 27 Dec 2021 18:30:00 -0500
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Jun 27 6:30pm")
# => Sun, 27 Jun 2021 18:30:00 EDT -04:00
Time.find_zone("Eastern Time (US & Canada)").parse("Mon, Jun 27 6:30pm").to_datetime
# => Sun, 27 Jun 2021 18:30:00 -0400
Time.find_zone("EST5EDT").parse("Mon, Jun 27 6:30pm").to_datetime
# => Sun, 27 Jun 2021 18:30:00 -0400
Notice the date in June, above, is automatically set to EDT (-0400) because this date is in DST, contrary to the December date.
To force EST regardless if date is within DST or not:
Time.find_zone("EST").parse("Mon, Jun 27 6:30pm")
# => Sun, 27 Jun 2021 18:30:00 EST -05:00
Time.find_zone("EST").parse("Mon, Jun 27 6:30pm").to_datetime
# => Sun, 27 Jun 2021 18:30:00 -0500