JohnDel
JohnDel

Reputation: 2112

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.

Upvotes: 2

Views: 343

Answers (2)

NoNonsense
NoNonsense

Reputation: 941

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.

Upvotes: 4

MD Tawab Alam Khan
MD Tawab Alam Khan

Reputation: 188

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.

Upvotes: 5

Related Questions