Фильтрация даты и времени не работает при использовании часового пояса

В моей настройке я хочу отфильтровать Schedule по Activity, City, Date, Starting Time и End Time с помощью меню выбора.

Я заставил это работать с Ransack:

# db
create_table "schedules", force: :cascade do |t|
  t.integer  "city_id"
  t.integer  "activity_id"
  t.datetime "starts_at"
  t.datetime "ends_at"
  ...
end

# Schedule model
scope :six_hours_from_now, -> { where('starts_at >= ?', Time.zone.now + 6.hours) }

ransacker :start_date, type: :date do
  Arel.sql('starts_at::date')   # filter only the date from starts_at attribute
end

ransacker :start_time, type: :time do
  Arel.sql('starts_at::time')   # filter only the time from starts_at attribute
end

ransacker :end_time, type: :time do
  Arel.sql('ends_at::time')     # filter only the time from ends_at attribute
end

# Schedule controller
def index
  @q          = Schedule.ransack(params[:q])
  @q.sorts    = ['starts_at asc', 'ends_at asc'] if @q.sorts.empty?
  @schedules  = @q.result.six_hours_from_now
end

# view
<%= search_form_for @q do |f| %>
  <%= f.collection_select :activity_id_eq, @activities, :id, :name, { include_blank: 'All' } %>
  <%= f.collection_select :city_id_eq, @cities, :id, :name, { include_blank: 'All' } %>
  <%= f.select :start_date_eq, options_for_select([
        ['Today', Time.zone.today],
        ['Tomorrow', Time.zone.tomorrow],
        [(Time.zone.today + 2).strftime("%d %b"), Time.zone.today + 2],
        [(Time.zone.today + 3).strftime("%d %b"), Time.zone.today + 3],
        [(Time.zone.today + 4).strftime("%d %b"), Time.zone.today + 4],
        [(Time.zone.today + 5).strftime("%d %b"), Time.zone.today + 5],
        [(Time.zone.today + 6).strftime("%d %b"), Time.zone.today + 6]
      ], @q.start_date_eq), { include_blank: true } %>
  <%= f.time_select :start_time_gteq, ampm: true, default: { hour: '07' } %>
  <%= f.time_select :end_time_lteq, ampm: true, default: { hour: '23', minute: '45' } %>

  <%= f.submit "Filter" %>
<% end %>

К сожалению, фильтры больше не работают, когда я добавляю код для обработки часового пояса:

# application.rb
default settings, no changes

# application_controller.rb
before_filter :set_time_zone

def set_time_zone
  if current_user
    Time.zone = current_user.time_zone
  elsif current_admin
    Time.zone = current_admin.time_zone
  end
end

Если я фильтрую только по городу, я получаю такой запрос:

SELECT "schedules".* 
FROM "schedules" 
WHERE (
    "schedules"."city_id" = 6 AND 
    starts_at::time >= '2015-09-30 23:00:00.000000' AND 
    ends_at::time <= '2015-10-01 15:45:00.000000'
) AND (
    starts_at >= '2015-10-01 20:52:39.866880'
) ORDER BY "schedules"."starts_at" ASC, "schedules"."ends_at" ASC`

По сути, если часовой пояс пользователя — GMT+8, тогда он возьмет значение из меню выбора времени starts_at и ends_at, используя сегодняшнюю дату (1 октября 2015 г.), и автоматически вычтет из него 8 часов перед выполнением запроса. Как запретить Ransack вычитать смещенные часы?


person hsym    schedule 01.10.2015    source источник


Ответы (1)


По-видимому, запрос sql, автоматически вычитающий 8 часов в соответствии с моим часовым поясом, является ожидаемым поведением и правильным решением.

Вместо того, чтобы фильтровать данные без какой-либо даты, я удалил пустую опцию из выбора даты и убедился, что показанные расписания по умолчанию отфильтрованы до завтрашней даты, а не показывают все расписания. Таким образом, пользователь должен сначала выбрать дату, прежде чем фильтровать расписания.

Мне пришлось изменить входящие параметры из выбора времени, чтобы он использовал параметры из выбора даты, прежде чем передать все параметры Ransack.

Расписание модели:

# Delete the custom Ransack methods as we don't need them anymore:

ransacker :start_date, type: :date ...
ransacker :start_time, type: :time ...
ransacker :end_time, type: :time ...

# ...and add these scopes:

scope :sort_by_datetime_asc, -> { order(:starts_at, :ends_at) }

def self.only_tomorrow
  where('starts_at BETWEEN ? AND ?', Time.zone.tomorrow.beginning_of_day, Time.zone.tomorrow.end_of_day)
end

Контроллер расписания:

params[:q] = {} unless params[:q]

# Copies the date params from date select to replace the date params from the time select menu.
if params["start_date"].present?
  date  = Time.zone.parse(params["start_date"])

  year  = date.strftime("%Y")
  month = date.strftime("%m")
  day   = date.strftime("%e")

  params[:q]["starts_at_gteq(1i)"] = year
  params[:q]["starts_at_gteq(2i)"] = month
  params[:q]["starts_at_gteq(3i)"] = day

  params[:q]["ends_at_lteq(1i)"]   = year
  params[:q]["ends_at_lteq(2i)"]   = month
  params[:q]["ends_at_lteq(3i)"]   = day
end

@q = Schedule.ransack(params[:q])

if params[:q].present?
  @schedules = @q.result
else
  @schedules = Schedule.only_tomorrow
end

@schedules = @schedules.six_hours_from_now.sort_by_datetime_asc

Расписание # просмотр индекса:

<%= select_tag "start_date", options_for_select([...], selected: default_date_select) %>
<%= f.time_select :starts_at_gteq ...
<%= f.time_select :ends_at_lteq ...

Помощник по расписанию:

# Show selected date for Schedule's date filter.
def default_date_select
  if params["start_date"].present?
    params["start_date"]
  else
    ['Tomorrow', Time.zone.tomorrow]
  end
end
person hsym    schedule 06.10.2015