time conversion based on country_name/country_code - ruby-on-rails

I've been trying to get my head around this problem but even after doing lot of research I wasn't able to get a proper solution.
I'm trying to build a flight booking system. I've have an api which returns me the arrival and departure time of flights in UTC time. Now I want to show the departure and arrival time of the flight in the local time of that country. Is there any faster way to do this. So far I have got this thing working which seems quite heavy. I'm writing some sample code below.
<% flights.each do |flight| %>
<% time_zone_name = TZInfo::Country.get(flight.departure_country_code).zone_names.first %>
<% time_zone = TZInfo::Timezone.get('time_zone_name') %>
departure_time : <%= time_zone.utc_to_local(flight.departure_time) %>
<% end %>

This might help. You can get the user's time zone and Time#localtime will give you the time in the current time zone of the machine running the code
> time = Time.now.utc
=> 2013-11-11 14:42:49 UTC
> time.localtime
=> 2013-11-11 20:12:49 +0530

Related

How can I display local time for data in Rails PostgreSQL correctly

And by correctly I mean if the event is Jan. 24 at 9 am Pacific time, I want to see Jan. 24 at 9 am, not Jan. 24 at 5 pm. I'm recording events with time and date in Rails in a PostgreSQL database. Timestamp with time zone (timestampz). All is well and good as long as I keep the database and app on my computer in "US/Pacific", but when I try to use it from the Heroku server which appears to be in UTC, the times are shifted by 8 hours. I now finally get that timestampz only means that this is the datetime in UTC, but the zone isn't stored. Do I need to store the time zone of the even in another field and do the math to see the time and date correctly?
Adding config.time_zone = "America/Los_Angeles" or to "UTC" to config/application.rb gives the same results as before, i.e., this setting made no difference. And this wouldn't be the final solution because the data won't always be in US/Pacific.
Viewing in a table:
<%= stat.statdate.localtime.strftime("%a %d %b %Y") %></td> <td class="text-start"> <%= stat.statdate.localtime.strftime("%_l:%M %P") %>
which results in Thu 27 Jan 2022 9:05 am on localhost, but Thu 27 Jan 2022 5:05 pm on Heroku
And charting the value over time, both over say a year and daily. For plotting variation over a recent day. Quite tortured trying to get both table and charting to work
<% timeZone = last_record.localtime.formatted_offset(false).slice!(0, 3).to_i %>
<% begin_1_day_ago = (last_record-timeZone.hour-1.day).beginning_of_day %>
<% begin_2_day_ago = (last_record-timeZone.hour-2.day).beginning_of_day %>
<% span21 = begin_2_day_ago..begin_1_day_ago %>
<%= line_chart BP.where(statdate: span21).pluck(:statdate, :value) %>
Where line_chart is using gem "chartkick" which uses High Charts. But this doesn't work when I move the app and database to Heroku
I ended up doing it "manually." Getting the time zone extracted from the data
<% last_record = BP.order('statdate asc').last.statdate %><br> # last data point.
<% last_record_zone = BP.order('statdate asc').last.statzone %> # time zone grabbed from statdate in timestampz (so zone was added)
<% last_record_zone_in_seconds = last_record_zone*3600 %>
# Getting midnight for the day of the last record in UTC but shifted by the local time of the last record
<% last_record_midnight = last_record + last_record_zone_in_seconds - (last_record.min*60) - (last_record.sec) %>
# Setting variables used to title the graphs and set beginning and end of daily graphs (set for one week, not shown)
# The first day is a special case and not shown here
#
# For graph title
<% last_record_1_day_ago = last_record-1.day %>
<% last_record_2_day_ago = last_record-2.day %>
# For daily graph data
<% span10 = last_record_midnight-1.day..last_record_midnight-0.day %>
Haven't checked for other than localhost and current server.

Rails - Strange time_select buggy behavior

My rails repo example is here:
https://github.com/johndel/strange_timeselect
The replication of the strange behavior can be shown here:
https://captain24.herokuapp.com/availabilities/new
I have the following problem with time_select:
In a new rails app with postgresql, I have a model (availabilities) with a started_at column which is of type time in postgres. When I create a new record, it saves the started_at value, one hour before than the selected. On the update it works correctly.
I noticed the following:
I can replicate it only on heroku (locally I cannot replicate it). It works only on create and only after I set default_timezone on application.rb as you can see on this commit.
The application has also another model named tasks with a column named started_at and type datetime. This one works always correct but it saves the time on different timezone than the time field of availabilities.
I can fix it if I add ignore_date: true on the time_select field, but I am wondering why is this happening? Is it normal and I am missing something? Or is it some very strange ruby or rails bug? Or is it an issue / misconfiguration with postgresql on heroku?
Update:
Because #max asked me to explain the code further, here it goes:
Regarding the code, right now it is just two scaffold resources with the commands rails g scaffold tasks started_at:datetime and rails g scaffold availabilities started_at:time. So one is tasks and the other is availabilities with only one column each model. I have also added in the config/application.rb these lines of code:
config.time_zone = 'Athens'
config.active_record.default_timezone = :local
The code of availabilities form is this (plain scaffold):
<%= form_with(model: task, local: true) do |form| %>
<% if task.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(task.errors.count, "error") %> prohibited this task from being saved:</h2>
<ul>
<% task.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= form.label :started_at %>
<%= form.time_select :started_at %>
</div>
<div class="actions">
<%= form.submit %>
</div>
<% end %>
The code of migration for availability is this:
class CreateAvailabilities < ActiveRecord::Migration[6.0]
def change
create_table :availabilities do |t|
t.time :started_at
t.timestamps
end
end
end
If you add the above code and deploy on heroku, you will be able to reproduce the problem.
So regarding the problem with the above code is that when I am creating an availability record, it keeps one hour less than what it is selected. This happens only on create. It isn't the case with task record so it probably has to do with time postgres type, with the active_record timezone. this happens only on heroku as you can check on the link above.
This is occurring because you need to take your server's timezone into account.
You need to configure Heroku as well.The command to change Heroku Time settings is something like below
heroku config:add TZ="Europe/Paris"
And it not a good idea to save date in any other format than UTC
Details
EDIT
To answer the question why the update method is working differently, you have to inspect the form rendered by rails.Rails creates additional three hidden fields
availability[started_at(1i)],
availability[started_at(2i)] and
availability[started_at(3i)]
On the create form, those fields have default values of 2020,5,19
But on the edit form, they have the value of 2000,1,1
My educated guess is they are messing up with the daylight saving time, and thus creating the anomaly.
There are two different problems.
1) Different scenario in local vs Heroku.
- Your schema.rb file has "started_at" listed as datetime, while in the migration, it is "time"
- Heroku runs the migration, but bin/rails db:setup loads from schema.rb, hence you don't see the problem locally as it's saved as datetime.
2)
The problem with the time column is that with a lack of date, Ruby has to guess. When you are creating a new record, the form is prefilled with the current date, which at the time of the post, is in May (a summer month), and will be parsed in EEST.
Incoming attributes "2020", "05", "20", "11", "00" will be parsed as Wed, 20 May 2020 11:00:00 EEST +03:00
This is then saved in the database as time offset by -3 hours 8:00:00
When it is next loaded from the database, there's no date attached, so Rails parses it with the date assumption of "2000", "01", "01" , which results in Sat, 01 Jan 2000 10:00:00 EET +02:00. Note that only two hours is added in this case, since it's EET.
When you edit, the form is prefilled with the availability's current started_at Sat, 01 Jan 2000 10:00:00 EET +02:00. When you update, since date from the form is now January, it is parsed using EET time zone and saves now. Since the date used to display and edit is the same, it now looks 'correct'
How to fix? It depends on the application, and I'm no expert, but some thoughts:-
1) Some would say that time inherently has no meaning without date. Perhaps you could consider using datetime column instead. 12PM availability in Greece may mean differently to people in different time zones.
2) You could use something like the tod gem to handle time.
3) You could store it as an integer offset from say, 12AM.

Having trouble getting Rails form builder to respect a given time zone for a time_select input

I cannot get my rails input helpers to respect a given time zone. I've looked at all the existing issues on this subject and code examples. It seems like this should be simple, but I'm getting nowhere. I'm hoping someone here can quickly see the error of my ways:
I have a Pro model in Rails with a time field called "work_start_time".
# schema.rb
create_table "pros", force: true do |t|
t.time "work_start_time"
I have a very basic form for my Pro that includes some additional lines to help me debug:
Work_start_time in UTC:<br>
<%= #pro.work_start_time %><br>
Work_start_time in PST:<br>
<% Time.use_zone("Pacific Time (US & Canada)") do %>
<%= #pro.work_start_time.in_time_zone %><br>
<% end %>
Form for work_start_time using PST:<br>
<%= form_for #pro do |f| %>
<% Time.use_zone("Pacific Time (US & Canada)") do %>
<%= f.time_select :work_start_time %><br>
<% end %>
<% end %>
Now I would expect the second and third times to be the same, since they're both supposed to be displayed in PST. But the time_select fails to respect it and shows the time in UTC instead:
What am I doing wrong?? I've tried using datetime_select. I've tried providing the work_start_time as a value: argument to the select method. No dice.
This is a simplified example - normally the time zone is actually a stored current_user.time_zone and we set Time.use_zone as an around_filter in the application controller. But those are disabled for this simplified experiment.
Ruby 2.2 (same issue with 2.0)
Rails 4.1.8 (same issue with 4.2.6 and Ruby 2.2)
I haven't tried using Time.use_zone do, but you might try adding to_s to the end of your #pro.work_start_time.in_time_zone call. Alternatively you could also use Moment.js and Moment time zone and render in javascript.
I found it easier to have the front end use moment to return utc times in forms, and to convert utc times to a default time zone.

Organizing by rails datetime in view

I am creating a scheduling app in Rails and I am getting stuck while trying to organize the rooms by date. When I say a room, I essentially just mean a block in the schedule. A typical event might have a lunch (one room), then a networking section (another room). Here is how I am getting the rooms:
#rooms = Room.where(event_id: #current_event.id).order(:start_time)
So that returns the rooms that belong to an event, in order of the start time. In my view I loop through and display the rooms for the event, but I want to group them in the view by the date in case there is an event that is on multiple days.
Also :start_time is a datetime type in Ruby. In human speak, what I would do is look at the date portion and if the room date is not the same as the current group, I would print the new date and continue to group the rooms that fall on that day. Here is a trivial example in case I am not being clear:
Event: Staff Retreat
July 14th, 2015
-----------------------
12:30 PM
Team building Lunch Begins
------------------------
6:30 PM
Team building Dinner Begins
------------------------
July 15th, 2015
------------------------
9:30 AM
Team building Breakfast Begins
So having the grouping of rooms in the #rooms variable that is a datetime, what would be the best way to display a table like the above? I would think that in my loop I should check if the date was the same as the previous room, and if not print the date
- #rooms.each do |room|
room.start_time.strftime("%B %d, %Y")
room.start_time.strftime("%I:%M%p")
room.name
I am having trouble with the logistics because with the Model-View-Controller concept, I feel that sorting in the view may have the view do something it shouldn't have to. I am also struggling with how to do that in the view. I would think setting a variable in the controller that would hold the temporary date as I loop through would work, but it seems like that would start to get pretty messy though. What is the best way to group the various dates from the #rooms variable?
Can you try this. this will return you a hash where date is the key and value will contain all the #rooms related to that time.
#rooms = Room.where(event_id: #current_event.id)
#rooms = Hash[#rooms.sort_by{|o| o.start_time.to_date}.group_by {|room| room.start_time.to_date}.map{|l,m| [l, m.sort_by{|k| k.start_time}]}]
now you can traverse the rooms like this in the views. im putting code in erb format.
<% #rooms.each do |k, v| %>
<%= k %>
<% v.each do |room| %>
<%= room.created_at.start_time('%I:%M %p')%>
<%= #room.name or title what so ever. %>
<% end %>
<% end %>

Validate date/time in another country, prevent delay in timezones

How do I validate if the date of an object is going on today, preventing delay timezones.
My Ruby's app hosted in the USA and my time zone is in another country.
Ex:
List of games to be played today.
<% #matches.each do |m| %>
<% if(m.date.today?) %>
<p>This match will be played today</p>
<% end %>
<% end %>
The 'if' sentence brings back the results of the matches to be played today, including upcoming games seven hours, server delay product.
Convert the date to the user's timezone.
m.date.in_time_zone(<user's time zone>).today?

Resources