Based on a Rails 8 setup with Google Calendar and Geocoder gems, here are essential best practices for handling time in a scheduling application.
- Database: Always keep
config.active_record.default_timezone = :utc(this is the default). Never store local times in the database. - Why: It makes daylight saving time (DST) calculations and cross-timezone scheduling manageable. If you store "9:00 AM" without a zone, it’s ambiguous.
In config/application.rb, set a sensible default for the application logic, even if the DB is UTC.
# config/application.rb
config.time_zone = "Brasilia" # Or your primary target audience's zoneThis ensures that Time.zone.now defaults to this zone if a user-specific zone isn't set.
Since this is a scheduling app, every user (and potentially every appointment) needs a specific timezone.
- Add a column: Add
time_zoneto youruserstable. - Validation:
validates :time_zone, inclusion: { in: ActiveSupport::TimeZone.all.map(&:name) }
- Usage: Use an
around_actionin yourApplicationControllerto set the context for each request.around_action :set_time_zone, if: :current_user def set_time_zone(&block) Time.use_zone(current_user.time_zone, &block) end
- DO use
Time.currentorTime.zone.now. These respect the configuredTime.zone. - DO use
1.day.from_noworDate.current. - DON'T use
Time.now. It uses the server's system time and ignores your Rails timezone configuration, often leading to subtle bugs. - DON'T use
Date.today. It also relies on system time.
Using google-apis-calendar_v3 requires strict time formats.
- Format: Always convert times to ISO 8601 / RFC 3339.
start_time: appointment.start_at.iso8601
- Zone: Pass the timezone explicitly to Google's API event payload to ensure the event shows up correctly in the user's GCal.
Use geocoder to auto-detect a user's timezone upon sign-up based on their IP address:
# In a callback or service
results = Geocoder.search(user_ip)
timezone = results.first&.data['timezone'] # generic example, depends on providerTip: Also consider using JavaScript (Intl.DateTimeFormat().resolvedOptions().timeZone) on the frontend to detect the browser's timezone and populate a hidden field during registration.
Use Rails' built-in time helpers to "freeze" time during tests. This prevents flaky tests that fail only at night or across month boundaries.
# test/test_helper.rb
include ActiveSupport::Testing::TimeHelpers
# In your test
travel_to Time.zone.local(2024, 11, 24, 10, 0, 0) do
# Perform assertions assuming it's always this specific time
end