Unixdates

Guides · 8 min read

Timezones, UTC, and Unix time: common mistakes

Unix time is timezone-agnostic on the wire — but the moment a human sees it, a timezone has to enter the picture. Here are the timezone mistakes that bite developers most often, and how to keep your code out of them.

Published 22 April 2026

A Unix timestamp is just a number of seconds. It does not have a timezone. It does not even know what a calendar is. The number 1745301600 corresponds to a single moment in physical time — the same moment, everywhere on Earth.

The trouble starts as soon as you want to display that moment to a user, parse a date a user typed, or compare one date to another in a way that respects calendar logic. Timezones enter, and they bring an entire genre of bugs with them.

This guide covers the most common timezone mistakes, why they happen, and the patterns that prevent them.

Mistake 1: Storing local time as a Unix timestamp

When you take a calendar moment like “2026-04-22 10:00 in Oslo” and convert it to a Unix integer, what you get depends on the timezone you assume:

new Date('2026-04-22T10:00').getTime() / 1000;
// 1745308800 (if your machine is in UTC)
// 1745301600 (if your machine is in CEST, UTC+2)

Notice the missing Z at the end. JavaScript treats that string as local time, and “local time” depends on which machine the code runs on. Your laptop in Oslo, a CI runner in Frankfurt, a Lambda in us-east-1, and a Cloudflare Worker all give different answers for the same input string.

Fix: Always include an explicit timezone designator when you parse a date. Use Z for UTC, +HH:MM for an offset, or use a library that requires you to pass a zone.

new Date('2026-04-22T10:00:00+02:00').getTime() / 1000;  // unambiguous
new Date('2026-04-22T08:00:00Z').getTime() / 1000;       // unambiguous

Mistake 2: Thinking UTC offsets are timezones

A timezone is not the same as a UTC offset. “UTC+2” is an offset. “Europe/Oslo” is a timezone. The distinction matters because a single timezone uses different offsets at different times of the year (DST), and historically (Norway moved between offsets several times in the 20th century).

If you store +02:00 as your idea of “Oslo time”, everything works in summer and silently breaks in November when CEST switches to CET. The result is meeting reminders that fire an hour off, log timestamps that suddenly look an hour wrong, and reports that mis-bucket events around midnight.

Fix: Store IANA timezone names (Europe/Oslo, America/New_York, Asia/Tokyo) when you need to remember the timezone an event happened in. Convert to an offset only at the very end, when displaying.

Mistake 3: Doing date arithmetic in Unix time

Unix time counts seconds. That’s great for short durations and terrible for calendar arithmetic.

// Add one day to a Unix timestamp.
const tomorrow = ts + 86400;

This is wrong on any day where there is a DST transition. In Oslo, the day of the spring-forward transition has 23 hours, not 24. The day of the fall-back transition has 25 hours. Adding 86,400 seconds gives you the same wall-clock time only on days with no transitions.

It is also wrong for “add one month” or “add one year”, because months and years have variable lengths.

Fix: For calendar arithmetic, use a date library or a calendar-aware language API:

# Python
from datetime import datetime, timedelta
from zoneinfo import ZoneInfo

now = datetime.now(ZoneInfo("Europe/Oslo"))
tomorrow = now + timedelta(days=1)   # respects DST
// JavaScript with date-fns
import { addDays } from 'date-fns';
const tomorrow = addDays(new Date(), 1);

For pure “add a day to a Unix integer in UTC”, + 86400 is fine. For “what time will it be tomorrow at 10am in Oslo”, you have to think calendar-first.

Mistake 4: Storing wall-clock time without storing the zone

Imagine an app that lets users schedule a recurring meeting at 9am in their timezone. You store the meeting as a Unix timestamp.

A user in Oslo sets a meeting for 9am. Two months later, DST changes. Without the timezone, you don’t know whether to fire the reminder at the same Unix instant (which is now 8am or 10am wall-clock) or at a new Unix instant that lines up with 9am wall-clock.

This is a real, recurring source of calendar bugs. Google Calendar, Outlook, and every serious scheduling product solve it by storing both:

  • the timezone the event was created in (e.g. Europe/Oslo)
  • the wall-clock time, in that timezone, of the event (e.g. 09:00)

When it comes time to fire the reminder, they compute the Unix timestamp on the fly using the latest timezone rules.

Fix: If your data represents a future moment that should obey wall-clock semantics, store the wall-clock time and the IANA timezone, not just a Unix integer. If it represents a past moment, store the Unix timestamp and (optionally) the timezone the event was observed in for display.

Mistake 5: Confusing “the user’s timezone” with “the server’s timezone”

Most servers run in UTC. Most users do not. If you ever write code like:

const today = new Date();
today.setHours(0, 0, 0, 0);   // "midnight" — in the server's local time

…you are computing midnight in whatever zone the server happens to be in, and presenting that to the user as if it were their midnight. If your server is UTC and your user is in Tokyo (UTC+9), the user’s “today” is 9 hours ahead of your server’s “today”, and they will see yesterday’s data for the first 9 hours of their day.

Fix: Always be explicit about which timezone “today” means. Either:

  • Compute on the client where you have access to Intl.DateTimeFormat().resolvedOptions().timeZone, or
  • Pass the user’s timezone to the server with each request and use it for formatting and bucketing.

Mistake 6: Trusting Intl for parsing

Intl.DateTimeFormat is excellent for formatting dates. It does not parse. It is also locale-sensitive, which means the same code on two browsers might render slightly different output.

new Intl.DateTimeFormat('en-US').format(new Date());
// "4/22/2026" in Chrome
// "04/22/2026" in some Firefox builds
// "4/22/26" with different defaults

new Date('4/22/2026');   // Don't do this. It works, but it's fragile.

Fix: For parsing, either use ISO 8601 (the only format Date parses reliably across engines) or use a library with a strict parse(input, format) API.

Mistake 7: Treating “UTC” as a timezone for users

UTC is a timezone in the technical sense, but it is not a timezone any human lives in. A user who travels between timezones still wants to see times in their zone, not in UTC. If you display all timestamps as UTC because “it’s safest”, you push the conversion burden onto every user, every time they look at a screen.

Fix: Display dates in the user’s local timezone by default. Use UTC for storage, transit, logging, and machine-to-machine communication. Reach for ISO 8601 with the Z suffix when you want a string format that’s unambiguous and timezone-aware.

A defensive-coding checklist

Before you ship any code that touches dates:

  1. Every parsed date string has a timezone designator. Either Z or ±HH:MM. No naive parsing.
  2. Every displayed date has a target timezone. Either explicitly passed, or the user’s local zone via the platform API.
  3. Every date stored alongside calendar semantics has a timezone field next to it. “9am Oslo time” loses information if you only keep the Unix integer.
  4. Date arithmetic involving days, months, or years uses a calendar library, not raw integer math on Unix time.
  5. Tests include the DST transition days for whatever zones your users live in. The hours immediately before and after the transition are where bugs hide.

If you have a Unix timestamp on hand and want to see how it renders across UTC, your local zone, and ISO 8601 side by side, paste it into the Unixdates converter — that’s exactly what the output panel shows.

Need to convert a timestamp right now? Try the Unixdates converter — auto-detects seconds, milliseconds and microseconds.