How to display the time in user's timezone - ruby-on-rails

I am using rails 3.0.5 and I have created_at and updated_at stored in UTC. Now I want to display the created_at time in users' timezone. I believe it is possible to pick user's timezone from the browser and then convert time to user's timezone.
I am sure rails will have a gem/plugin to take care of something like this. Is there something?

Rails by default converts every date into UTC before storing the value into the database. This means that, regardless the server timezone, you always have UTC dates in your database.
In order to convert the dates into your user's timezone you have basically two possibilities:
server-side approach
client-side approach
Server-side approach
If your site allows registration, you can store the user timezone as user preference. In the user table, store the user timezone. Then create a custom helper you can use to format any date/time into the proper timezone using the in_time_zone method.
> t = Time.current
# => Mon, 23 Dec 2013 18:25:55 UTC +00:00
> t.zone
# => "UTC"
> t.in_time_zone("CET")
# => Mon, 23 Dec 2013 19:25:55 CET +01:00
Your helper may looks like
def format_time(time, timezone)
time.in_time_zone(timezone)
end
I normally also like to output a standard format, using the I18n.l helper
def format_time(time, timezone)
I18n.l time.to_time.in_time_zone(timezone), format: :long
end
Client-side approach
If your site has no registration or you don't want to ask your users for their timezone or you simply want to use the user system timezone, then you can use JavaScript.
My suggestion is to create a custom helper that will print out every time in a proper way so that you can create a generic JavaScript function to convert the values.
def format_time(time, timezone)
time = time.to_time
content_tag(:span, I18n.l(time, format: :long), data: { timezone: timezone, time: time.iso8601 })
end
Now, create a JavaScript function that is performed on DOM load and that will select all the HTML tags with data-time attribute. Loop them and update the value inside the span tag with the proper time in the given timezone.
A simple jQuery example would be
$(function() {
$("span[data-time]").each(function() {
// get the value from data-time and format according to data-timezone
// write the content back into the span tag
});
});
I'm not posting the full code here, since there are plenty of JavaScript time formatters available with a simple search. Here's a few possible solutions
Convert date to another timezone in JavaScript
Convert date in local timezone using javascript

There is a nice gem by Basecamp called local_time for client side rendering - https://github.com/basecamp/local_time. It's great for applications where user is not signed in and it's caching friendly.

You can add this to your application controller to convert all times to the User's timezone:
class ApplicationController < ActionController::Base
around_filter :user_time_zone, :if => :current_user
def user_time_zone(&block)
Time.use_zone(current_user.timezone_name, &block)
end
end
You just have to capture the user's timezone

Assuming that the value you want displayed is coming from the database, :ie started_at and is (as is the default) stored in UTC.
If you have the user's timezone as an offset you can also localize the time by doing:
started_at.in_time_zone(-2)
=> Mon, 24 Feb 2014 23:07:56 GST -02:00
Which then can be munged in all sorts of way to get the parts you want:
started_at.in_time_zone(-2).yesterday
=> Sun, 23 Feb 2014 23:07:56 GST -02:00
started_at.in_time_zone(-2) + 3.days
=> Thu, 27 Feb 2014 23:07:56 GST -02:00

http://api.rubyonrails.org/classes/Time.html#method-c-use_zone
This is what you're looking for :
Time.use_zone(#user.timezone) do
blah blah blah
end

If you'd like to convert your date to a specific Timezone:
deadline.in_time_zone(time_zone)
Here deadline is a date.
In addition, you can find Universal time through your local machine Timezone plus local time and vice verse, like in Karachi - +05:00, you can simply add it to value in Universal time to find time in your time zone or get Universal time from your local time by subtraction of Timezone difference (05:00 in our case) from your local time

My jquery is a rusty, so it took me a little while to figure out how to implement the client-side approach of the accepted answer above.
Here's my solution:
HTML:
<span data-time="<%= last_message_at %>"> </span>
Jquery/Javascript:
<script type="text/javascript">
$(document).ready($(function() {
$("span[data-time]").each(function() {
var timestamp = $(this).attr("data-time");
var localeTimestamp = new Date(timestamp).toLocaleString();
$(this).html(localeTimestamp);
});
}));
</script>

You might want to take a look at these links in addition to the one Ben posted:
http://railscasts.com/episodes/106-time-zones-in-rails-2-1

Related

Rails and Postgres problem querying record that was stored in UTC; app Timezone is UTC but record is not being found

I have a Rails 5 app with default Timezone settings (UTC).
I have a record that was created on Thu, 08 Aug 2019 02:12:56 UTC +00:00 but the app is being accessed by someone located at Central Time which means the record was created for them on Wed, 07 Aug 2019 20:12:56 CST -06:00; The app provides a filter and this user is trying to retrieve records from 08/07/2019 and is expecting that this record would be part f the resulting set, date filter is being passed as a Date object.
I have tried several forms to convert this Date object into a form that when passed down to the query it successfully returns the expected results. Note: filter.end_date is an instance of Date with value "Wed, 07 Aug 2019"
Query:
Record.where("records.created_at BETWEEN ? AND ?", filter.start_date.beginning_of_day, filter.end_date.end_of_day)
SQL:
Record Load (4.2ms) SELECT "records".* FROM "records" WHERE (records.created_at BETWEEN '2019-08-07 00:00:00' AND '2019-08-07 23:59:59.999999')
The only way that I have achieved this is when I set Timezone to central time and then pass the query:
This is working
Time.use_zone("Central America") { Record.where("records.created_at BETWEEN ? AND ?", date_filter.end_date.beginning_of_day, date_filter.end_date.end_of_day) }
SQL:
Record Load (4.2ms) SELECT "records".* FROM "records" WHERE (records.created_at BETWEEN '2019-08-07 06:00:00' AND '2019-08-08 05:59:59.999999')
I am wondering if there is a way to avoid the necessity to set a Timezone per user within the app and just do the correct conversion when passing down the date provided in the filter.
Unfortunately there isn't a simple way to ignore the timezone.
If you are getting the users timezone on sign up you could add something like Time.zone(current_user.timezone) if current_user into a before action callback or some other initial startup sequence. The has the disadvantage of making the value mostly static, ie if they are traveling and are in a different timezone your results will still be for your "home" timezone unless you add that as an option in their profile etc.
Alternatively you could set the current timezone dynamically using javascript:
window.addEventListener("submit", function(event) { // listen to all submit events
var form = event.target.closest('form');
var timezoneInput = form.querySelector('input[name=timezone]');
if (timezoneInput) {
timezoneInput.value = moment.tz.guess(); // assuming you are using a time library
}
});
And handle that in your controller:
def index
Time.use_zone(params[:timezone]) do
#records = Record.where("records.created_at BETWEEN ? AND ?", date_filter.end_date.beginning_of_day, date_filter.end_date.end_of_day)
end
end
For a database solution (which is commendable since you use timestamp with time zone), just set the timezone parameter for the database session to the current time zone of the user. Then everything should work as expected.
Whether or not you should store user's timezone in the DB depends on how often you will need to show times in a specific timezone. Best practice is to set your application to use the time zone where most of your users are. If you have not already, you probably want to set this application.rb for example:
config.time_zone = 'Eastern Time (US & Canada)'
For complete list you can run rake time:zones:all
Now make sure you always use zone. Anywhere in your application you will want to replace Time.now with Time.zone.now and Date.today with Date.current or better Time.zone.now.to_date.
If in your case you just want to filter records timestamps (i.e. created_at) I'm guessing you use some kind of datetime picker on the front end in a form etc. There is likely an option to add the users's browser's time offset. Here's an example of using jQuery datetimepicker. The additional format option is O
$(function() {
$("#datetimepicker").datetimepicker({
timepicker: true,
format: 'm/d/Y H:i O',
maxDate: '0'
});
})
Now you will have the datetime with timezone offset value which you can use in your database query. You'll be able to do something like this rough example.
start_time = Time.zone.parse(params[:start_time].to_s)
end_time = Time.zone.parse(params[:end_time].to_s)
Record.where('created_at > ? and created_at < ?', start_time, end_time)
I would also recommend checking out this Railscast, old but relevant.

Rails Twitter created_at TimeZone

Never dealt with timezones before. Haven't heard pleasant things.
I am pulling Tweets from a Twitter account to post on an application. I am using the following line of code to display the date as follows: November 26th, 2015.
<%= Date::MONTHNAMES[tweet.created_at.month] %> <%= tweet.created_at.day.ordinalize %>, <%= tweet.created_at.year %>
However, the time is displayed as +0600 further into time than my current location. I am located in Canada/US Central which is -0600. All my local records are correct (both in time and timezone), but I assume this is different because I am pulling it from a non-local source.
2015-11-27 03:07:04 +0000 is the date displayed from the created_at property of the tweet.
I am wanting to change the tweet's time so that is correct for my timezone at minimum, if not too hard to do I'd prefer it to be compatible with all timezones.
You can use in_time_zone method to convert the time to your desired timezone:
created_at = '2015-11-27 03:07:04 +0000'
created_at.in_time_zone('Central Time (US & Canada)')
# => Thu, 26 Nov 2015 21:07:04 CST -06:00
ActiveRecord keeps track of time zones and stores all times as UTC in the database. In you're code you're retrieving that time from the database, but using a very non-standard way to output it which is bypassing ActiveRecord's time zone conversion.
you can try this
tweet_time = tweet.created_at.in_time_zone
tweet_time.strftime("#{tweet_time.day.ordinalize} %B, %Y")
to get the time in your server's timezone, which can be configured in config/application.rb
have a look here and here for more information on working with time zones
In order to get each user to see the time in their own zone you need to capture each user's timezone, then can do this in your action controller
around_filter :user_time_zone, :if => :current_user
def user_time_zone(&block)
Time.use_zone(current_user.time_zone, &block)
end

Submitting dates in US format: mm/dd/yyyy

I can get Rails to display mm/dd/yyyy using the following initializer:
date_format.rb
Date::DATE_FORMATS[:default]="%m/%d/%Y"
or l18n using this answer
But this does not change the way dates are submitted. When submitted:
10/02/2014
Will be parsed as
#entry.photo_date
Mon, 10 Feb 2014
I'm looking for a way to submit a date in mm/dd/yyyy format.
I'd like to be able to do this centrally, so I do not have to repeat code throughout the application
In modern ruby (ie. with prepend) you can insert your own type casting in front of Rails's. You'll want to do this for whatever other date/time formats you're using. Here's the code for Date, just stick this in an config/initializers/typecasts.rb or somewhere:
module Typecasting
module Date
def cast_value v
::Date.strptime v, "%m/%d/%Y" rescue super
end
end
::ActiveRecord::Type::Date.prepend Date
end
Rails will try the American format and fall back to using the builtin method if that didn't work.
Try this:
irb(main):001:0> today = Date.today
=> Tue, 06 Jan 2015
irb(main):003:0> today.strftime("%m/%d/%Y")
=> "01/06/2015"
So in your case, #entry.photo_date.strftime("%m/%d/%Y") should work.

How do I keep the time, but change the time zone?

I've looked at a few StackOverflow questions but none of them seem to crack the case.
My time for example is :
2012-04-19 08:42:00 +0200
This is what is inputed through the form. But everything is displayed relative to what timezone it is in. So because the computer works in UTC, this time after it is saved comes out as :
Wed, 18 Apr 2012 23:42:00 PDT -07:00
How do I keep the same time, but just change the zone?
I think the method Time.use_zone might help you. In the app I'm working one we wanted times to be interpreted according to the current user's time zone. Our User class was given a time_zone attribute, which is just a valid ActiveSupport::TimeZone string, and we created an around_filter as follows:
class ApplicationController < ActionController::Base
around_filter :activate_user_time_zone
def activate_user_time_zone
return yield unless current_user # nothing to do if no user logged in
return yield unless current_user.time_zone && ActiveSupport::TimeZone[current_user.time_zone] # nothing to do if no user zone or zone is incorrect
Time.use_zone(ActiveSupport::TimeZone[current_user.time_zone]) do
yield
end
end
end
Basically, this will execute the code in the controller action as if it was in the current user's time zone.
I have same requirement. Please check this and this link also
Alternatively
"2012-04-19 08:42:00 +0200".to_time.strftime("%c").to_datetime

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

Resources