Manipulating TimeWithZone in Rails - ruby-on-rails

I have this rails application that needs to capture a time date and a time zone in one of the forms, for example:
Time zone: 'Hong Kong' as string
Date time: '31/12/2013 23:59' as string
The default time zone for the application is set to 'Melbourne', the application currently accept the date time and automatically convert to '2013-12-31 23:59:00 +1100', which +1100 is the Melbourne time zone offset with daylight saving.
I want the application to:
1) Take '31/12/2013 23:59:00' as a time of the selected time zone, i.e. Hong Kong
2) Convert the Hong Kong time to Melbourne time, i.e. '31/12/2013 23:59:00 +0800' to '01/01/2014 02:59 +1100' and persist into the database.
3) During the conversion in 2), daylight saving will need to be taken care of.
I have written up the following code would do exactly what i want it to do ONLY works in the controller. However, it doesn't work when I move it to the model with before_create filter
time_zone = ActiveSupport::TimeZone.new(params[:time_zone])
date_time = DateTime.strptime(params[:date_time], '%d/%m/%Y %H:%M')
new_date_time = date_time.change(offset: time_zone.formatted_offset(false))
melbourne_date_time = new_date_time.in_time_zone(Time.zone)
The reason it doesn't work in the model is that in the controller, I manually parse the date time string to a date time object. But in the model, rails automatically convert the date time string into a TimeWithZone object... therefore, can't really alter the object...
I have done fair a bit of googling and still can't work out the exact solution.
Any help will be appreciated!
P.S. The application is on Rails 3.2.12. I am planning to run the conversion within a method attached with the before_create filter of a model.
Cheers

You can simply set default time_zone for your rails application.
Open up config/application.rb
set default time_zone by using this
class Application < Rails::Application
config.time_zone = 'Hong Kong'
config.active_record.default_timezone = 'Hong Kong'
end

I have found a (dirty?) way to archive what I want in the model.
User input from the form:
Time Zone - 'Hong Kong' as String,
Date Time - '31/12/2013 23:59:00' as String
config.time_zone = 'Melbourne'# in config/application.rb
self.time_zone # 'Hong Kong' as String
self.date_time # '31/12/2013 23:59:00 +1100' as TimeWithZone
class MyClass < ActiveRecord::Base
before_create :populate_date_time_with_zone
def populate_date_time_with_zone
original_tz = Time.zone # Capture the system default timezone, 'Melbourne'
Time.zone = self.time_zone # Change the timezone setting for this thread to 'Hong Kong'
dt = Time.zone.parse(self.date_time.strftime('%d/%m/%Y %I:%M%p')) #Dirty??
self.date_time = dt.in_time_zone(original_tz) # '01/01/2014 02:59:00 +1100' as TimeWith Zone
end
end
Is there a better way of doing it than printing the date time into a string than parse it again with an altered Time.zone?

Related

Ruby - Is there a way to get the timestamp for a specific future time but have that be dependent on timezone

I have an email system. I want to send emails to people at 8AM local time. If I have their timezone in this format: "America/New_York" how can I get a Time object with the next instance of 8AM for that timezone?
I assume that you have a User model and each user has its timezone stored in an attribute timezone and timezone #=> 'America/New_York'.
Then you can add a method like the following to your User model:
def next_time_it_is_8am_in_this_users_timezone_in_utc
time = ActiveSupport::TimeZone[timezone].now.change(hour: 8)
time = time + 1.day if time.past?
time.utc
end

How to present Rails form datetime select in different time zone?

I would like to present a datetime select to the user in their preferred time zone but store the datetime as UTC. Currently, the default behavior is to display and store the datetime field using UTC. How can I change the behavior of this field without affecting the entire application (i.e. not changing the application default time zone)?
Update: This is not a per-user timezone. I don't need to adjust how times are displayed. Only these specific fields deal with a different time zone, so I would like the user to be able to specify the time in this time zone.
Here's how you can allow the user to set a date using a specific time zone:
To convert the multi-parameter attributes that are submitted in the form to a specific time zone, add a method in your controller to manually convert the params into a datetime object. I chose to add this to the controller because I did not want to affect the model behavior. You should still be able to set a date on the model and assume your date was set correctly.
def create
convert_datetimes_to_pdt("start_date")
convert_datetimes_to_pdt("end_date")
#model = MyModel.new(params[:my_model])
# ...
end
def update
convert_datetimes_to_pdt("start_date")
convert_datetimes_to_pdt("end_date")
# ...
end
def convert_datetimes_to_pdt(field)
datetime = (1..5).collect {|num| params['my_model'].delete "#{field}(#{num}i)" }
if datetime[0] and datetime[1] and datetime[2] # only if a date has been set
params['my_model'][field] = Time.find_zone!("Pacific Time (US & Canada)").local(*datetime.map(&:to_i))
end
end
Now the datetime will be adjusted to the correct time zone. However, when the user goes to edit the time, the form fields will still display the time in UTC. To fix this, we can wrap the fields in a call to Time.use_zone:
Time.use_zone("Pacific Time (US & Canada)") do
f.datetime_select :start_date
end
There are a couple of options:
Utilize the user's local timezone when displaying data to them. This is really easy with something like the browser-timezone-rails gem. See https://github.com/kbaum/browser-timezone-rails. It is essentially overriding the application timezone for each request based on the timezone detected from the browser. NOTE: it only uses the OS timezone, so it's not as accurate as an IP/geo based solution.
Setup your application timezone so that it is consistent with the majority of your user base. For example: config.time_zone = 'Mountain Time (US & Canada)'. This is a very standard thing to do in rails. Rails will always store the data in the DB as UTC, but will present / load it using the application timezone.
Create a timezone for your user model. Allow users to set this value in their account settings. And, then use a similar approach to that of the above gem does in the application_controller.

How to change Rails app time zone setting based on user's input?

I have a User model, which allow user to provide their own time zone. User can choose their own time zone using the time_zone_select. So we will store something like Pacific Time (US & Canada) in database.
My Rails 3 application default setting is using Pacific Time (US & Canada). So all the time display is in this time zone.
May I change the time time display based on user time zone? For example, User A will see all the time displayed in his time zone Central Time (US & Canada), and User B will see all the time in London.
Thanks all.
In controllers/application.rb
before_filter :set_user_time_zone
private
def set_user_time_zone
Time.zone = current_user.time_zone if logged_in?
end
From railscast
First you'll need to add a column to your users table, to keep the selected timezone for this user (user.timezone should be fine).
in the form, you'll need to use ActiveSupport's #time_zone_select to allow the user to select their desired timezone.
finally, you'll need a before filter in ApplicationController to set the current sessions's timezone to the user's specific timezone.
Here is a gist with the migration, partial view and application controller filter:
https://gist.github.com/eladmeidar/6121183

Saving a Rails Record with a specific Timezone

How to save an event in Berlin with its own specific timezone and the next event in Tijuana with a different one?
The user is asked to choose a City for the event, as a source for e.g. +02:00.
I would like to receive a time code like this:
eventstart = "2011-07-22T18:00:00+02:00"
How would you go about creating that form?
UPDATE:
Realized saving as standard UTC is fine for many reasons. So now I am altering a time string in the view to present a *distance_to_time_in_words* for eventstart, depending on the user's local time.
Event in Tijuana, viewing from Berlin time:
old_zone = Time.zone
#=> "Berlin"
Time.zone = "Tijuana"
t = Time.zone.parse(eventstart.to_s[0..-7])
#=> 2011-07-22 18:00:00 +08:00
Time.zone = old_zone
#=> "Berlin"
distance_of_time_in_words(Time.now.in_time_zone, t)
#=> "9 hours"
cumbersome, but works for now. Improvement ideas welcome!
Add a time_zone column to your database and use time_zone_select in your form to let user select the time_zone for which he is creating event.
And in the model you can convert the datetime to zone specific datetime and store utc in the database. You can use helper something like below
def local_time(date)
Time.use_zone(self.time_zone) do
Time.zone.at(date.to_i)
end
end

How to save dates in local timezone to db with rails3?

I have Rails3 application with model user and field expires_at created like this:
t.column :expires_at, :timestamp
In my database (postgresql) it has type:
timestamp without timezone
The problem is when I call:
#user.expires_at = Time.now
#user.save
it is saved into database with UTC timezone (my local time is UTC + 1:00, Warsaw) but I don't want that. I just want to have time with my local timezone saved into the database (2011-03-30 01:29:01.766709, not 2011-03-29 23:29:01.766709)
Can I achieve this using rails3?
For saving time in local timezone to database this has to be set in application.rb
config.active_record.default_timezone = :local
If you only want to use local times on certain columns, rather than as a global setting, then the Rails documentation tells us this:
# If your attributes are time zone aware and you desire to skip time zone conversion to the current Time#zone when reading certain attributes then you can do following:
class Topic < ActiveRecord::Base
self.skip_time_zone_conversion_for_attributes = [:written_on]
end
(This also skips time zone conversion on writing, not just reading). And you can pass in an array of symbols for multiple attributes.
I am not sure which versions of Rails this was introduced in, though.

Resources