Setting and Syncing User Time Zones in a Rails Application
Introduction
Handling time zones can be a tricky business in web development, particularly in a global application where users can hail from various parts of the world. Failing to properly account for time zones can lead to a range of issues—from scheduling mishaps to confused users.
In Ruby on Rails, there are straightforward methods to address these challenges. This article provides a comprehensive guide on how to set and sync user time zones in a Rails application. Here's what we'll cover:
Migrations and Validations: How to set the foundational data structure for storing user time zones.
Building the UI Component: Creating a reusable partial for a time zone dropdown.
Enhancing User Experience: Introducing a button to automatically set the time zone to the user's system setting.
Automatic Setting Upon User Registration: Making the user's life easier by setting the time zone for them.
For those of you who prefer video content, make sure to check out the supplementary YouTube video that walks you through the entire process.
Whether you're looking to add this feature to an existing project or want to include it in your next application, this guide aims to be your go-to resource. Let's dive in!
Setting the Foundation
Before diving into the intricacies of enhancing user experience, let's set up the foundational elements. This involves two main steps:
Generating and Running a Migration: Add a
time_zonecolumn to theuserstable.Implementing Validations: Ensure that the time zone data is both present and stored in the right format.
Generate and Run Migration
First things first, we need to add a time_zone field to the users table. This can be effortlessly accomplished by running a simple migration with the Rails CLI.
bin/rails generate migration AddTimeZoneToUsers time_zone:string
bin/rails db:migrateBy now, you should have a time_zone column added to your users table.
Validations
Once the database is all set up, we need to validate the time_zone field to ensure data integrity. After all, a time zone field without validations is like a ship without a compass—prone to go off course.
Navigate to your User model file and add the following validations for the time_zone field:
class User < ApplicationRecord
validates :time_zone,
presence: true,
inclusion: {
in: ActiveSupport::TimeZone.all.map(&:tzinfo).map(&:identifier)
}
endPresence: This ensures that a time zone value must be present.
Inclusion: While the presence check makes sure something is there, the format validation ensures that the time zone is stored in a usable and consistent format.
Note: You can use Rails’ built-in validation methods for both checks.
By completing these two steps, you've laid a solid foundation for adding the time zone feature to your Rails application. With the groundwork done, we can now focus on enhancing the user experience and making our application more robust.
Building the Time Zone Component
After setting up the foundational aspects like migrations and validations, the next logical step is building the time zone component itself. This component will provide users with an intuitive interface to set their preferred time zone.
Create a Partial for the Time Zone Component
Why Use a Partial?
Partials are a way to place reusable code in a single location, making your application DRY (Don't Repeat Yourself).
Create a new partial file:
Create a new file under yourapp/views/shareddirectory, naming it_time_zone_select.html.erb.Flexible Styling:
To make this component more adaptable to various parts of your application, allow the caller to pass in class names for controlling the surroundingdivstyle.
<div class="<%= class_names %>">
</div>Anatomy of the Time Zone Component
Label:
The first element within your component should be a label that is clearly associated with the following select field.Select Dropdown:
Options: Populate the dropdown with all available time zones. Rails provides a
time_zone_selectform helper that can ease this process.Pretty Print: Display the pretty print name for each option, but make sure that the actual value saved is the time zone identifier.
Validation and Prompts: To guide the user, include a prompt like "Select your time zone" when no value is selected.
<div class="<%= class_names %>"
data-controller="time-zone">
<%= form.label :time_zone %>
<%= form.select :time_zone,
ActiveSupport::TimeZone.all.map { |tz|
[tz.name, tz.tzinfo.identifier]
},
{prompt: "Select time zone"} %>
</div>Incorporate the Time Zone Component into the User Settings Page
With the partial ready, you can easily incorporate it into any form. For this example, let's add it to the User Settings Page.
Locate the Form: Open the edit form for user settings, usually found in
app/views/users/edit.html.erb.Render the Partial: Use Rails'
rendermethod to include the time zone partial.Styling: If you need to add specific classes for styling within this context, pass them as local variables when rendering the partial.
<%= render "shared/time_zone_select", form: form %>By following these steps, you should have a fully functional and reusable time zone component. Next, we will focus on enhancing the user experience by reducing the number of options and automatically setting sensible defaults.
Enhancing User Experience
Managing time zones in an application is not just a technical task, but also one deeply tied to user experience. While it's important to have accurate data, you also don't want users to get frustrated with overly complex or tedious processes.
The Problem: Long Lists Are Not User-Friendly
As we already know, the dropdown list for selecting a time zone can be quite long. The longer the list, the harder it is for the user to quickly find and select their appropriate time zone.
User Fatigue: Long lists can overwhelm users, making them less likely to complete the form.
Error-Prone: The longer the list, the higher the chances of a user accidentally selecting the wrong option.
Convenient Syncing
Wouldn't it be great if we could make this easier for the user? The solution is simple: Let's add a button that allows users to automatically set the dropdown to their system's time zone.
Quick Action: A simple button that does the job in a single click.
Reduced Errors: By setting it to the system's time zone by default, there's a lower chance of user error.
Adding JavaScript Magic
To implement this feature, we'll employ a little bit of JavaScript magic. Given your preference for minimizing the amount of JavaScript code, this will be a simple, straightforward Stimulus controller.
Create a Stimulus Controller: Name it time_zone. This controller will manage the behavior of the syncing button.
bin/rails generate stimiulus time_zoneConnect to Stimulus: Update your time zone partial to include data-controller attributes to link it with the Stimulus controller.
<div class="<%= class_names %>" data-controller="time-zone">
...
</div>Implement Targets and Actions: Create a target for the button and a second one for the dropdown select. Add an action that triggers on button click to set the select value to the system's time zone.
<div class="<%= class_names %>"
data-controller="time-zone">
<div class="flex justify-between items-end">
<!-- Label for Time Zone -->
<%= form.label :time_zone %>
<!-- Button to Set System Time Zone -->
<button type="button"
data-action="time-zone#setSystemTimeZone"
data-time-zone-target="button"
class="link font-medium text-sm hidden">
Set to System Time Zone
</button>
</div>
<!-- Dropdown for Time Zone Selection -->
<%= form.select :time_zone,
ActiveSupport::TimeZone.all.map { |tz| [tz.name, tz.tzinfo.identifier] },
{prompt: "Select time zone"},
{
data: {
time_zone_target: "select",
action: "time-zone#hideButtonIfSystemTimeZoneSet"
}
} %>
</div>Hide Button When Not Needed
It’s good practice to only display UI elements when they’re useful. In this case, if a user has already set their time zone to match the system’s, the sync button becomes redundant.
Add a Target for the Select: This will allow us to detect when the value changes.
Conditionally Hide the Button: If the system time zone is already set, hide the button to declutter the UI.
import { Controller } from "@hotwired/stimulus";
// Connects to data-controller="time-zone"
export default class extends Controller {
static targets = ["select", "button"];
connect() {
if (!this.selectTarget.value) {
this.selectTarget.value = this.#systemTimeZone;
}
this.hideButtonIfSystemTimeZoneSet();
}
get #systemTimeZone() {
return Intl.DateTimeFormat().resolvedOptions().timeZone;
}
applySystemTimeZone() {
this.selectTarget.value = this.#systemTimeZone;
this.hideButtonIfSystemTimeZoneSet();
}
hideButtonIfSystemTimeZoneSet() {
const shouldHide = this.selectTarget.value === this.#systemTimeZone;
this.buttonTarget.classList.toggle("hidden", shouldHide);
}
}Wrapping Up
Implementing this button adds a small, yet powerful layer of user experience optimization. It eliminates the need for manual searching through a dropdown, thus making the entire process more efficient and user-friendly.
By focusing on such details, we're not just building applications; we're crafting experiences. As you continue to optimize your Rails application, always remember: the devil is in the details.
Beyond Explicit Setting
Up until this point, we've focused on giving the user a way to explicitly set their time zone either through a dropdown select field or by syncing it with their system time zone. However, wouldn't it be a more delightful experience if the system set the time zone automatically for them during the registration process? In this section, we'll delve into how to achieve just that.
Automatically Setting Time Zone Upon Registration
To automatically set the user's time zone upon registration, we can modify the Devise registration views and controllers.
1. Customizing Devise Views:
bin/rails generate devise:viewsAdd the time zone partial in the Devise new registration view. Keep this component hidden as we'll be setting this value automatically.
<%= render "shared/time_zone_select", {form: f, class_names: "hidden"} %>2. Prepopulate Time Zone:
When a new user is registering, the time zone is not yet set. So when the Stimulus controller connects, the Stimulus controller will automatically set the hidden time zone select field’s value to the system time zone.
connect() {
if (!this.selectTarget.value) {
this.selectTarget.value = this.#systemTimeZone;
}
this.hideButtonIfSystemTimeZoneSet();
}Overriding Devise Behavior
Since we are relying on Devise for user registration, a custom registration controller is necessary to handle the automatic setting of the time zone.
bin/rails generate devise:controllers users -c registrations1. Update Routes:
Your
routes.rbwill have to be updated to point Devise to use your custom registration controller instead of the default one.
Rails.application.routes.draw do
devise_for :users, controllers: {
registrations: "users/registrations"
}
end2. Controller Overrides:
Create your custom registration controller that inherits from
Devise::RegistrationsController.Override the
configure_sign_up_paramsmethod to permit the time zone parameter.
class Users::RegistrationsController < Devise::RegistrationsController
before_action :configure_sign_up_params, only: [:create]
def configure_sign_up_params
devise_parameter_sanitizer.permit(:sign_up, keys: [:time_zone])
end
endNote: Devise already provides excellent flexibility for overriding default behavior. More details can be found in the Devise documentation.
With this, the time zone automatically when a new user account is created.
Testing the Behavior
Lastly, it's crucial to test this new behavior to make sure everything is working as expected.
Verify that upon new user registration, the time zone is automatically set to the system's time zone.
Head over to the user settings page to confirm that the time zone is displayed correctly.
Testing Tips: Remember, automated tests can be your friend here. Use Minitest to validate that your logic is sound.
Final Thoughts on Automatic Time Zone Setting
By now, you should have a fully automated system for setting the user's time zone, thereby eliminating one less thing the user has to worry about. It enhances the user experience by reducing friction during the registration process and also ensures that time zone data is consistent and accurate right from the start.
This level of automation is a simple yet effective way to elevate the quality of your Rails application. It's all about the little things that make a big difference.
Conclusion
What We've Achieved
Easy Time Zone Setting: We started by adding a 'time_zone' column to the User table and applied validations to ensure its correct format.
User-Friendly Dropdown: Created a partial for a flexible time zone select component, which can be easily plugged into various parts of the application.
One-Click Syncing: Utilized a bit of JavaScript via Stimulus to add a button that syncs the user's system time zone with the dropdown. This eliminated the need for the user to sift through a long list.
Automatic Time Zone Assignment: Took things a step further by automatically setting the time zone for new users during the registration process. This was achieved by overriding Devise's default behavior.
Why It Matters
User Experience: No one likes to scroll through a long list. The sync button and automatic setting significantly improve user satisfaction.
Data Integrity: By setting validations and using system time zones, we ensure that the data is both accurate and in a standardized format.
Code Reusability: The partial makes it easy to include the time zone select field in multiple places without duplicating code.
Efficiency: Automatic time zone setting during user registration streamlines the onboarding process, letting the user focus on other important settings or actions.
Final Thoughts
Do It Yourself: If you've been putting off implementing time zones in your Rails application, hopefully, this guide has shown you that it's both doable and beneficial.
Next Steps: For those looking to dive deeper into time zones in Rails, I recommend reading the official Rails guide on time zones.
Contribute: If you have further insights or optimizations, feel free to share. The community thrives on shared knowledge.
By implementing these practices, not only will you elevate your Rails application's functionality, but you'll also offer a more intuitive and satisfying user experience.
Additional Resources
Rails Documentation on Time Zones: For an in-depth understanding of how Rails manages time zones, consider reviewing the official Rails documentation on time zones.
Stimulus Handbook: Given that we've employed Stimulus in our solution, brushing up on its fundamentals will be useful. Check out the Stimulus Handbook for a deep dive.
Devise GitHub Repository: We've customized Devise views in this tutorial. For more advanced customizations or to understand how Devise works under the hood, take a look at its GitHub repository.
Call to Action
Implement It Now: Don't just read—act. Try implementing the time zone setting feature in your own Rails application. The sooner you do it, the better you'll understand it.
Share Your Progress: Built something cool based on this tutorial? Share it on social media and tag us. I’d love to see your implementations and variations!
Feedback: Your opinions and feedback are vital for improvement. Drop a comment below to share your thoughts or any challenges you've encountered.
Stay Updated: If you've found this article useful, make sure to like it and subscribe to my Substack. I regularly push out content like this, and you won't want to miss it.
Community: Join Rails-specific communities and forums to share your knowledge and learn from others. The Rails community is a great asset, and participating will only make you a better developer.
By empowering your Rails applications with intuitive time zone setting features, you enhance user experience considerably. Now that you've got the knowledge, it's time to put it into action. See you in the next article!

