Which should I always parse date times with? DateTime, Time, Time.zone? - ruby-on-rails

In Rails, I see I have a few options to parse a date and a date-time. What is the best practice for parsing dates for the sole purpose of persisting them into the DB via ActiveRecord? And why?
Time.zone.parse(...)
Time.parse(...)
DateTime.parse(...)

Go with Time.zone.parse if you just want to write into ActiveRecord.
DateTime should be avoided. If you're handling dates, you should use Date.parse instead.
Beyond that, it depends on whether the input comes with timezone information, what the current timezone is set to, and whether you want timezones in your data.
Time.zone.parse will return an ActiveSupport::TimeWithZone, defaulting to UTC.
> Time.zone.parse("12:30")
=> Thu, 10 May 2012 12:30:00 UTC +00:00
Time.parse will return a Time, with a zone if it's specified in the input, or the local TZ.
> Time.parse("12:30")
=> 2012-05-09 12:30:00 -0700
For a more detailed explanation of Ruby time comparisons and precision, read this blog post:
http://blog.solanolabs.com/rails-time-comparisons-devil-details-etc/

Times that are automatically set by ActiveRecord (e.g. created_at and updated_at) come back as ActiveSupport::TimeWithZone instances.
According to the documentation, ActiveSupport::TimeWithZone and Time have the same API, so Time is what I would use.
On an unrelated note, there are methods to_time and to_datetime that you can call on strings:
"2012-05-09".to_time # => 2012-05-09 00:00:00 UTC
"2012-05-09".to_datetime # => Wed, 09 May 2012 00:00:00 +0000

Related

preserving datetime values in rails while saving in utc(default)

I have a a checkin:datetime field in rails and is using the default utc.
But the issue is that when the user submits the form the checkin date is coming with his local timezone info. So rails will automatically convert this to utc and depending on the difference with his timezone and utc there might be an off of one day.
So how can I change the date to utc without changing values?
Update
This is the only code I use for saving to database.(the utc conversion is done by activerecord(i think) if the passed in value is not utc)
reservation=current_user.reservations.create(reservation_params)
reservation.save
I have found a way to do that. It is not direct conversion as I expected but it works. I changed the form input field to sent a string with just the date and time(without utc).
eg: 2016-07-13 00:00:00
Then in my controller before saving I used below code to parse it to utc.
reservation.check_in= Time.zone.parse(value_from_view)
example:
reservation.check_in= Time.zone.parse('2016-07-13 00:00:00')
This returns Wed, 13 Jul 2016 00:00:00 UTC +00:00 as expected.

ActiveRecord DateTime field not matching object

i've got a Course model, which has a datetime attribute. If I look at it from the database i get one time, and if i look at it from the object i get a different date & time.
>> Course.last.attribute_for_inspect :datetime
=> "\"2012-01-04 01:00:00\""
>> Course.last.datetime
=> Tue, 03 Jan 2012 19:00:00 CST -06:00
Does anyone know why this value is different, and what I can do to fix it? The time from Course.last.datetime is correct, but my queries on the course table aren't working correctly due to the mix-up.
From the fine manual:
attribute_for_inspect(attr_name)
Returns an #inspect-like string for the value of the attribute attr_name.
[...]
Date and Time attributes are returned in the :db format.
So, when attribute_for_inspect is used for a datetime, it will return the string that the database uses for that value. Rails stores times in the database in UTC and any sensible database will use ISO 8601 for formatting timestamps on the way in and out.
2012-01-04 01:00:00 is the UTC timestamp in ISO 8601 format. When Rails returns a datetime, it converts it to an ActiveSupport::TimeWithZone instance and includes a timezone adjustment based on the timezone that you have configured in your application.rb. You're using the CST timezone and that's six hours behind UTC; subtracting six hours from 01:00 gives you 19:00 and you lose a day from crossing midnight. The human friendly format, Tue, 03 Jan 2012 19:00:00 CST -06:00, is just how ActiveSupport::TimeWithZone represents itself when inspected and the console uses x.inspect to display x.
As far as fixing your queries goes, just use t.utc before sending in a time t and you should be good.
Configuring your Rails App Timezone in application.rb
set config.active_record.default_timezone to :local as it is set to :utc by default in the application.rb
paste this code in your application.rb
config.active_record.default_timezone = :local #or :utc
config.time_zone = "Singapore" #your timezone

Did I just find a bug in rails Date format?

In trying to parse a date, I have been racking my brain for hours:
Date.today.to_s
=> "06/07/2011"
Date.today
=> Tue, 07 Jun 2011
Date.parse Date.today.to_s
=> Wed, 06 Jul 2011
Date::DATE_FORMATS[:default]
=> "%m/%d/%Y"
The default format for to_s is different than the default format for parsing? Why would they do this to me?
Using Rails 3.0.5 with Ruby 1.9.2-p180
UPDATE
So thanks to your answers, I realize that the DATE_FORMATS is a rails thing while Date.format is using the ruby library (correct?). Is there a way then to parse dates/times with the default DATE_FORMAT without using strptime?
Normally, Date.today.to_s would return "2011-06-07", but since you set a default date format, it's using "06/07/2011" instead.
Date.parse easily recognizes the YYYY-MM-DD format, but when it sees 06/07/2011 it thinks that's really DD/MM/YYYY (not MM/DD/YYYY as you're expecting -- keep in mind that Date.parse knows nothing about Rails' default date format you set. The default date format is only for Rails' outputting of Date.to_s).
You can force it to parse a MM/DD/YYYY date like this:
Date.strptime(Date.today.to_s, "%m/%d/%Y")
# => Tue, 07 Jun 2011
I would guess that the to_s method uses a locale option to determine how to write the date.
I dont see how this is an issue though. Date.parse uses heuristics to parse the date so sometimes it will get it wrong.
The Chronic gem (link)solves pretty much all date parsing issues (and yes, they can be quite annoying).
Chronic.parse("06/07/2011")
#=> 2011-06-07 12:00:00 +0000

How to convert Date into UTC in MongoMapper & Ruby/Rails?

I added this line of code
self.auth_history.push [start_date, self.coupon_code]
And got this error message
Date is not currently supported; use a UTC Time instance instead.
I also tried start_date.utc, but it didn't work either.
Please help. Thanks.
I got this answer from Seattle Brigade group -
===
I didn't see start_date defined in your code as a key in MongoMapper, so
I'll assume you're creating your own date object, either directly via Ruby,
or wrapped by Rails. As far as I know, and someone please correct me, Mongo
stores dates as UTC time in milliseconds since epoch. So when you define a
key with a :date mapping in MongoMapper, you're wrapping a Time object in
Ruby.
Therefore, if you want to store a date inside of Mongo, and it wasn't
created by MongoMapper, make sure you create a Time object in UTC.
MongoMapper comes with a Date mixin method called to_mongo that you can use.
>> Time.now.utc
=> Fri Jan 28 03:47:50 UTC 2011
>> require 'date'
=> true
>> date = Date.today
=> #<Date: 4911179/2,0,2299161>
>> Time.utc(date.year, date.month, date.day)
=> Thu Jan 27 00:00:00 UTC 2011
>> require 'rubygems'
=> true
>> require 'mongo_mapper'
=> true
>> Date.to_mongo(date)
=> Thu Jan 27 00:00:00 UTC 2011
But watch out for the time change.
>> Date.to_mongo(Time.now)
=> Thu Jan 27 00:00:00 UTC 2011
>> Date.to_mongo(Time.now.utc)
=> Fri Jan 28 00:00:00 UTC 2011
Good luck.
===
And by using
Date.to_mongo(start_date)
it works for me.
First, I think the question title is bad in description. Actually, the difference between different timezone is on Time not on Date. So, it's really not proper to say I want to convert a date to UTC format.
Here is another way in Ruby to convert DateTime to its UTC format:
DateTime.now.new_offset(0)
Here's another option:
Time.at(Date.today.to_datetime.to_i).utc
Here I am using Date.today as an arbitrary example date. Replace with whatever date you want to convert. Once the date is converted to a Time instance, it can be serialized to BSON without any problem, as Time is a supported primitive type, that is to say, it can be saved using MongoMapper to the database.
As per EfratBlaier's comment I have updated the answer.
Date.today.to_time.utc

Why does this rails query behave differently depending on timezone?

I have a rails time-based query which has some odd timezone sensitive behaviour, even though as far as I know I'm using UTC. In a nutshell, these queries give different answers:
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours).gmtime]).length
=> 279
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours)]).length
=> 280
Where the DB actually does contain one model created in the last hour, and the total number of models is 280. So only the first query is correct.
However, in environment.rb I have:
config.time_zone = 'UTC'
The system time zone (as reported by 'date') is BST (which is GMT+1) - so somehow this winds up getting treated as UTC and breaking queries.
This is causing me all sorts of problems as I need to parameterise the query passing in different times to an action (which are then converted using Time.parse()), and even though I send in UTC times, this 'off by one hour' DST issue crops a lot. Even using '.gmtime()' doesn't always seem to fix it.
Obviously the difference is caused somehow by an implicit conversion somewhere resulting in BST being incorrectly treated as UTC, but why? Doesn't rails store the timestamps in UTC? Isn't the Time class timezone aware? I am using Rails 2.2.2
So what is going on here - and what is the safe way to program around it?
edit, some additional info to show what the DB and Time class are doing:
>> Model.find(:last).created_at
=> Tue, 11 Aug 2009 20:31:07 UTC +00:00
>> Time.now
=> Tue Aug 11 22:00:18 +0100 2009
>> Time.now.gmtime
=> Tue Aug 11 21:00:22 UTC 2009
The Time class isn't directly aware of your configured timezone. Rails 2.1 added a bunch of timezone support, but Time will still act upon your local timezone. This is why Time.now returns a BST time.
What you likely want is to interact with Time.zone. You can call methods on this like you would the Time class itself but it will return it in the specified time zone.
Time.zone.now # => Tue, 11 Aug 2009 21:31:45 UTC +00:00
Time.zone.parse("2:30 PM Aug 23, 2009") # => Sun, 23 Aug 2009 14:30:00 UTC +00:00
Another thing you have to be careful with is if you ever do queries on the database where you are comparing times, but sure to use the UTC time (even if you have a different time zone specified) because Rails always stores UTC in the database.
Item.all(:conditions => ["published_at <= ?", Time.now.utc])
Also, instead of Time.now-1.hour do 1.hour.ago. It is easier to read and Rails will automatically use the configured timezone.
The TimeZone you need to set is UK, this will automatically handle BST
Time.zone = 'UK'
Time.zone.now
=> Sun, 17 Oct 2010 02:09:54 BST +01:00
start_date_format = DateTime.strptime(#start_date, date_format)
start_date_format_with_hour =
DateTime.strptime((start_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
end_date_format = DateTime.strptime(#end_date, date_format)
end_date_format_with_hour = DateTime.strptime((end_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
#filters_date = "invoices.created_at >= ? AND invoices.created_at < ?", start_date_format_with_hour, end_date_format_with_hour

Resources