DevTools Hub
All articles

Development

Unix Timestamps: A Practical Guide to Epoch Time and Time Zones

Seconds versus milliseconds, why you should store UTC, how daylight saving time bites, and the Year 2038 problem.

By DevTools Hub Team · · 6 min read

Dates and times cause a remarkable share of software bugs: reports that are off by an hour twice a year, appointments that shift when a user travels, tokens that expire instantly. Most of these bugs disappear once you understand one simple idea, the Unix timestamp, and a few rules about when to apply time zones.

What a Unix timestamp is

A Unix timestamp is the number of seconds elapsed since 00:00:00 UTC on 1 January 1970, a moment known as the Unix epoch. At the moment of writing it is a little under 1.8 billion. The key property is that it identifies an instant: the same number means the same moment everywhere on Earth, regardless of time zone or daylight saving.

1793836800  →  2026-11-05T00:00:00Z  (Thursday, 5 November 2026, midnight UTC)

One subtlety: Unix time ignores leap seconds. Every day is treated as exactly 86,400 seconds, which makes arithmetic easy and is the right behavior for almost every application.

Seconds or milliseconds?

The single most common timestamp bug is mixing up units. Different ecosystems chose differently:

Seconds (10 digits today)Milliseconds (13 digits today)
Unix/Linux date +%s, C, Go's Unix()JavaScript Date.now()
Python time.time() (as a float)Java System.currentTimeMillis()
PHP time(), MySQL UNIX_TIMESTAMP()Many JSON APIs and analytics tools
JWT exp / iat claimsKafka and Elasticsearch timestamps

If a converted date comes out in January 1970, you passed seconds to something expecting milliseconds; if it lands tens of thousands of years in the future, you did the opposite. Counting digits is the fastest sanity check.

The golden rule: store UTC, display local

Store and transmit moments in time as UTC, either as a Unix timestamp or an ISO 8601 string ending in Z like 2026-11-05T14:30:00Z. Convert to the viewer's local time zone only at the very last step, when you show it on screen.

Following this rule means:

  • Sorting and comparing times is just comparing numbers.
  • Servers in different regions agree on what "now" means.
  • Moving your servers or changing your hosting region doesn't corrupt historic data.

When UTC is not enough

There is one important exception. For future events tied to a place, like a dentist appointment at 9:00 in Berlin six months from now, store the local time plus the time zone name (2027-05-12 09:00, Europe/Berlin), not a pre-computed UTC timestamp. Governments change daylight saving rules surprisingly often, sometimes with only weeks of notice. If you convert to UTC today and the rules change, the appointment silently moves by an hour.

Always use IANA zone names like America/New_York rather than abbreviations like EST. Abbreviations are ambiguous (CST is used for zones in the US, China and Cuba) and don't capture daylight saving transitions.

Daylight saving traps

  • Not every day has 24 hours. On transition days a local day has 23 or 25 hours. Adding 24 * 3600 seconds to get "tomorrow" lands an hour off. Use your date library's calendar-aware "add one day" function instead.
  • Some local times don't exist, and others happen twice. When clocks spring forward, 02:30 is skipped; when they fall back, 01:30 occurs twice. Scheduled jobs set for those times may be skipped or run twice.
  • Offsets aren't always whole hours. India is UTC+5:30, Nepal UTC+5:45.

The Year 2038 problem

Systems that store Unix time in a signed 32-bit integer can only count up to 2,147,483,647, which is reached at 03:14:07 UTC on 19 January 2038. One second later the value wraps to a large negative number, interpreted as December 1901. Modern operating systems, languages and databases have moved to 64-bit time, which won't overflow for about 292 billion years, but old embedded devices, file formats and database columns declared as 32-bit INT are still worth auditing.

Handy snippets

// JavaScript
Math.floor(Date.now() / 1000)          // current Unix time in seconds
new Date(1793836800 * 1000).toISOString()

# Python
import time, datetime
int(time.time())
datetime.datetime.fromtimestamp(1793836800, tz=datetime.timezone.utc)

-- PostgreSQL
SELECT extract(epoch FROM now())::bigint;
SELECT to_timestamp(1793836800);

For quick conversions, including seconds versus milliseconds and several time zones at once, use our Timestamp Converter.

Keep reading