Developer

Unix Timestamps and Time Zones: A Working Guide for Developers

Epoch seconds, milliseconds, UTC offsets, ISO 8601 and daylight saving explained with real numbers, plus the rules for storing and displaying times that stop clock bugs before they ship.

Unix Timestamps and Time Zones: A Working Guide for Developers
Photo by Monstera Production on Pexels

Key takeaways

  • A Unix timestamp counts seconds since 1 January 1970 UTC and means the same instant everywhere, which is why it is the safest thing to store.
  • Ten digits is seconds, thirteen is milliseconds; mixing them up puts a date in 1970 or in the year 58,000.
  • Signed 32-bit timestamps overflow on 19 January 2038 at 03:14:07 UTC, so use 64-bit integers and date types that reach beyond it.
  • Store instants in UTC, store a named zone such as Europe/London for future local events, and convert to local time only when displaying.
  • Daylight saving removes one hour in spring and repeats one in autumn; never add 24 hours to get tomorrow, add one calendar day in the right zone.

A Unix timestamp is the number of seconds that have passed since 00:00:00 UTC on 1 January 1970. It is a single integer that names one instant everywhere on Earth, with no zone, no format and no daylight saving to argue about, which is why databases, APIs and log files lean on it. The trouble starts when that integer meets people, who want to see 27 October 2026 at 10:00 in their own zone. This guide covers the timestamp itself, the seconds-versus-milliseconds trap, the 2038 limit, UTC and ISO 8601, the two daylight saving bugs that bite every year, and a set of rules for storing and displaying times that hold up.

What a Unix timestamp is

The reference point, called the epoch, is 1970-01-01T00:00:00Z. Timestamp 0 is that moment; 86400 is one day later; 1793102400 is 12:00:00 UTC on 27 October 2026. Negative values go backwards, so -86400 is 31 December 1969. The count is the same whether the computer sits in Tokyo or Toronto, because it measures elapsed time rather than wall-clock time.

Two details are worth knowing. First, the count ignores leap seconds: every Unix day is exactly 86,400 seconds, and the 27 leap seconds added since 1972 are absorbed by stretching or repeating a second rather than by changing the count. For almost all applications that is a feature. Second, the value is a plain integer, so comparing, sorting and subtracting timestamps is ordinary arithmetic. The gap between 1793102400 and 1793098800 is 3,600 seconds, one hour, with no calendar logic involved.

You get the current value from date +%s in a shell, time.time() in Python, time() in PHP, Date.now() in JavaScript and System.currentTimeMillis() in Java. Three of those five return seconds and two return milliseconds, which brings us to the first trap.

Seconds or milliseconds?

There is no marker in the number that says which unit it is. You have to know, or infer it from the length. Values for any date in the twenty-first century fall into clear bands:

DigitsUnitExampleTypical source
10Seconds1793102400Unix tools, PHP, Python, Go, most databases, JWT claims
13Milliseconds1793102400000JavaScript, Java, Kotlin, many JSON APIs
16Microseconds1793102400000000PostgreSQL internals, some tracing systems
19Nanoseconds1793102400000000000Go UnixNano(), Linux kernel, InfluxDB

The two failure modes are mirror images. Feed a 13-digit millisecond value to a function expecting seconds and the date lands around the year 58,800. Feed a 10-digit seconds value to JavaScript's new Date(), which expects milliseconds, and you get 21 January 1970. Both are instantly recognisable once you know to look. The Unix Timestamp Converter detects the unit from the length and shows the date either way, which is the quickest sanity check when an API response looks wrong.

Worked example. A webhook delivers "created": 1793102400123. Thirteen digits, so milliseconds. Dividing by 1,000 and discarding the remainder gives 1793102400, which is 12:00:00 UTC on 27 October 2026; the trailing 123 is the millisecond part. In JavaScript, new Date(1793102400123) works directly, while in Python you would write datetime.fromtimestamp(1793102400123 / 1000, tz=timezone.utc).

The 2038 problem

Older systems store the timestamp in a signed 32-bit integer, whose largest value is 2,147,483,647. That many seconds after the epoch is 2038-01-19T03:14:07Z. One second later the integer wraps around to its most negative value, and the date becomes 13 December 1901. Software that still relies on 32-bit time will misorder events, reject certificates, expire sessions that have not expired and refuse dates that are perfectly reasonable.

Modern 64-bit operating systems and languages use 64-bit time values, which last for roughly 292 billion years, so the problem is no longer in the kernel. It survives in the places that are hard to update: embedded controllers, old file systems, binary file formats with 32-bit fields, firmware in industrial and medical equipment, and database columns. MySQL's classic TIMESTAMP type, for instance, has historically been limited to that 2038 moment, which is one reason DATETIME is often recommended instead. Any system that calculates dates 12 or more years ahead, such as mortgages, pensions and long certificates, is already hitting the limit now.

The fix is to use 64-bit integers for timestamps everywhere you control, choose database types without the limit, and test with a date beyond 2038 rather than assuming. Converting 2147483647 and 2147483648 in a timestamp tool is a one-minute way to see whether a library handles the boundary.

UTC versus local time

Coordinated Universal Time (UTC) is the reference from which every other zone is an offset. London is UTC+0 in winter and UTC+1 in summer. New York is UTC-5 or UTC-4. Kolkata is UTC+5:30 all year. The timestamp 1793091600 is 09:00 UTC on 27 October 2026, which at that moment is 05:00 in New York, 14:30 in Kolkata and 20:00 in Sydney. Same instant, four wall clocks.

The distinction that matters in code is between an offset and a zone. +01:00 is an offset: a fixed number of hours from UTC. Europe/London is a zone: a place with a set of rules that say which offset applies on which dates, including every historical change and every future daylight saving transition. An offset tells you how to read one timestamp; a zone tells you how to read any timestamp for that place. If you store only an offset for a future event, you lose the ability to know what the local time will be if the rules change, and they change somewhere in the world almost every year.

The Time Zone Converter works with named zones, so it applies the right offset for the date you enter rather than whatever the offset is today.

Wall clocks showing the time in different cities
Photo by wal_ 172619 on Pexels

ISO 8601: the one format to write down

When a time has to be text, use ISO 8601. It sorts correctly as a string, every language parses it, and it carries the offset so there is no ambiguity about which instant is meant.

FormExampleMeaning
UTC instant2026-10-27T10:00:00Z10:00 UTC; the Z means zero offset
Instant with offset2026-10-27T10:00:00+01:0010:00 in a zone one hour ahead, which is 09:00 UTC
Fractional seconds2026-10-27T10:00:00.123ZMilliseconds, common in logs and JavaScript
Date only2026-10-27A calendar day with no time and no zone
Local, no offset2026-10-27T10:00:00Ambiguous: 10:00 somewhere; avoid for stored data
DurationP1DT2HOne day and two hours

The last-but-one row is the dangerous one. A timestamp without Z or an offset is a wall-clock reading with the location torn off. JavaScript treats such a string as local time; some libraries treat it as UTC. Across two services in different regions it means two different instants. The rule is simple: if it is an instant, it carries a Z or an offset, always.

Prefer T between date and time and a colon in the offset, as in +05:30. Older formats such as Tue, 27 Oct 2026 10:00:00 GMT from HTTP headers or 10/27/2026 from spreadsheets should be parsed at the boundary and converted, never stored.

Daylight saving pitfalls

Twice a year, in regions that observe daylight saving time, the local clock jumps. In the United Kingdom in 2026 the clocks go forward at 01:00 UTC on 29 March and back at 01:00 UTC on 25 October. Each jump creates a specific bug.

The missing hour

On 29 March, local time goes from 00:59:59 straight to 02:00:00. The local time 01:30 on that date does not exist. Code that builds a date from the parts year, month, day, hour and minute for a scheduled job at 01:30 will either throw, silently move to 02:30, or move back to 00:30, depending on the library. A job scheduled for 01:30 local simply does not run that day in some schedulers.

The repeated hour

On 25 October, local time goes from 01:59:59 back to 01:00:00, so 01:30 happens twice: once at UTC+1 and again an hour later at UTC+0. A log line stamped 01:30 local is ambiguous. A job set for 01:30 may run twice. A duration calculated from two local times inside that hour can come out negative.

Adding a day is not adding 24 hours

The day of the spring change is 23 hours long; the autumn one is 25. If you compute "same time tomorrow" by adding 86,400 seconds to a timestamp, the result lands at 09:00 one day and 10:00 the next across the transition. Add one calendar day in the named zone instead, and let the library resolve the offset. The Date Difference Calculator is useful for checking what a span of days should look like when the arithmetic feels off.

Not everyone changes on the same day

The United States moved its clocks on 8 March 2026 and moves them back on 1 November; most of Europe changed on 29 March and 25 October. For three weeks in spring and one week in autumn, the gap between New York and London is four hours instead of the usual five. A standing 14:00 London call that is normally 09:00 in New York becomes 10:00 during those weeks. Hard-coding "London is five hours ahead of New York" is wrong for about a month every year.

Many places do not observe daylight saving at all, including most of Asia and Africa, Queensland and Arizona. Zone rules also change by legislation, sometimes with weeks of notice. Keep your operating system and language zone database updated, because stale rules produce wrong offsets with no error message.

Morning sunlight on a clock beside a window
Photo by MÖV Frame on Pexels

Storing and displaying times correctly

Most clock bugs disappear if you follow a handful of rules consistently.

  1. Store instants as UTC. Either an integer timestamp or an ISO 8601 string ending in Z, or a database column with time zone support set to UTC. The moment an order was placed, a message was sent or a login happened is an instant.
  2. Store future local events with their zone. A meeting at 09:00 on 3 March 2027 in Europe/Paris is a local time plus a zone name, not an instant, because the offset on that day is not guaranteed yet. Store 2027-03-03T09:00 and Europe/Paris as two fields and resolve to an instant when the day approaches.
  3. Store the user's zone as a name, such as America/Chicago, never as an offset like -6.
  4. Run servers in UTC. A server set to local time hides daylight saving bugs until they strike, and makes log lines from two regions impossible to line up.
  5. Convert at the edges. Parse incoming local times into UTC as soon as they arrive, and format UTC into local time as late as possible, in the user's zone, using the platform's locale rules for the order of day and month.
  6. Show the zone when it matters. "10:00" on an invoice shared across countries is a question; "10:00 BST" or "10:00 (UTC+1)" is an answer.
  7. Test the transitions. Add the four dates above to your test suite, with times inside the missing and repeated hours, and test a date past 2038. These are the cases that production will find otherwise.

Date-only values deserve one more note. A birthday or a public holiday is a calendar date, not an instant. Storing 27 October as 2026-10-27T00:00:00Z and displaying it in a zone west of Greenwich shows 26 October. Keep dates as dates, in a date column or a YYYY-MM-DD string, and never attach midnight to them.

Conversion table of common values

These are handy for recognising values on sight and for checking that a tool or library agrees with reality.

Timestamp (seconds)UTC date and timeWhy it comes up
01970-01-01 00:00:00The epoch; a value of 0 or 1970 usually means an unset field
864001970-01-02 00:00:00One day in seconds
6048001970-01-08 00:00:00One week in seconds
9466848002000-01-01 00:00:00Start of the year 2000
10000000002001-09-09 01:46:40The billionth second
15000000002017-07-14 02:40:00A round number seen in test data
17000000002023-11-14 22:13:20Another round number
17672256002026-01-01 00:00:00Start of 2026
17931024002026-10-27 12:00:00The example used throughout this article
20000000002033-05-18 03:33:20The two-billionth second
21474836472038-01-19 03:14:07Largest signed 32-bit value: the 2038 problem

A few rule-of-thumb durations complete the set: 3,600 seconds in an hour, 86,400 in a day, 604,800 in a week, 2,592,000 in 30 days and 31,536,000 in a 365-day year. A value that is a clean multiple of 86,400 is almost always a date-only field that was stored as midnight UTC. For other spans, the Time Converter turns seconds into minutes, hours, days or weeks; its month and year are averages (a year is 365.25 days, or 31,557,600 seconds), so use it for durations and leave calendar arithmetic to a date library.

Where to go next

Paste any timestamp into the Unix Timestamp Converter to see the date in UTC and your local zone, or type a date to get the number back; it handles seconds and milliseconds. Use the Time Zone Converter to check what a time in one named zone is in another on a specific date, including across daylight saving changes, and the Date Difference Calculator to count the days, weeks or business days between two dates. All three run in your browser.

Frequently asked questions

Is a Unix timestamp in seconds or milliseconds?

The classic Unix timestamp is in seconds, and for current dates it has ten digits. JavaScript and Java use milliseconds, which gives thirteen digits. There is no marker inside the number, so look at its length: ten digits is seconds, thirteen is milliseconds, sixteen is microseconds. Passing milliseconds where seconds are expected produces a date tens of thousands of years away; the reverse produces a date in January 1970.

Does a Unix timestamp change with time zone?

No. A timestamp counts seconds since 1 January 1970 UTC and identifies one instant, so the same value is correct everywhere in the world. Only the way it is displayed changes: 1793091600 is 09:00 in London, 05:00 in New York and 20:00 in Sydney on 27 October 2026. That is why storing timestamps or UTC values, and converting to local time only when displaying, avoids most time bugs.

What happens in 2038?

Systems that keep the timestamp in a signed 32-bit integer reach their maximum, 2,147,483,647, at 03:14:07 UTC on 19 January 2038. One second later the value wraps to a large negative number and the date reads as December 1901. Modern 64-bit systems are unaffected, but embedded devices, old file formats and some database column types still have the limit, and anything calculating dates more than twelve years ahead already meets it.

Should I store dates in UTC or local time?

Store instants, such as when something happened, in UTC, either as a timestamp or an ISO 8601 string ending in Z. Store future local events, such as a meeting next year, as a local time plus a named zone like Europe/London, because the offset for that date is not guaranteed until it arrives. Store calendar dates like birthdays as plain dates with no time at all, and convert to the viewer's zone only for display.

Why did my scheduled job run twice or not at all?

Almost certainly daylight saving. When clocks go forward, an hour of local time does not exist, so a job set inside it is skipped or moved. When clocks go back, an hour repeats, so a job set inside it can run twice. Schedule jobs in UTC, or avoid the 01:00 to 03:00 local window on transition days, and make sure the scheduler uses a named zone whose rules are up to date.

Try the tools from this guide

Keep reading

Related guides

All guides