WinstonYW

Just Another Tech Journal

Projects

Elsewhere

Time.now vs Time.zone.now

03 MARCH 2014

Ruby Time can get confusing when used in your Rails app. What Time is it now?

Suppose I have a server in UTC time and all of my users are from Singapore (GMT +08:00), then your Rails app's time_zone should be configured to SGT.

# config/application.rb

# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
config.time_zone = 'Singapore'

Besides that, instead of using Time.now, use Time.zone.now especially when you are printing time attributes as display to your users.

Why? Let's explore the difference:

irb> Time.zone = "Singapore"
=> "Singapore"
irb> Time.now
=> 2014-03-03 11:07:05 +0000
irb> Time.zone.now
=> Mon, 03 Mar 2014 19:07:12 SGT +08:00

Time.now uses the system time because it's is part of the Ruby standard library.

However, Time.zone.now returns the time, corrected to our Singapore time zone because it's been extended by ActiveSupport to have the zone method.

Therefore, in order for your users to see the time in SGT, use Time.zone.now in your code. In fact, use Time.current. Read the code to find out what's the difference.

What about DateTime?

irb> Time.zone = "Singapore"
=> "Singapore"
irb> DateTime.now
=> Mon, 03 Mar 2014 11:15:08 +0000
irb> DateTime.now.in_time_zone
=> Mon, 03 Mar 2014 19:15:14 SGT +08:00

DateTime returns the system time too because it's also part of the Ruby standard library.

The method to convert DateTime to our time zone is the .in_time_zone method which also accepts a time zone as a param.

Now then.. What do you do when you query the database with ActiveRecord?

You can use either Time.now or Time.current. Both will work because ActiveRecord will convert the times to UTC before sending it to the database, as all time columns in the database are also stored in UTC. Let's verify..

irb> Feedback.all
  Feedback Load (0.4ms)  SELECT "feedbacks".* FROM "feedbacks"
=> [#<Feedback id: 1, created_at: "2014-03-03 11:29:26", updated_at: "2014-03-03 11:29:26">]

Notice that created_at and updated_at columns are stored in UTC.

irb> Feedback.first.created_at
  Feedback Load (0.4ms)  SELECT "feedbacks".* FROM "feedbacks" ORDER BY "feedbacks"."id" ASC LIMIT 1
=> Mon, 03 Mar 2014 19:29:26 SGT +08:00

When you retrieve an object and print created_at, it will be converted to your time zone.

irb> Time.now
=> 2014-03-03 11:41:31 +0000
irb> Feedback.where("created_at < ?", Time.now)
  Feedback Load (0.5ms)  SELECT "feedbacks".* FROM "feedbacks" WHERE (created_at < '2014-03-03 11:41:32.687457')
=> [#<Feedback id: 1, created_at: "2014-03-03 11:29:26", updated_at: "2014-03-03 11:29:26">]

When you use Time.now, it generates a SQL query with time in UTC.

irb> Time.current
=> Mon, 03 Mar 2014 19:42:22 SGT +08:00
irb> Feedback.where("created_at < ?", Time.current)
  Feedback Load (0.4ms)  SELECT "feedbacks".* FROM "feedbacks" WHERE (created_at < '2014-03-03 11:42:23.549020')
=> [#<Feedback id: 1, created_at: "2014-03-03 11:29:26", updated_at: "2014-03-03 11:29:26">]

When you use Time.current, it also generates a SQL query with time in UTC.

Hope this helps to explain Time in Rails!

Further reading:

comments powered by Disqus