Reputation: 14631
There is task model with attributes when
and duration
.
create_table "tasks", force: true do |t|
...
t.datetime "when"
t.integer "duration"
...
end
I wrote method for checking if task is active so I can show it on page. This is active method:
def active?
if (self.when + self.duration) > Time.now
true
end
end
I tried in console to inspect object:
t.when + t.duration
=> Sun, 08 Sep 2013 01:01:00 UTC +00:00
DateTime.now
=> Sun, 08 Sep 2013 01:57:13 +0200
t.active?
=> true
It's true
but I entered 1:00 time and 1 minute for duration
and I hoped it shouldn't be true.
It seems that when
column in database is not saved in correct time zone, so it gives incorrect results. How to solve this issue?
Upvotes: 0
Views: 641
Reputation: 48599
It seems that when column in database is not saved in correct time zone
1) Rails automatically converts times to UTC time before inserting them in the database (which is a good thing), which means the times have an offset of "+0000" . That means if you save a time of 8pm to the database, and your server is located in a timezone with an offset of "+0600", then the equivalent UTC time is 2pm, so 2pm gets saved in the database. In other words, your local server's time is 6 hours ahead of UTC time, which means that when it's 8pm in your server's time zone, it's 2pm in the UTC timezone.
2) When you compare dates, ruby takes the timezone offset into account--in other words ruby converts all times to the same timezone and then compares the times. Here is an example:
2.0.0p247 :086 > x = DateTime.strptime('28-01-2013 08:00:00 PM +6', '%d-%m-%Y %I:%M:%S %p %z')
=> Mon, 28 Jan 2013 20:00:00 +0600
2.0.0p247 :087 > y = DateTime.strptime('28-01-2013 08:20:00 PM +7', '%d-%m-%Y %I:%M:%S %p %z')
=> Mon, 28 Jan 2013 20:20:00 +0700
2.0.0p247 :088 > x < y
=> false
If you just compare the times of the two Datetime objects, x is less than y. However, y has a time of 8:20pm in a timezone that has an offset of +7, which is equivalent to the time 7:20pm in a timezone with an offset of +6. Therefore, y is actually less than x. You need to compare apples to apples, which means you need to mentally compare times that have been converted to the same timezone to get the same results as ruby/rails produces.
3) You can convert Time.now to a UTC time using the rails utc() method:
2.0.0p247 :089 > x = Time.now
=> 2013-09-07 8:00:00 +0600
2.0.0p247 :090 > x.utc
=> 2013-09-07 02:00:00 UTC
That's what ruby does before comparing Time.now to task.when + task.duration
4) You might find it more convenient to create a DateTime object with the time you want using:
DateTime.strptime('28-01-2013 08:00:00 PM +0', '%d-%m-%Y %I:%M:%S %p %z'
Because you are able to specify the offset as zero, you don't have to create a time that anticipates the conversion to UTC time.
Or you can use the change() method, which causes the offset() to change without converting the time:
2.0.0p247 :011 > x = DateTime.now
=> Sun, 08 Sep 2013 00:34:08 +0600
2.0.0p247 :012 > x.change offset: "+0000"
=> Sun, 08 Sep 2013 00:34:08 +0000
Upvotes: 1
Reputation: 461
ActiveRecord stores timestamps in UTC by default. See How to change default timezone for Active Record in Rails? for changing default time zone.
You can also just use Time#in_time_zone to convert t.when to your timezone, see http://api.rubyonrails.org/classes/ActiveSupport/TimeWithZone.html.
Upvotes: 0