Monkey-patching the Time class in Rails leads to time zone confusion - ruby-on-rails

I've got a Rails 3.2.6 app running on Ruby 1.8.7. The app is configured to use central European time (i.e. UTC+2) as its time zone, and in my initializers, I monkey-patch Time and DateTime with some custom functionality.
The odd thing is, that in my monkey-patched methods, the Time/DateTime instances act as if they're UTC (but using the time zone-adjusted value), but elsewhere in the app they respect the time zone config.
So, as an example, in config/initializers/monkey_patching.rb I have the following
module MonkeyPatching
def foo
inspect
end
end
class Time
include MonkeyPatching
end
class DateTime
include MonkeyPatching
end
Now, elsewhere in the app (or in the rails console), here's what I get
model.created_at.inspect #=> "Mon, 24 Sep 2012 15:06:34 CEST +02:00" (correct!)
model.created_at.foo #=> "Mon Sep 24 15:06:34 UTC 2012" (all wrong!)
So, calling inspect "directly" on model.created_at gives me the correct, timezone-adjusted result. But calling the patched-in method foo - which also just calls inspect! - treats the time as UTC, even though it isn't.
To add to my confusion, this only happens with model attributes. I.e. in the rails console, I get identical - and correct - results for DateTime.now.inspect and for DateTime.now.foo. But doing the same for a DateTime attribute, give me the strange behavior seen above.
Any idea why this happens (and how to fix it)?

Rails uses ActiveSupport::TimeWithZone for time attributes, not regular Ruby Time. Try to patch ActiveSupport::TimeWithZone too.
class ActiveSupport::TimeWithZone
include MonkeyPatching
end

Related

Converting UTC to user preferred timezone on server side in Rails

In my Rails app, I want to show all time instances in the timezone set by the user's preference rather than UTC.
This a common problem, and I have seen solutions to change the default UTC to one specific timezone (like EDT). I want to make the times dynamic though, and I would like to do it in my time_formats.rb initializer on the server side. In other words, users should be able to see a time in GMT 0, GMT +1, or GMT +2 based on their preference.
I have done this in a way that works locally but breaks when pushed to Heroku. Here's what I set up.
First, I have added timezone as a column in my Devise user model.
class AddTimezoneToUsers < ActiveRecord::Migration
def change
add_column :users, :timezone, :string
end
end
Next, I am formatting my time-formats.rb with:
Time::DATE_FORMATS[:default] = in_time_zone("%I:%M %p")
Locally, this works great. I can change my time zone preference in Settings and it adjusts across all time instances. However, my push to Heroku has failed for the following reason:
.../bundle/ruby/2.2.0/gems/activesupport-4.0.4/lib/active_support/values/time_zone.rb:283:
warning: circular argument reference - now
remote: rake aborted!
remote: NoMethodError: undefined method `in_time_zone' for main:Object
Having found in_time_zone from the Rails class documentation, why is it working locally but causing a method issue in Heroku?
EDIT 1: Just had another thought, and I wanted to add some other debugging. Since it is a method error, I now checked my ApplicationController.
before_filter :set_timezone
def set_timezone
tz = current_user ? current_user.timezone : nil
Time.zone = tz || ActiveSupport::TimeZone["London"]
end
Since that is setting the default as London before a user changes current_user.timezone, I don't think it should conflict. Wanted to include though.
After debugging for another hour, I figured out my mistake.
In the time_formats.rb file where I am trying to set the time zone to the user's preference, I used the following:
Time::DATE_FORMATS[:default] = in_time_zone("%I:%M %p")
The in_time_zone was causing the problem. I am already setting using the Application Controller to modify the time to the user's setting with current_user.timezone : nil. By changing my time_formats.rb to:
Time::DATE_FORMATS[:default] = "%I:%M %p"
I am now showing the correct time based on the user settings, and I can push to Heroku successfully while letting the Application Controller do the work.
Setting timezone as per user in server might not be a good idea, what happens if the user moves to a different timezone, are you going to change all the time instances saved in server to that?
What I have found from googling it that, do not interfere with specific timezone's in server, but create a timezone attribute for user and serve contents accordingly.
I found out that the below blog post describes about this scenario very well.
https://www.reinteractive.net/posts/168-dealing-with-timezones-effectively-in-rails

How does Rails know my timezone?

How does Rails know my time zone? I don't have it set in application.rb:
# config.time_zone = 'Central Time (US & Canada)'
I searched the whole app for time_zone and that is the only instance of it. I don't see any environment variables for time zone set either. I'm on Windows.
C:\Users\Chloe\workspace\MyBestBody>rails runner "p Time.now"
DL is deprecated, please use Fiddle
2015-06-12 23:38:33 -0400
It prints UTC time when deployed to Heroku.
C:\Users\Chloe\workspace\MyBestBody>heroku run rails console
Running `rails console` attached to terminal... up, run.1949
Loading production environment (Rails 4.2.1)
irb(main):001:0> Time.new
=> 2015-06-13 03:28:34 +0000
Rails 4.2.1
From gettimeofday(), or so the ruby MRI source would have me believe:
int __cdecl
gettimeofday(struct timeval *tv, struct timezone *tz)
{
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
filetime_to_timeval(&ft, tv);
return 0;
}
Per comment, this is a C function, called by the ruby date routines under the hood. You do not call the method yourself, rather you call the ruby methods you are calling.
Your question involves two separate things.
The first is what ruby thinks the time zone is. This is the zone used when you use things likeTime.now or Time.mktime. Ruby uses the C level APIs provided by the operating system to get this information (the TZ environment variable can override this on unixy operating systems).
The second is the time zone your users will see when they use your app. This is frequently not the same thing. Even if your users were all in the same timezone it's a good idea to have your servers use UTC because it is free from things like daylight savings time.
For this reason rails has its own timezone system and the setting you found controls the default value of that timezone. Sometimes you might overwrite this on a per user basis. Rails always records information in UTC, but it will be displayed in views using the value of Time.zone (And input from forms is interpreted in that zone). You can see what the current time in this zone is by doing
Time.zone.now

Rails Postgres Time Zone Handling

The simple thing I want is to store a DateTime in UTC in the database and retrieve it in UTC again, but rails seems to assume I stored the DateTime in my local time zone and adds the 8 hours difference (PST). I have a DateTime field in my model. If I send a request (PUT) to update this field in a particular instance of my model, somehow timezones get mixed up for whatever reason:
Sending to rails via PUT: 2012-02-17T03:46:58Z
Returned via subsequent GET: 2012-02-17T11:46:58Z
The time difference is exactly 8 hours and could be explained by my timezone which is PST (-08:00).
I am using Datamapper 1.2 on Rails 3.1.3.
I set my application config timezone to UTC explicitly.
I also tried to use dm-zone-types. Did not change anything for me.
A git-bisect on my repo revealed, that this misbehavior was introduced as I switched the database to postgres from the original sqlite. This commit only changed the database.yml and the gemfile. Nothing else.
Any ideas?
I have found a hacky solution myself:
Set the servers timezone in the environment variable TZ to 'UTC'. In order to persist this configuration option, I decided to put the environment variable setting in config/boot.rb:
ENV['TZ'] = "UTC"
I still feel dirty and this solution still gives me the willies. So any better/cleaner solution is highly appreciated!

Ruby on Rails, delayed_job serializing incorrect datetime as GMT

I have some code that seems to execute improperly when it is serialized and run with delayed_job. To get the necessaries out of the way, I am running Ubuntu 11.04, Ruby 1.8.7 with Rails 3.0.4.
I have a datetime field in one of my tables that obviously stores the date and time of a particular event. In my RoR application, I use this field through the application calling strftime on it to format it in different ways. The output of this formatting is correct on the web page.
I am also using delayed_job to put this same field into an email that is sent out (triggered via some action). When the email arrives, it appears that it has somehow been formatted as GMT. For example, if the date time was supposed to be 11-12-2011 15:30:00 (3:30 PM) in the database, the email would read 11-12-2011 22:30:00 (10:30 PM).
I looked in the database and found somethings that are interesting:
The datetime in the database is 2011-11-12 22:30:00
The app, when displayed via the web, formats the data properly as 11-12-2011, 3:30 PM
The email, formats the data properly as 11-12-2011, 10:30 PM
When I run a small ruby file that simply prints the date and time
p Time.now
I get this the correct output (local, non GMT time)
Mon Aug 15 10:15:27 -0600 2011
When I look at what is in the serialized yaml for the delayed_jobs table, I can see that the date field is formatted as
2011-11-12 22:30:00 Z
In my application.rb, I have
config.time_zone = 'Mountain Time (US & Canada)'
and in my environment.rb, I have
timezone = "Mountain Time (US & Canada)"
Can any one help me figure out what is going on here? I am relatively new to RoR, so this may be a super obvious, easy question. If you need any more debug information, please let me know and I can post it up. Similarly, if my question is unclear, I can try to clar
In my case, I solved explicitly specifying the method in_time_zone in the view
example: #my_obj.created_at.in_time_zone.
I know it is not elegant, but so far I have not found better solutions.
Try changing the date object to a string before serializing.
You may want to set the timezone in the database. If it's MySQL, see: http://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html

DateTime Problem in Rails Unit Tests

I'm working on unit tests for my Rails app and have run into the following problem.
I have an Event model with a fixture that boils down to:
concert:
name: Wallflowers
start_at: <%= DateTime.new(1999) %>
In my unit test, I have the following assertion:
assert_equal DateTime.new(1999), events(:concert).start_at
The test fails, with the following message:
<Fri, 01 Jan 1999 00:00:00 +0000> expected but was
<Thu, 31 Dec 1998 19:00:00 UTC +00:00>.
I can't figure out why its getting adjusted. The offset of the incorrect time is 5 hours, which is my local offset.
Other info that might be relevant:
The problem only occurs while testing--I don't have any problems in development
environment.rb contains config.time_zone = 'UTC'
The test works if I use Date.new instead of DateTime.new, but I need to use DateTime
What am I missing? Appreciate the help.
Change it to this
concert:
name: Wallflowers
start_at: <%= DateTime.new(1999).to_s(:db) %>
ActiveRecord automatically converts all inserted timestamps to UTC. This explains why your concert time is adjusted to a different timezone. The first step would be to see if the problem is solved by you setting your config.time_zone in environment.rb to your timezone.
If that doesn't solve the issue, read on:
After some testing, I discovered that there seems to be some discrepancy with using DateTime in fixtures compared to the same code in a controller. If I used DateTime.new(1999) in my controller, the inserted column was 1999-01-01 00:00:00. If I used the same call in my fixture, the inserted column was 1999-01-01 10:30:00, which is my timezone. This was regardless of what config.time_zone was set to.
In both cases, changing the timezone correctly changed the ActiveRecord object fetched from the database.
In truth, I don't know which is the correct representation. I do know that the test passed when I changed the fixture to '1999-01-01 00:00' instead of using DateTime.new(1999). If the solution in the first paragraph doesn't work for you, try changing the fixture to the string representation of the date.

Resources