Managing Timezones in JavaScript Apps

Handling timezones in web and mobile applications can feel intimidating. For a long time, I tried to avoid it — hoping to sidestep a deep dive into the complexities until I absolutely had to confront them. That time finally came, and after some exploration, I developed a mental model that simplifies the process considerably for me.

The foundation of this model is based on two facts:

  1. ISO 8601 provides a standardized way to represent dates and times, including precise timezone information. As long as you include the timezone in an ISO 8601 string, you have all the data needed for an exact date and time representation.

  2. JavaScipt's Date object stores time in UTC internally, ensuring consistent time tracking regardless of the user's local timezone.

Let's break this down with an example.

const t1 = new Date();
t1.toISOString();
// 2024-11-02T15:00:00.000Z
const t2 = new Date("2024-11-02T15:00:00.000Z");
const t3 = new Date("2024-11-02T16:00:00.000+01:00");
const t4 = new Date("2024-11-02T14:00:00.000-01:00");
// t2, t3 and t4 are exactly the same date and time

This means that no matter what ISO 8601 string you use to initialize a Date, JavaScript will correctly interpret it, converting everything to UTC internally.

Mental Model

This means we can simply use the Date object type in the browser or Node backends. Whenever we don't have a native Date type, we can rely on a ISO 8601 strings e.g. APIs, Entry in a DB (e.g. Sqlite).

In the application we can create dates using these ISO 8601 strings as input for Date and can convert every Date to an ISO 8601 string. Timezone information must always be included in the ISO 8601 strings.

While JavaScript internally manages time in UTC, most users are concerned with their local time. No problem, at the UI boundary we can convert the Date value to the user's local time e.g.

t2.toLocaleString("en-US", { timeZone: "America/New_York" });

For any user input that includes date and time we create a new Date and it automatically will use the user's local timezone.

A simple model that should work for most use-cases.

APIAPItoLocalStringtoLocalStringDateDateDateDateISO8601string(UTC)ISO8601string(UTC)ISO8601stringISO8601stringnativeDatetypeorISO8601string(UTC)nativeDatetypeorISO8601string(UTC)UIUIClientLogicClientLogicBackendLogicBackendLogicDatabaseDatabase

Handling Multiple Timezones

While most developers won't need it, let's explore an advanced use-case. What if users need to view or work with dates in a timezone other than their system's? Unfortunately, native JavaScript does not allow you to switch to a different timezone context, but libraries like Luxon provide this capability.

With Luxon, you can set the default timezone globally:

import { DateTime, Settings } from "luxon";
Settings.defaultZone = "Asia/Tokyo";
DateTime.local().zoneName; //=> 'Asia/Tokyo'

However, keep in mind that any time you render or manipulate dates, you must use Luxon. This applies to all date-related operations, including parsing and rendering dates in external UI components. For instance, the React DayPicker library has a timezone prop that also forces a specific timezone internally.

If you get the now() as if the user would be in another timezone you need to calculate the difference between the global timezone and the local timezone and adjust the time accordingly.

import { DateTime, Settings } from "luxon";
Settings.defaultZone = "Asia/Tokyo";
export function now() {
const localTime = DateTime.now().setZone("local");
const globalTime = DateTime.now();
const differenceInMinutes =
globalTime.offset - localTime.offset;
return globalTime.minus({
minutes: differenceInMinutes,
});
}

A Note on ISO 8601 and Databases

While you could store ISO 8601 strings directly in a database, I recommend parsing and serializing dates to UTC before storing them. This approach ensures that all date values are valid and allows for efficient sorting by date in a lexicographical order.

This model, based on a few clear principles and the right tools, can help you confidently manage timezones in JavaScript applications, keeping code clean and user-friendly without excessive complexity.

Conclusion

Managing timezones in JavaScript applications can initially seem complex, but with this mental model, it hopefully becomes much more approachable.

Thanks to Botond Rostas for exploring and defining the mental model with me.


Join the Newsletter

Thoughts on Software Engineering with a focus on React, Cryptography, CRDTs and Effect.