Rails - Strange time_select buggy behavior - ruby-on-rails

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.

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.

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.

Creating a date range picker using two select boxes in Rails

I'd like to setup a model that would allow a date range depicting a timeframe of activity-inactivity. So for example you have an employee model and would be able to set his/her duration of employment from a start date to an end date. Essentially there would be two select boxes that would allow you to do this. If the employee is still employed there would be an option in the second box labeled "current".
I've looked around for an existing answer to this problem and it seems most direct you towards using either date_select or select_year to create a range within one select box. I'm looking to do something of the sort using two select boxes (start / end || current) and saving the two values to the database. Now for the second select box I wanted to have it default to the current year and be called "Current", indicating there is no end date yet.
Looking through the Rails API there's an option for a prompt but I'm not exactly sure how to have that prompt represent a physical value and reside at the top of the list. For something as simple as this I was leaning towards not using an extensive jQuery datepicker plugin to reduce the unnecessary overhead. I'm open to using SimpleForm but haven't found a way to do this through that gem.
What I have now:
<% form_for #product do |f| %>
<%= f.label :employed %>
<%= select_year(Date.today, start_year: 2000, end_year: 2012) %>
<%= select_year(Date.today, :start_year => Date.today.year, :end_year => 8.years.from_now.year) %>
<% end %>
I'm wondering if creating two attributes to the employee model specifying these date (stardate + enddate) would work or could you possibly do this in one fell swoop (I'm assuming the latter would be cleaner)?
I would consider creating a separate table to store these values, call it EmploymentPeriod. An Employee then has_many EmploymentPeriods, which could be useful to keep track of... (maybe an employee is a student, and works one summer, and then returns the following, for example).
EmploymentPeriod could keep track of things like start_date, end_date, and even something like salary, for that particular period (maybe the student gets a raise the following summer...).
To recap:
Employee has_many :employment_periods
EmploymentPeriod belongs_to :employee # e.g. it has an employee_id foreign key
This is how I would tackle it! Good luck.

Rails Form display datetime at local timezone

I'm new to rails (just finished the Rails Tutorial) and I'm building my first solo app. The user can create 'events' at specific times. Currently the user form is in UTC. How do I set it to their local time?
I don't just want to change it after they submit, because it would be confusing for the user. When you open a new form the default time is the current time (currently UTC). I want that to be the time of their local timezone.
<%= f.label :Date_Time %><br />
<%= f.datetime_select :date %>
thanks,
Loads of ways to do this!
I'd recommend you look at the question Stepan recommended first
Server Side
If you're wanting to keep your times consistent, you may wish to use the Rails in-built timezone option:
#config/application.rb
config.time_zone = 'Eastern Time (US & Canada)'
You can see more about ActiveSupport::Timezone here
Client-Side
Client-side, you'd have to think about what you want to achieve. If you have set timezones, you may benefit from creating a TimeZone repository (either ENV variables or datatable), and allow user to select from them, like this:
#app/views/form.html.erb
<%= form_tag %>
<%= select_tag :date, options_from_collection_for_select(#timezones, "id", "name") %>
<% end %>
Alternatively, you could pass the timezone through javascript: Getting the client's timezone in JavaScript
UX
The bottom line is you need to think about what you're trying to achieve. If your users need localized times, you should use a country-select system, where they will give you their country, and you can dedicate a timezone
Speaking from experience, it's much better to store all times equally (using UTC), and then process them using user's specifications

Rails Date Select Parameters

I'm using a date select in a rails 3 form.
<%=f.date_select :date %>
I would like to restrict the dates so that you can only pick dates that fall on a Sunday. Is there any way of going about doing this?
I'm also trying to stop dates which have already passed from appearing.
Thanks for any help in advance!
Rails date_select field generates three dropdown to select the parts of the date. There is no chanche, that you modify for example the month, and the day will still be sunday.
You must write some js magic to enforce such a role, or find an already existing datepicker and limit it. Or alternatively, you let the user to select a week, and calculate the exact date of sunday from that.
Ok having studied this out a bit further I don't think this is possible due to the format of the date_select field. The closest I can get is
<%=f.date_select :date, start_year: Time.now.year %>
so that at least you can't select dates from previous years. I've implemented the restriction on days and months that have past by setting up the view to automatically delete records that aren't relevant:
<% if(service.date < Date.today) %>
<% service.destroy %>
<% end %>
Not perfect but does the job in my case.

Resources