How to Convert a User's Local Time to UTC Before Saving It
A user books a meeting for “3pm on Friday.” What gets written to your database depends on one question: did you convert their local time to UTC before saving it?
If not, you’ve stored 2026-06-20 15:00:00 with no time zone context. When you retrieve it and display it to another user — or the same user after a DST transition — the time will be wrong.
The rule: always store UTC. Always convert from the user’s IANA time zone at the point of input. Never store a fixed offset.
The correct input flow
User selects: Friday 20 June, 3:00pm
User's time zone: America/New_York (from browser or profile)
Convert at input:
2026-06-20T15:00:00 America/New_York
→ 2026-06-20T19:00:00Z (UTC, because EDT = UTC−4 in June)
Store: 2026-06-20T19:00:00Z
On retrieval, convert back to the user’s local time:
2026-06-20T19:00:00Z → 3:00pm America/New_York ✓
2026-06-20T19:00:00Z → 8:00pm Europe/London ✓ (for a different attendee)
Getting the user’s time zone
In the browser:
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
// → "America/New_York"
Send this to your backend alongside the datetime string. Don’t trust it blindly — let users confirm or override it in their profile. Some devices report incorrect zones.
Node.js with date-fns-tz:
import { fromZonedTime } from "date-fns-tz";
const localDateString = "2026-06-20T15:00:00"; // from user input
const timeZone = "America/New_York"; // from browser/profile
const utcDate = fromZonedTime(localDateString, timeZone);
// utcDate is a JS Date object representing 2026-06-20T19:00:00Z
Python (with zoneinfo, Python 3.9+):
from datetime import datetime
from zoneinfo import ZoneInfo
local_dt = datetime(2026, 6, 20, 15, 0, 0, tzinfo=ZoneInfo("America/New_York"))
utc_dt = local_dt.astimezone(ZoneInfo("UTC"))
# utc_dt → 2026-06-20 19:00:00+00:00
The DST ambiguity trap
On the night clocks fall back, 1:30am America/New_York occurs twice — once before the transition and once after. If a user picks a time in that repeated hour, you can’t know which occurrence they mean.
Most libraries default to the earlier (pre-transition) occurrence. Make this explicit in your code rather than relying on the default. For scheduling tools, the cleanest solution is to avoid offering slots in the ambiguous hour on DST-transition nights, or to flag them and ask the user to confirm.
What to store
- A UTC timestamp (
TIMESTAMP WITH TIME ZONEin PostgreSQL, UTC ISO 8601, or Unix epoch) - The user’s IANA zone string (
America/New_York) alongside the event, for display - Not: a bare local datetime without zone, or a numeric offset like
−04:00
The IANA zone is for display. The UTC timestamp is for comparisons, sorting, and calendar math.