YII2 get user timezone dynamically - timezone

I am using :
'timeZone' => 'Asia/Calcutta',
to set user timezone. But i need the timezone should be dynamic according to user location.
How can i do that ?

Related

Value of timezone gets added in the selected time (Ruby on Rails)

I'm using current user time to show time. But the timezone of user gets added in the time selected. Let's say, if user's timezone is +05:30 Asia/Kolkata ,then the value 05:30 gets added in the selected time. This is the code I'm using.
def customer_timezone
user_profile_timezone = current_user.user_profile.timezone_name
if user_profile_timezone.present?
time_zone = ActiveSupport::TimeZone[user_profile_timezone]
#user_timezone = "#{time_zone.formatted_offset} - #{time_zone.name}" if time_zone.present?
end
return #user_timezone
end
Is there any way possible to resolve this issue?

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

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.

Convert time zone of datetime returned by f.datetime_select

When I use f.datetime_select in a rails form, it returns a datetime to my controller in UTC, e.g.
2014-06-18T11:00:00+00:00
My local time zone is set to Melbourne (+10) as specified in my application.rb file:
config.time_zone = 'Melbourne'
So when I retrieve datatimes from my database they are automatically converted to Melbourne (+10) timezone, e.g.
2014-06-17 19:00:00 +1000
I want to compare the datetime returned by f.datetime_select with a field in my database. How can I do this?
i.e. how can i change the time zone of the datetime returned by f.datetime select to 'Melbourne' (+10) without changing the actual time? i.e. convert:
2014-06-18T11:00:00+00:00
to
2014-06-18T11:00:00+10:00
All dates stored in database are in UTC time. So when your app get new date from 'params' you basically have 2 options: to save it to the ActiveRecord model, and during that AR will perform all heavylifting of deciding of what timezone was meant.
If you don't want to save data to the model, you have to deal with it yourself. Date select control return just 3 specially formatted strings in params hash.
Let's say I named field in my form 'birthdate'. Controller will get something like:
"<your_model_name>" => {... , "birthdate(3i)" => "<day>", "birthdate(2i)" => "<month>", "birthdate(1i)" => "<year>", ...}
So you could deal with that info something like:
Time.zone.parse("#{ params[:model]['birthdate(3i)'] }-#{ params[:model]['birthdate(2i)'] }-#{ params[:model]['birthdate(1i)'] }")
And yeah I know that it looks ugly, and after some research I surprised that there is no any 'out of the box' solution )

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.

Resources