How to Loop through Months in Ruby on Rails - ruby-on-rails

I need to loop through several months in a Ruby on Rails application. For each month, I need to find the first and last day of that given month. These dates are then used in a separate query to find events occurring during those dates and running calculations on them.
Initially, I tried something like:
(11.months.ago.to_date.month..Date.today.month).each do |m|
start_date = '01-#{m}-#{Date.today.year}'.to_date.beginning_of_month
end_date = '01-#{m}-#{Date.today.year}'.to_date.end_of_month
end
Of course, the year isn't updated in this case in the event that 11 months ago involves going back to the previous year. And, I don't think this type of for/each loop works. I also tried mapping the numbers to an array and using the same method, but received an error.
What's the best way to accomplish something like this?

Here's one way to do it:
number_of_months = 0..11
number_of_months.to_a.reverse.each do |month_offset|
start_date = month_offset.months.ago.beginning_of_month
end_date = month_offset.months.ago.end_of_month
puts "Start date : #{start_date} End date : #{end_date}"
end
=>
Start date : 2012-01-01 00:00:00 -0700 End date : 2012-01-31 23:59:59 -0700
Start date : 2012-02-01 00:00:00 -0700 End date : 2012-02-29 23:59:59 -0700
Start date : 2012-03-01 00:00:00 -0700 End date : 2012-03-31 23:59:59 -0600
Start date : 2012-04-01 00:00:00 -0600 End date : 2012-04-30 23:59:59 -0600
Start date : 2012-05-01 00:00:00 -0600 End date : 2012-05-31 23:59:59 -0600
Start date : 2012-06-01 00:00:00 -0600 End date : 2012-06-30 23:59:59 -0600
Start date : 2012-07-01 00:00:00 -0600 End date : 2012-07-31 23:59:59 -0600
Start date : 2012-08-01 00:00:00 -0600 End date : 2012-08-31 23:59:59 -0600
Start date : 2012-09-01 00:00:00 -0600 End date : 2012-09-30 23:59:59 -0600
Start date : 2012-10-01 00:00:00 -0600 End date : 2012-10-31 23:59:59 -0600
Start date : 2012-11-01 00:00:00 -0600 End date : 2012-11-30 23:59:59 -0700
Start date : 2012-12-01 00:00:00 -0700 End date : 2012-12-31 23:59:59 -0700

First, count the number of months between two dates (courtesy of Massimiliano Peluso):
start_date = 13.months.ago.to_date
# => Wed, 16 Nov 2011
end_date = Date.today
# => Sun, 16 Dec 2012
number_of_months = (end_date.year*12+end_date.month)-(start_date.year*12+start_date.month)
# => 13
Then from the start month, counting each month thereafter, find the first/last dates and append to an accumulator array.
dates = number_of_months.times.each_with_object([]) do |count, array|
array << [start_date.beginning_of_month + count.months,
start_date.end_of_month + count.months]
end
# => ...
Now dates will contain an array with nested Date pairs corresponding to the first and last date of each month. This dates array will be easy to iterate for processing.
dates
# => [[Tue, 01 Nov 2011, Wed, 30 Nov 2011], [Thu, 01 Dec 2011, Fri, 30 Dec 2011], ...
dates[0].first
# => Tue, 01 Nov 2011
dates[0].last
# => Wed, 30 Nov 2011
dates[0].last.class
# => Date
This is tested and working in Rails 3.2.5/Ruby 1.9.3p194

(start_date..end_date).select{|date| date.day == 1}.map{|date| [date.beginning_of_month, date.end_of_month]}

time_start = 13.months.ago
time_end = 0.seconds.ago
time = time_start
while time < time_end
puts("m:#{time.month} y:#{time.year}")
time = time.advance(months: 1)
end

start and end of next 12 months from (including) some_date:
(0..11).map do |m|
start_date = some_date.at_beginning_of_month + m.month
end_date = start_date.at_end_of_month
end

Related

How can I change the offset of a timestamp and then store the change in postgresql?

I have a form on my rails app that allows you to create a campaign object where the user can set a start_date, end_date, and timezone for which the dates will officially start and end.
What I would like to do is to apply the offset of the selected timezone to both the start_date and end_date. I've tried a few combinations but I can't seem to make it work. For starters, when I select my start and end dates for 11:00 PM and select Central Time Zone CDT with offset of -0500, the end result in postgresql is the following timestamp.
# No offset is applied
Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
In order to try to apply the offset, I've tried a few combinations in my rails code. Here are some examples in a before_validation callback. It's pseudocode but this is the gist of it.
Example #1
date_tmp = send("start_at") # Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
date_string = date_tmp.to_datetime.change(offset: '-0500').to_s
assign_attributes("start_at" => date_string) # "2022-06-08T22:00:00-05:00"
The result is no change for example 1.
Example #2
date_tmp = send("start_at") # Wed, 08 Jun 2022 23:00:00.000000000 UTC +00:00
date_string = date_tmp.to_datetime.change(offset: '-0500').to_s
dt = Chronic.parse(date_string.to_time.to_s)
assign_attributes("start_at" => dt) # "2022-06-08 22:00:00 -0500"
None of these variations work. I've also tried to save to postgresql timestamps that look like this:
2022-06-08T22:00:00-05:00
2022-06-08 22:00:00 -0500
Wed, 08 Jun 2022 22:00:00 -0500
I don't understand why rails and postgresql can't save the timestamp with the adjusted utc_offset that I want. What can I be doing wrong?
Try new_offset method on your DateTime instance.
It duplicates datetime object and resets its offset.
Some examples:
date = DateTime.now
date # => Thu, 16 Jun 2022 20:14:58 +0000
date.new_offset('+03:00') # => Thu, 16 Jun 2022 23:14:58 +0300
date.new_offset('-03:00') # => Thu, 16 Jun 2022 17:14:58 -0300

Ruby hash with dates as keys

When I do this request on my database :
users_count = User.select("date_trunc('day', created_at)").group("date_trunc('day', created_at)").count
I then have a hash with dates as keys :
# => {2016-07-29 00:00:00 UTC=>1, 2016-07-06 00:00:00 UTC=>1, 2016-07-07 00:00:00 UTC=>1, 2016-07-13 00:00:00 UTC=>2, 2016-07-04 00:00:00 UTC=>421, 2016-07-09 00:00:00 UTC=>3, 2016-07-08 00:00:00 UTC=>2, 2016-07-22 00:00:00 UTC=>1, 2016-07-19 00:00:00 UTC=>1, 2016-07-21 00:00:00 UTC=>2}
But then I'm not able to retrieve data by date :
date = DateTime.new(2016, 7, 29)
# => Fri, 29 Jul 2016
users_count[date]
# => nil
users_count.keys.first === date
# => true
users_count[users_count.keys.first]
# => 1
How can I retrieve the number of users created on specific days in a single request? And why is ruby giving me a value of nil for a key that is present in the hash?
Thanks for your help.
ruby version 2.2.4, Rails version 4.2.5.1
Use DateTime instance instead, converted to time, since hash keys are apparently instances of Time:
date = DateTime.new(2016,7,29,0,0,0).to_time

Rails query changes the time

<% date = Time.now.beginning_of_day %>
<%= date %> prints 2016-06-01 00:00:00 +0600
<% schedule = Schedule.where(:date_ => date).first %>
<%= date %> prints 2016-05-31 18:00:00 UTC
2016-06-01 00:00:00 +0600
2016-05-31 18:00:00 UTC
Using mongoid
date_ is Time field
My local timezone is UTC +6
I am sorry if my question is stupid -_-'
▶ Time.now
#⇒ 2016-06-26 07:43:42 +0200
▶ Time.now.utc
#⇒ 2016-06-26 05:43:46 UTC
That said, 2016-06-01 00:00:00 +0600 and 2016-05-31 18:00:00 UTC you got are the same time, printed in different timezones.
In Rails you should always explicitly define a timezone you are dealing in:
Time.now # incorrect
Time.zone.now # correct
Further reading: http://danilenko.org/2012/7/6/rails_timezones/

Ruby/Rails Time.parse in input timezone

I have a string that contains ISO8601 formatted date and time with timezone. How can I get Time or TimeWithTimezone object in the timezone specified in the string?
PC's timezone is '+01:00' and ActiveSupport::TimeWithZone is set to UTC. Here's what I've tried so far:
Loading development environment (Rails 3.2.13)
irb(main):001:0> Time.parse '1990-01-23T00:11:22-07:00'
=> 1990-01-23 08:11:22 +0100
irb(main):002:0> Time.zone.parse '1990-01-23T00:11:22-07:00'
=> Tue, 23 Jan 1990 07:11:22 UTC +00:00
Here's what I want:
irb(main):001:0> Time.???? '1990-01-23T00:11:22-07:00'
=> 1990-01-23 00:11:22 -0700
How can I parse the string and get time object in the timezone specified in the string?
Your issue was that you've used Time, whereas had to use DateTime:
DateTime.strptime('1990-01-23T00:11:22-07:00', "%Y-%m-%dT%H:%M:%S%z")
#=> Tue, 23 Jan 1990 00:11:22 -0700
EDIT
To make it a Time class object, you can do something like this (somewhat ugly, but still it does its job):
date = DateTime.strptime('1990-01-23T00:11:22-07:00', "%Y-%m-%dT%H:%M:%S%z")
#=> Tue, 23 Jan 1990 00:11:22 -0700
Time.new(date.year, date.month, date.day, date.hour, date.min, date.sec, date.zone)
#=> 1990-01-23 00:11:22 -0700

How Do I Add 15 Minutes To A Datetime In Ruby Only Between Certain Hours Of The Day?

I'm a full time SQL Server database developer, but I'm looking to start utilizing Ruby for certain management tasks. Also, I'm trying to take my Rails skills to the next level. I'm great with SQL, but weak in programming languages (I'm working hard to fix that!). Here is my question:
I want to write a simple loop that will seed my database for a rails application for an appointments model. I want to start with a Monday date (let's say March 19, 2012) and I want to end with a Friday date (March 23, 2012). I want to start at 9am on each day, and I want to end at 5pm each day. I want my appointments in 15 minute intervals.
Currently, I have a simple loop that will add the 15 minutes to a parsed time. However, I want to add 15 minutes only between the hours of 9am and 5pm each day. Here is my code so far:
s = Time.parse("March 19, 2012, 9:00 AM")
e = Time.parse("March 23, 2012 5:00 PM") + 15 * 60
while s < e
puts "#{s} "
s+=15 * 60
end
I have a perfect list of dates from the above code (although I know there's probably a much better way to do it), but as I said, I want to limit my results so that I don't seed my database table with a bunch of useless dates that I constantly have to deal with in my Rails application.
Any advice would be greatly appreciated. Thank you in advance for your help!
I'd do it like this...
Appointment.delete_all # clear table
start_date = Time.parse("March 19, 2012, 9:00 AM")
end_date = Time.parse("March 23, 2012 5:00 PM")
# create a range from start_date to end_date moving in 15 minute intervals
(start_date..end_date).step(15 * 60) do |date_time|
# only create Appointment if the hour is in the range 9 - 17 (24 hour clock)
Appointment.create(:appt => date_time) if (9..17) === date_time.hour
end
Alternate version for ruby 1.9.3
curr_time = start_date
begin
Appointment.create(:appt => curr_time) if (9..17) === curr_time.hour
end while (curr_time += 60 * 15) <= end_date
EDIT: This works for me.
irb(main):003:0> require 'time'
=> true
irb(main):004:0> start_date = Time.parse("March 19, 2012, 9:00 AM")
=> Mon Mar 19 09:00:00 -0400 2012
irb(main):005:0> end_date = Time.parse("March 23, 2012 5:00 PM")
=> Fri Mar 23 17:00:00 -0400 2012
irb(main):006:0> (start_date..end_date).step(15 * 60) do |date_time|
irb(main):007:1* puts date_time
irb(main):008:1> end
Mon Mar 19 09:00:00 -0400 2012
Mon Mar 19 09:15:00 -0400 2012
Mon Mar 19 09:30:00 -0400 2012
Mon Mar 19 09:45:00 -0400 2012
Mon Mar 19 10:00:00 -0400 2012
Mon Mar 19 10:15:00 -0400 2012
Mon Mar 19 10:30:00 -0400 2012
.
.
.
Fri Mar 23 15:45:00 -0400 2012
Fri Mar 23 16:00:00 -0400 2012
Fri Mar 23 16:15:00 -0400 2012
Fri Mar 23 16:30:00 -0400 2012
Fri Mar 23 16:45:00 -0400 2012
Fri Mar 23 17:00:00 -0400 2012
I thought that #step would suit this just fine, then tried it and discovered that Ruby's Time objects don't respond to #step as Numerics do.
It's a nice idiom, so I mixed it in:
MIN = 60
HR = 60 * MIN
DAY = 24 * HR
start_date = Time.mktime(2012,3,19,0,0,0)
end_date = Time.mktime(2012,3,23,0,0,0)
start_time = 9 * HR
end_time = 17 * HR
interval = 15 * MIN
module TimeStep
def step(end_time, incr)
t = self
while t <= end_time
yield t
t += incr
end
end
end
start_date.extend(TimeStep).step(end_date, DAY) do |day|
first_time = day + start_time
first_time.extend(TimeStep).step(day+end_time, interval) do |tick|
# make record for tick, or just print it for this example
puts tick
end
end
# Outputs...
Mon Mar 19 09:00:00 -0700 2012
Mon Mar 19 09:15:00 -0700 2012
Mon Mar 19 09:30:00 -0700 2012
...
Fri Mar 23 16:45:00 -0700 2012
Fri Mar 23 17:00:00 -0700 2012

Resources