# How to Handle Time Zones in Retool Applications

- Tool: Retool
- Difficulty: Beginner
- Time required: 15-20 min
- Compatibility: Retool Cloud and Self-hosted
- Last updated: March 2026

## TL;DR

Store all dates in UTC in the database. Enable the 'Manage time zone' toggle on Date Pickers to auto-convert between local and UTC. For display, use Intl.DateTimeFormat with the timeZone option to render UTC values in the user's local zone. For complex conversions, load moment-timezone via Retool's Preloaded JS setting.

## UTC Storage and Local Display — the Timezone Strategy for Retool

Timezone bugs are among the most confusing issues in data apps. The root cause is almost always the same: dates stored in local time instead of UTC, causing records to appear one day off for users in different timezones. The solution is a simple rule: always store UTC, always display local.

Retool runs in the user's browser, so component values default to the browser's local timezone. This creates subtle bugs: a Date Picker without timezone management enabled stores whatever the user selected as a bare timestamp, without timezone context. When another user in a different timezone loads that record, the same timestamp displays at a different local time.

This tutorial covers the correct timezone strategy for Retool apps: enabling the Date Picker's Manage time zone toggle for automatic UTC conversion, displaying stored UTC values in local time using the Intl API, and handling complex cross-timezone displays with moment-timezone for apps serving users across many zones.

## Before you start

- A Retool app with a Date Picker and a Table showing datetime values
- A database resource that stores TIMESTAMPTZ (PostgreSQL) or DATETIME (MySQL) columns
- Basic familiarity with JavaScript Date objects

## Step-by-step guide

### 1. Store dates as TIMESTAMPTZ in PostgreSQL

The foundation of correct timezone handling is the database column type. Use TIMESTAMPTZ (timestamp with time zone) in PostgreSQL rather than TIMESTAMP (without time zone). TIMESTAMPTZ stores the moment in time in UTC and PostgreSQL converts to the session timezone for display. In MySQL, use DATETIME with explicit UTC storage. Always insert UTC values from your application.

```
-- PostgreSQL: use TIMESTAMPTZ
CREATE TABLE events (
  id SERIAL PRIMARY KEY,
  title TEXT,
  event_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
  created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Insert UTC timestamp from Retool (Date Picker with Manage Time Zone enabled):
INSERT INTO events (title, event_at)
VALUES ({{ titleInput.value }}, {{ datePicker1.value }}::TIMESTAMPTZ);
-- datePicker1.value with Manage Time Zone ON = UTC ISO string

-- Query with timezone conversion:
SELECT
  title,
  event_at AT TIME ZONE 'UTC' AT TIME ZONE 'America/New_York' AS event_at_eastern
FROM events;
```

**Expected result:** Dates are stored as UTC TIMESTAMPTZ. PostgreSQL can convert to any timezone on retrieval.

### 2. Enable 'Manage time zone' on the Date Picker

Select the Date Picker component. In the Inspector → Advanced section, find the 'Manage time zone' toggle and enable it. With this on, when a user selects a date and time in their local timezone, Retool automatically converts it to UTC before exposing the value in {{ datePicker1.value }}. When loading a UTC value from the database into the Date Picker's Default Value, Retool converts it back to the user's local time for display. This toggle handles the most common timezone use case automatically.

```
// With 'Manage time zone' ON:
// User in New York selects: 2026-03-25 09:00 AM EST
// datePicker1.value = '2026-03-25T14:00:00.000Z' (UTC)

// User in London selects: 2026-03-25 09:00 AM GMT
// datePicker1.value = '2026-03-25T09:00:00.000Z' (UTC)

// Database INSERT — same UTC value regardless of user timezone:
INSERT INTO events (event_at)
VALUES ({{ datePicker1.value }}::TIMESTAMPTZ);

// Loading a UTC value back into the Date Picker:
// Default Value: {{ getEvent.data[0].event_at }}
// Display: shows in user's LOCAL time (e.g., 9 AM for both users)
```

**Expected result:** Date Picker stores UTC values regardless of user timezone. Users in different timezones see the correct local time for the same event.

### 3. Display UTC timestamps in user local time using Intl API

When displaying stored UTC timestamps in Text components, Table columns, or Text Inputs, convert them to the user's local timezone using the browser's built-in Intl.DateTimeFormat API. No additional libraries needed. This approach uses the user's actual browser timezone, ensuring correct display regardless of their location.

```
// Display UTC timestamp in user's browser timezone:
{{ new Intl.DateTimeFormat('en-US', {
  timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  year: 'numeric',
  month: 'short',
  day: 'numeric',
  hour: '2-digit',
  minute: '2-digit',
  timeZoneName: 'short'
}).format(new Date(table1.selectedRow.data.event_at)) }}
// Output: 'Mar 25, 2026, 09:00 AM EST'

// Short display (date only, local timezone):
{{ new Date(row.created_at).toLocaleDateString() }}

// With explicit timezone:
{{ new Intl.DateTimeFormat('en-US', {
  timeZone: 'America/New_York',
  dateStyle: 'medium',
  timeStyle: 'short'
}).format(new Date(row.event_at)) }}
// Output: 'Mar 25, 2026, 9:00 AM'
```

**Expected result:** UTC timestamps from the database display in the correct local time for each user's timezone.

### 4. Show the user's current timezone in the UI

Display the user's detected timezone in the app so they know which timezone dates are displayed in. Use Intl.DateTimeFormat().resolvedOptions().timeZone to get the IANA timezone identifier from the user's browser. Show it in a Text component or tooltip.

```
// Text component showing current timezone:
{{ `Times shown in: ${Intl.DateTimeFormat().resolvedOptions().timeZone}` }}
// Output: 'Times shown in: America/New_York'

// Shorter display with UTC offset:
{{ (() => {
  const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
  const offset = new Date().toLocaleTimeString('en-US', {
    timeZoneName: 'short',
    timeZone: tz
  }).split(' ').pop();
  return `${tz} (${offset})`;
})() }}
// Output: 'America/New_York (EST)'

// Store user timezone in a Temporary State for use in queries:
// In a 'Run on page load' JS Query:
await userTimezone.setValue(Intl.DateTimeFormat().resolvedOptions().timeZone);
```

**Expected result:** The app displays the user's timezone identifier. Users understand which timezone their dates are displayed in.

### 5. Use moment-timezone for complex conversions via Preloaded JS

For apps with explicit timezone selection (users choose which timezone to view data in) or complex DST-aware conversions, use the moment-timezone library. Load it via App Settings → Preloaded JavaScript by adding the CDN URL. After loading, moment() and moment.tz() are available globally in all JS Queries and transformer expressions.

```
// Preloaded JS URL to add:
// https://cdnjs.cloudflare.com/ajax/libs/moment-timezone/0.5.43/moment-timezone-with-data.min.js

// After loading, available in transformers and JS Queries:

// Convert UTC to a specific timezone:
{{ moment.tz(row.event_at, 'America/Los_Angeles').format('MMM D, YYYY h:mm A z') }}
// Output: 'Mar 25, 2026 6:00 AM PDT'

// Convert to the timezone selected in a dropdown:
{{ moment.tz(row.event_at, timezoneSelect.value).format('MMM D, YYYY h:mm A z') }}

// Convert local time to UTC for storing:
// JS Query:
const localTime = datePicker1.value; // user's local selection
const utcTime = moment.tz(localTime, selectedTimezone.value).utc().toISOString();
await insertEvent.trigger({ additionalScope: { eventAt: utcTime } });
```

**Expected result:** After loading moment-timezone, all JS Queries and transformer expressions can call moment.tz() for any timezone conversion.

### 6. Build a timezone selector for user-configured timezone display

For apps where users want to view data in a specific timezone (regardless of their browser timezone), add a timezone Select dropdown. Create a Temporary State named displayTimezone defaulting to the browser timezone. Populate the Select with common IANA timezone options. When the user changes it, update displayTimezone and the table/text display expressions update reactively.

```
// Temporary State: displayTimezone
// Default value: {{ Intl.DateTimeFormat().resolvedOptions().timeZone }}

// Timezone Select static options (common zones):
// Label: UTC                Value: UTC
// Label: Eastern US (ET)    Value: America/New_York
// Label: Pacific US (PT)    Value: America/Los_Angeles
// Label: London (GMT)       Value: Europe/London
// Label: Paris (CET)        Value: Europe/Paris
// Label: Tokyo (JST)        Value: Asia/Tokyo

// Display expression in Table column or Text component:
{{ new Intl.DateTimeFormat('en-US', {
  timeZone: displayTimezone.value,
  dateStyle: 'medium',
  timeStyle: 'short',
}).format(new Date(row.event_at)) }}

// JS Query: updateDisplayTimezone
await displayTimezone.setValue(timezoneSelect.value);
```

**Expected result:** Users can switch the display timezone and all date/time values in the app update to show the selected timezone.

## Complete code example

File: `JS Query: convertAndInsertEvent`

```javascript
// convertAndInsertEvent
// Reads from a Date Picker with 'Manage time zone' enabled
// and inserts a UTC timestamp into PostgreSQL

// Validate that a date is selected
if (!datePicker1.value) {
  utils.showNotification({
    title: 'Date required',
    description: 'Please select an event date and time.',
    notificationType: 'warning',
  });
  return;
}

// datePicker1.value is already UTC because 'Manage time zone' is enabled
const utcTimestamp = datePicker1.value;

// Display what we are storing (for debugging)
console.log('Storing UTC timestamp:', utcTimestamp);
console.log('User timezone:', Intl.DateTimeFormat().resolvedOptions().timeZone);
console.log('Local display:', new Date(utcTimestamp).toLocaleString());

// Insert into database
await insertEvent.trigger({
  additionalScope: {
    title: titleInput.value,
    eventAt: utcTimestamp,  // UTC ISO string → TIMESTAMPTZ
  }
});

utils.showNotification({
  title: 'Event created',
  description: `Scheduled for ${new Date(utcTimestamp).toLocaleString()} (your local time)`,
  notificationType: 'success',
});

form1.reset();
```

## Common mistakes

- **Not enabling 'Manage time zone' on Date Pickers — dates stored in the browser's local timezone cause different users to see different UTC values for the same selection** — undefined Fix: Enable 'Manage time zone' in Inspector → Advanced for every Date Picker. This converts the user's local selection to UTC before the value is stored.
- **Using TIMESTAMP (without time zone) columns in PostgreSQL instead of TIMESTAMPTZ — stored values have no timezone information, making cross-timezone queries ambiguous** — undefined Fix: Use TIMESTAMPTZ for all datetime columns. Migrate existing TIMESTAMP columns: ALTER TABLE events ALTER COLUMN event_at TYPE TIMESTAMPTZ USING event_at AT TIME ZONE 'UTC'.
- **Displaying dates without timezone conversion by using {{ row.event_at }} directly — shows the raw ISO string '2026-03-25T14:00:00.000Z' instead of a formatted local time** — undefined Fix: Wrap all date display expressions with new Date(row.event_at).toLocaleString() or Intl.DateTimeFormat with the timeZone option.
- **Assuming the browser timezone is the correct display timezone for all users in a multi-user admin app — admin users may all be in a different timezone than the data's primary timezone** — undefined Fix: Add a timezone selector component and store the display preference in Temporary State. Reference displayTimezone.value in all date formatting expressions.

## Best practices

- Always store dates in UTC using TIMESTAMPTZ columns — never store in local time, which makes cross-timezone queries and comparisons unreliable
- Enable 'Manage time zone' on every Date Picker in your app — it is off by default but almost always should be on for multi-user apps
- Display the user's timezone in the UI so users understand which timezone date values are shown in — prevents support tickets about 'wrong times'
- Do timezone conversion only at display time (in expressions and Transformers), not at storage time — store UTC, display local
- Use Intl.DateTimeFormat for simple timezone conversion (no library needed) and moment-timezone for DST-aware complex scenarios
- When comparing dates across timezones in SQL, always compare in UTC — use TIMESTAMPTZ columns which PostgreSQL stores and compares in UTC internally
- Test your app with users in at least UTC-8 (US Pacific) and UTC+9 (Japan) to catch date-boundary bugs that only appear in specific offset ranges

## Frequently asked questions

### Why does my Date Picker show March 24 when I stored March 25 in the database?

This is the classic timezone offset bug. The UTC timestamp '2026-03-25T00:00:00.000Z' (midnight UTC) is March 24 at 7 PM in US Pacific time (UTC-7). Enable 'Manage time zone' on the Date Picker so Retool converts display values to your local timezone, and use TIMESTAMPTZ columns in PostgreSQL to store timezone-aware values.

### Should I store user preferences for timezone or just use the browser's detected timezone?

For internal tools, browser detection is usually sufficient — team members are typically in known timezones and their browsers are correctly configured. For customer-facing tools or apps used by global teams, provide an explicit timezone selector and store the preference in the user's profile. The Intl API's detected timezone is accurate but users appreciate being able to override it.

### How do I run SQL queries that filter by date range in the user's local timezone rather than UTC?

The safest approach is to filter in UTC in the SQL and let Retool handle display conversion. The Date Picker with 'Manage time zone' enabled gives you UTC values in {{ datePicker1.value }}, which you pass directly to the TIMESTAMPTZ WHERE clause. PostgreSQL compares UTC to UTC correctly regardless of what timezone the user is in.

---

Source: https://www.rapidevelopers.com/retool-tutorial/how-to-handle-time-zones-in-retool-applications
© RapidDev — https://www.rapidevelopers.com/retool-tutorial/how-to-handle-time-zones-in-retool-applications
