Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ class ApplicationController < ActionController::Base
before_action :authenticate_user! # ensures only logged-in users can access pages
before_action :set_current_user # for AhoyTrackable in models
after_action :flush_lifecycle_events
around_action :set_time_zone_from_user, if: :current_user

# TODO add this callback to verify
# that `authorize!` has been called in all controllers
Expand Down Expand Up @@ -33,6 +34,16 @@ def after_sign_out_path_for(resource_or_scope)
end
end


def set_time_zone_from_user
zone = ActiveSupport::TimeZone[current_user&.time_zone]
if zone
Time.use_zone(zone) { yield }
else
yield
end
end

def authenticate_user!
super
ahoy.authenticate(current_user) if current_user
Expand Down
1 change: 1 addition & 0 deletions app/controllers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ def user_params
:notes, :primary_address, :avatar, :subscribecode,
:agency_id, :facilitator_id, :created_by_id, :updated_by_id,
:confirmed, :inactive, :super_user, :legacy, :legacy_id,
:time_zone,
project_users_attributes: [ :id, :project_id, :position, :title, :inactive, :_destroy ]
)
end
Expand Down
4 changes: 2 additions & 2 deletions app/decorators/event_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ def calendar_links
end

def times(display_day: false, display_date: false)
s = start_date
e = end_date || start_date
s = start_date.in_time_zone(Time.zone)
e = (end_date || start_date).in_time_zone(Time.zone)

# helpers
day = ->(d) { d.strftime("%a") }
Expand Down
7 changes: 7 additions & 0 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ class User < ApplicationRecord

# Validations
validates :email, presence: true, uniqueness: { case_sensitive: false }
validate :time_zone_must_be_valid, if: :time_zone_changed?
validates_associated :project_users

# Search Cop
Expand Down Expand Up @@ -174,6 +175,12 @@ def gallery_assets # method needed for idea_submission_fyi mailer

private

def time_zone_must_be_valid
return if time_zone.blank?

errors.add(:time_zone, "is not a valid time zone") unless ActiveSupport::TimeZone[time_zone]
end

def set_default_values
self.inactive = false if inactive.nil?
self.confirmed = true if confirmed.nil?
Expand Down
11 changes: 11 additions & 0 deletions app/views/users/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,17 @@
</div>
<% end %>

<!-- Preferences: timezone for event times etc. -->
<div class="space-y-6 p-3">
<%= f.input :time_zone,
as: :select,
collection: ActiveSupport::TimeZone.all.map { |z| [ z.to_s, z.name ] },
include_blank: "Use site default (Pacific Time)",
hint: "Event times and other dates will be shown in this timezone.",
input_html: { class: "w-full" },
wrapper_html: { class: "w-full max-w-md" } %>
</div>

<!-- Actions -->
<!-- Action buttons -->
<div class="action-buttons mt-8 flex justify-center gap-3">
Expand Down
7 changes: 7 additions & 0 deletions db/migrate/20260123120000_add_time_zone_to_users.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# frozen_string_literal: true

class AddTimeZoneToUsers < ActiveRecord::Migration[8.1]
def change
add_column :users, :time_zone, :string, default: "Pacific Time (US & Canada)"
end
end
1 change: 1 addition & 0 deletions db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -844,6 +844,7 @@
t.string "state2"
t.string "subscribecode"
t.boolean "super_user", default: false
t.string "time_zone", default: "Pacific Time (US & Canada)"
t.string "unconfirmed_email"
t.string "unlock_token"
t.datetime "updated_at", precision: nil
Expand Down
71 changes: 71 additions & 0 deletions spec/requests/events_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,38 @@

expect(response).to be_successful
end

context "when user time_zone is set" do
# 19:00 UTC = 12:00 noon PT = 15:00 (3 pm) ET (June 15, 2025 with DST)
let(:utc_start) { Time.utc(2025, 6, 15, 19, 0, 0) }
let(:utc_end) { Time.utc(2025, 6, 15, 20, 0, 0) }
let!(:event_with_fixed_times) do
create(:event,
start_date: utc_start,
end_date: utc_end,
title: "Timezone test event")
end

it "displays start time in Pacific (PT) for user with time_zone PT" do
user_pt = create(:user)
sign_in user_pt
get event_url(event_with_fixed_times)

expect(response).to be_successful
# 19:00 UTC = 12:00 noon PT
expect(response.body).to include("Sun, Jun 15 @ 12 - 1 pm")
end

it "displays start time in Eastern for user with time_zone America/New_York" do
user_et = create(:user, time_zone: "Eastern Time (US & Canada)")
sign_in user_et
get event_url(event_with_fixed_times)

expect(response).to be_successful
# 19:00 UTC = 3:00 pm ET (3 hours later than 12 pm PT)
expect(response.body).to include("Sun, Jun 15 @ 3 - 4 pm")
end
end
end

describe "GET /new" do
Expand Down Expand Up @@ -113,6 +145,45 @@

expect(response.body).to include("Event was successfully created")
end

it "stores start_date/end_date in UTC when created by user in Pacific time zone" do
user_pt = create(:user) # default Pacific Time (US & Canada)
sign_in user_pt
# datetime-local sends "YYYY-MM-DDTHH:MM" — interpreted in request's Time.zone (PT)
# 12:00–13:00 PT (PDT) on 2025-06-15 = 19:00–20:00 UTC
post events_url, params: { event: {
title: "PT event",
description: "desc",
start_date: "2025-06-15T12:00",
end_date: "2025-06-15T13:00",
registration_close_date: 1.day.ago,
publicly_visible: true
} }

expect(response).to redirect_to(events_url)
created = Event.order(created_at: :desc).first
expect(created.start_date.utc).to eq(Time.utc(2025, 6, 15, 19, 0, 0))
expect(created.end_date.utc).to eq(Time.utc(2025, 6, 15, 20, 0, 0))
end

it "stores start_date/end_date in UTC when created by user in Eastern time zone" do
user_et = create(:user, time_zone: "America/New_York")
sign_in user_et
# 15:00–16:00 ET (EDT) on 2025-06-15 = 19:00–20:00 UTC
post events_url, params: { event: {
title: "ET event",
description: "desc",
start_date: "2025-06-15T15:00",
end_date: "2025-06-15T16:00",
registration_close_date: 1.day.ago,
publicly_visible: true
} }

expect(response).to redirect_to(events_url)
created = Event.order(created_at: :desc).first
expect(created.start_date.utc).to eq(Time.utc(2025, 6, 15, 19, 0, 0))
expect(created.end_date.utc).to eq(Time.utc(2025, 6, 15, 20, 0, 0))
end
end

context "with invalid parameters" do
Expand Down
9 changes: 8 additions & 1 deletion spec/requests/users_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,14 @@
user = User.create! valid_attributes
patch user_url(user), params: { user: new_attributes }
user.reload
skip("Add assertions for updated state")
expect(user.first_name).to eq("Janet")
end

it "permits and updates time_zone" do
user = User.create! valid_attributes
patch user_url(user), params: { user: { time_zone: "EST" } }
user.reload
expect(user.time_zone).to eq("EST")
end

it "redirects to the users index" do
Expand Down
Loading