Ruby hash with dates as keys - ruby-on-rails

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

Related

How to save a date into rails using the console

I have a table that has a date field in it. How would I save the date using the console? I tried
event = Event.create(name:"Concert", date:08/20/2016, location:'Portland', state:'OR')
However, I am getting an Invalid octal digit error.
You'll want to pass in an actual Date object, which you can get from a string with the Date.parse method:
event = Event.create(name: "Concert", date: Date.parse('2016-08-20'), location: 'Portland', state: 'OR')
Note that I've rewritten your date to be in a different format. The MM/DD/YYYY format is not portable across locales, so I'd strongly suggest you use YYYY-MM-DD (the ISO 8601 format).
Using a string in the correct format will do the trick. For example:
>> foo = Foo.create date: "20/8/2016"
(0.0ms) begin transaction
SQL (1.0ms) INSERT INTO "foos" ("date") VALUES (?) [["date", Sat, 20 Aug 2016]]
(0.9ms) commit transaction
#<Foo id: 1, date: "2016-08-20">
>> foo.date
Sat, 20 Aug 2016
ActiveSupport provides core extensions on the String class for conversions from strings to date, time, and datetime. This is probably ok, and more convenient, to take advantage of while testing around in Rails console.
Taking advantage of this extension in the application itself (instead of explicitly parsing with Date.parse) is totally up to you and your team.
From the source:
"1-1-2012".to_date # => Sun, 01 Jan 2012
"01/01/2012".to_date # => Sun, 01 Jan 2012
"2012-12-13".to_date # => Thu, 13 Dec 2012
"12/13/2012".to_date # => ArgumentError: invalid date
Just to be thorough, examples for String#to_time
"13-12-2012".to_time # => 2012-12-13 00:00:00 +0100
"06:12".to_time # => 2012-12-13 06:12:00 +0100
"2012-12-13 06:12".to_time # => 2012-12-13 06:12:00 +0100
"2012-12-13T06:12".to_time # => 2012-12-13 06:12:00 +0100
"2012-12-13T06:12".to_time(:utc) # => 2012-12-13 06:12:00 UTC
"12/13/2012".to_time # => ArgumentError: argument out of range
And String#to_datetime:
"1-1-2012".to_datetime # => Sun, 01 Jan 2012 00:00:00 +0000
"01/01/2012 23:59:59".to_datetime # => Sun, 01 Jan 2012 23:59:59 +0000
"2012-12-13 12:50".to_datetime # => Thu, 13 Dec 2012 12:50:00 +0000
"12/13/2012".to_datetime # => ArgumentError: invalid date
Try this, for example:
date = Date.parse('3rd Feb 2001') ####=> #<Date: 2001-02-03 ...>
Reference: Date class

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 to Loop through Months in 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

Rails: DateTime of different time zones

I have a project model with a datetime attribute to define the deadline. The deadlines are of different time zones, and I receive them in a string format like below:
Jan 1st 2013 00:00:00 EST
Feb 9th 2013 23:59:00 PST
I want to store these values in the default UTC format in the database. I've seen that there are many options to parse the time like Time.zone.parse and Time.parse. My question is: what's the best practice to parse the datetime of different time zones? I'm using Rails 3.2.9.
You need not worry about that at all, as long as you set correct timezone in config/application.rb:
config.time_zone = 'UTC'
You just assign the time strings to the attributes, ActiveRecord will convert it correctly.
1.9.3p125 :002 > project.deadline = "Jan 1st 2013 00:00:00 EST"
=> "Jan 1st 2013 00:00:00 EST"
1.9.3p125 :003 > project.deadline
=> Tue, 01 Jan 2013 05:00:00 UTC +00:00
1.9.3p125 :004 > project.deadline = "Feb 9th 2013 23:59:00 PST"
=> "Feb 9th 2013 23:59:00 PST"
1.9.3p125 :005 > project.deadline
=> Sun, 10 Feb 2013 07:59:00 UTC +00:00
ActiveRecord uses Time.zone.parse to parse the strings internally.
When you run Time.parse it will convert the timestamp to your configured timezone in rails. For example, my rails app runs in EST.
[5] pry(main)> Time.parse('Jan 1st 2013 00:00:00 EST')
=> 2013-01-01 00:00:00 -0500
[6] pry(main)> Time.parse('Feb 9th 2013 23:59:00 PST')
=> 2013-02-10 02:59:00 -0500
Notice the +3:00hrs for the PST timestamp used to get the result into my EST timezone.
To get the UTC version of each timestamp, just call utc
[7] pry(main)> Time.parse('Jan 1st 2013 00:00:00 EST').utc
=> 2013-01-01 05:00:00 UTC
[8] pry(main)> Time.parse('Feb 9th 2013 23:59:00 PST').utc
=> 2013-02-10 07:59:00 UTC

Preserving timezones in Rails

If I have a time string of the form "Wed, 22 Jun 2011 09:43:58 +0200" coming from a client, I wish to store it with the time zone preserved. This is important because it's not just the absolute UTC time that is important but also the timezone.
Time.zone.parse(t) will convert the time to whatever the zone that Time.zone is using at the time, losing the source timezone.
Do I have to manually extract the timezone from the above string or is there an idiomatic way to do this?
A DateTime field can only store 'YYYY-MM-DD HH:MM:SS' (MySQL), no Time Zone info.
You should store the datetime in UTC, and the Timezone in a different field, preferably as an integer specifying the offset from UTC in minutes.
You can extract the offset like this:
ruby-1.9.2-p180:001:0>> require 'active_support/all' # included by Rails by default
# => true
ruby-1.9.2-p180:002:0>> dt = DateTime.parse "Wed, 22 Jun 2011 09:43:58 +0200"
# => Wed, 22 Jun 2011 09:43:58 +0200
ruby-1.9.2-p180:003:0>> dt.utc_offset
# => 7200
ruby-1.9.2-p180:004:0>> dt.utc
# => Wed, 22 Jun 2011 07:43:58 +0000
EDIT:
And to round trip the excercise
ruby-1.9.2-p180 :039 > u.utc.new_offset(u.offset)
=> Wed, 22 Jun 2011 09:43:58 +0500
ruby-1.9.2-p180 :040 > u
=> Wed, 22 Jun 2011 09:43:58 +0500
I think you are looking for the following solution:
In ApplicationController:
before_filter :get_tz
def get_tz
#tz = current_user.time_zone
end
def use_tz
Time.use_zone #tz do
yield
end
end
And in a controller add around filter at the beginnig
around_filter :use_tz

Resources