Unix Timestamps: Seconds, Milliseconds and Time Zones
What a Unix timestamp is, why seconds and milliseconds get mixed up, how to convert correctly in JavaScript, PHP, Python and WordPress, and the time zone traps, all tested.
On this page
A Unix timestamp is the number of seconds since 00:00:00 UTC on 1 January 1970, not counting leap seconds. It is always in UTC; time zones only matter when you convert it to a local date for display. The most common bugs come from mixing seconds and milliseconds. A 10-digit timestamp is in seconds, as used by PHP, Python, MySQL and most APIs. A 13-digit one is in milliseconds, as used by JavaScript's Date. Other common causes are parsing dates without a time zone and storing timestamps in 32-bit fields that overflow in January 2038.
This post explains how timestamps work and how to convert them correctly in JavaScript, PHP, Python and WordPress. It also covers the time zone traps that cause off-by-hours errors. We ran every example in Node.js 22, PHP 8.3, Python 3 and a WordPress 7.1.3 test site, and the outputs shown are from those runs.
What a Unix timestamp is
The timestamp 1791600000 means 1,791,600,000 seconds after the start of 1970 in UTC. That is 10 October 2026 at 02:40:00 UTC. The same moment is 03:40 in London, 08:10 in Kolkata and 22:40 on 9 October in New York. The timestamp does not change; only its local display does.
Table: One timestamp, several local times (computed in Node.js)
| Representation | Value |
|---|---|
| Unix timestamp (seconds) | 1791600000 |
| Milliseconds | 1791600000000 |
| ISO 8601, UTC | 2026-10-10T02:40:00Z |
| London (BST) | 10 October 2026, 03:40 |
| Kolkata (IST) | 10 October 2026, 08:10 |
| New York (EDT) | 9 October 2026, 22:40 |
Because a timestamp is a single number in UTC, it is ideal for storing and comparing moments in time: sorting events, measuring durations, checking expiry. It is less suited to things that are inherently local, such as "every day at 9:00 in the shop's time zone".
<h2
Seconds versus milliseconds
Table: Timestamp units by language and system
| Environment | Current time | Unit |
|---|---|---|
| JavaScript | Date.now() |
Milliseconds (13 digits today) |
| PHP | time() |
Seconds (10 digits) |
| Python | time.time() |
Seconds, with a decimal fraction |
| MySQL | UNIX_TIMESTAMP() |
Seconds |
JSON Web Tokens (exp, iat) |
N/A | Seconds |
Mixing them up produces dates that are wildly wrong. In our Node.js run:
new Date(1791600000 * 1000).toISOString() // "2026-10-10T02:40:00.000Z" correct
new Date(1791600000).toISOString() // "1970-01-21T17:40:00.000Z" seconds passed as ms
A date in January 1970 is the classic sign that seconds were treated as milliseconds. A date tens of thousands of years in the future is the opposite mistake. Convert explicitly at every boundary between a JavaScript front end and a PHP or API back end:
const seconds = Math.floor(Date.now() / 1000); // JS ms -> s
const date = new Date(seconds * 1000); // s -> JS Date
Before you work with timestamps
- Know the unit of every timestamp you receive: count the digits or read the API documentation.
- Know which time zone each displayed date should use: the visitor's, the site's or the business's.
- Store in UTC. Keep timestamps or UTC date-times in the database and convert only for display.
- Use 64-bit storage for timestamps that may go beyond 2038.

Converting in each language
JavaScript
const ts = 1791600000;
const d = new Date(ts * 1000);
d.toISOString(); // UTC
d.toLocaleString('en-GB', { timeZone: 'Europe/London' }); // a named zone
Math.floor(new Date('2026-10-10T02:40:00Z').getTime() / 1000); // back to seconds
PHP
echo gmdate( 'c', 1791600000 ); // 2026-10-10T02:40:00+00:00
$dt = new DateTimeImmutable( '@1791600000' );
echo $dt->setTimezone( new DateTimeZone( 'Asia/Kolkata' ) )->format( 'Y-m-d H:i' );
echo time(); // current timestamp in seconds
Python
from datetime import datetime, timezone
datetime.fromtimestamp(1791600000, timezone.utc).isoformat()
# '2026-10-10T02:40:00+00:00'
int(datetime(2026, 10, 10, 2, 40, tzinfo=timezone.utc).timestamp())
# 1791600000
Pass a time zone explicitly in Python. Without one, fromtimestamp() converts to the server's local time zone, which may not be what you expect.
For quick checks, the Unix timestamp converter converts in both directions, treats 13-digit values as milliseconds, and shows the result in UTC, your local time and ISO 8601.
The time zone traps
Parsing dates without a zone
JavaScript treats date strings differently depending on their format. We ran this in Node.js with the time zone set to Asia/Kolkata (UTC+5:30):
Table: How JavaScript parsed date strings (Node.js, TZ=Asia/Kolkata)
| Input | Interpreted as | Resulting UTC time |
|---|---|---|
new Date('2026-10-14T10:00:00') |
Local time | 2026-10-14T04:30:00Z |
new Date('2026-10-14') |
UTC midnight | 2026-10-14T00:00:00Z |
new Date('2026-10-14T10:00:00Z') |
UTC | 2026-10-14T10:00:00Z |
A date-time without a zone is local, but a date alone is UTC. The same code gives different timestamps on servers and browsers in different zones. Always include Z or an offset such as +05:30 when exchanging date strings.
Daylight saving time
Local clocks jump; timestamps do not. In our run, 00:30 UTC on 29 March 2026 displayed as 00:30 in London, and 01:30 UTC displayed as 02:30, because British Summer Time began in between. Local 01:30 that morning never existed. When you schedule by local time, use a time zone library or named zones (such as Europe/London), not fixed offsets.
Timestamps in WordPress
WordPress stores post dates twice: post_date in the site's time zone and post_date_gmt in UTC. For timestamps, the functions behave differently:
Table: WordPress time functions (tested on WordPress 7.1.3)
| Function | Returns | Use it for |
|---|---|---|
time() |
The real Unix timestamp (UTC) | Storing and comparing moments |
current_time( 'timestamp' ) |
The timestamp plus the site's UTC offset | Avoid in new code; it is not a true Unix timestamp |
wp_date( $format, $timestamp ) |
A formatted date in the site's time zone | Displaying dates to visitors |
current_time( 'mysql', true ) |
UTC date-time string | Database fields in UTC |
With the test site's time zone set to London during British Summer Time, current_time( 'timestamp' ) was exactly 3,600 seconds ahead of time(), and wp_date() printed the local time with "BST". WordPress's own documentation for current_time() explains that the timestamp type returns the timestamp "or a sum of timestamp and timezone offset". Treating that sum as a real timestamp shifts every calculation by the site's offset.
Set the site's time zone in Settings → General using a city name, such as "London", rather than a fixed UTC offset, so daylight saving is applied automatically.
Worked example: a token expiry bug
An illustrative WordPress plugin checks whether a sign-up link has expired. It stores the expiry as current_time( 'timestamp' ) + 3600 and later compares it with time(). On a site set to UTC+5:30, links appear to last 6.5 hours instead of 1, because the stored value already includes the 5.5-hour offset. On a site west of UTC, links expire before they are sent.
The fix is to use time() + HOUR_IN_SECONDS for the stored value and time() for the comparison, and to use wp_date() only when showing the expiry to a person. JSON Web Tokens use the same convention: their exp and iat claims are Unix timestamps in seconds, which the JWT decoder shows as readable dates.
Timestamps in APIs and JSON
JSON has no date type, so APIs send dates either as numbers or as strings. Each choice has its own pitfalls:
Table: Date formats in JSON
| Format | Example | Watch for |
|---|---|---|
| Unix seconds | 1791600000 |
Confusion with milliseconds |
| Unix milliseconds | 1791600000000 |
JavaScript loses precision above 2 to the power of 53 (about 9 quadrillion), but current millisecond timestamps are far below that |
| ISO 8601 with Z | "2026-10-10T02:40:00Z" |
The safest string form |
| ISO 8601 without a zone | "2026-10-10T02:40:00" |
Parsed as local time in JavaScript |
| Local formats | "03/04/2026" |
Ambiguous: 3 April or 4 March? Avoid |
When you design an API, pick one format, document it and use it everywhere. When you consume one, check a few real responses rather than trusting the field name. A date that arrives as a string where a number was expected can also make a strict parser fail; our guide to JSON syntax errors covers how to track down malformed responses.
The year 2038 problem
A signed 32-bit integer can hold up to 2,147,483,647. As a timestamp, that is 2038-01-19T03:14:07Z. One second later, a 32-bit counter overflows. In our tests, PHP on a 64-bit system (PHP_INT_SIZE 8) handled 2,147,483,648 normally as 2038-01-19T03:14:08+00:00. Packing the same value into a 32-bit integer in Python raised an error. The risk today is in old 32-bit systems, embedded devices and database columns or file formats that store timestamps in 32 bits. Use 64-bit integers or proper date-time types for anything that may extend past 2038, such as long subscriptions or mortgage schedules.
Verify your conversions
- Check the digit count of every timestamp entering your code.
- Convert a known value, such as
1791600000, and confirm you get2026-10-10T02:40:00Z. - Test with a server or browser set to a different time zone; results stored in UTC should not change.
- Test dates around daylight saving changes.
- Inspect API responses with a JSON formatter to see exactly what format dates arrive in.
Common mistakes
- Passing seconds to JavaScript's
Date, which expects milliseconds. - Exchanging date strings without a time zone.
- Storing local times instead of UTC.
- Using fixed offsets such as +01:00 for places with daylight saving.
- Using
current_time( 'timestamp' )as if it were a true Unix timestamp. - Rounding milliseconds inconsistently, so two systems disagree by a second.
- Storing timestamps in 32-bit fields.
Troubleshooting
Table: Timestamp problems and fixes
| Symptom | Likely cause | Fix |
|---|---|---|
| Date shows January 1970 | Seconds treated as milliseconds | Multiply by 1000 before creating a JavaScript Date |
| Date far in the future | Milliseconds treated as seconds | Divide by 1000 |
| Times off by whole hours | Missing time zone, or local time stored as UTC | Add Z or an offset; store UTC |
| Off by one hour for part of the year | Daylight saving ignored | Use named time zones |
| WordPress expiry checks wrong by the site's offset | current_time( 'timestamp' ) compared with time() |
Use time() for both |
| Negative or wrapped dates after 2038 | 32-bit storage | Use 64-bit integers or date-time columns |
Frequently asked questions
What is a Unix timestamp?
The number of seconds since 00:00:00 UTC on 1 January 1970, not counting leap seconds. It identifies a moment in time independent of time zones.
How do I tell seconds from milliseconds?
Count the digits. Current timestamps in seconds have 10 digits; in milliseconds they have 13.
Are Unix timestamps in UTC?
Yes. A timestamp always counts from a UTC starting point. Time zones apply only when you display it as a local date.
How do I convert a Unix timestamp to a date in JavaScript?
Multiply by 1000 and pass it to new Date(), then use toISOString() for UTC or toLocaleString() with a timeZone option for local time.
How do I get the current timestamp in PHP?
Use time(), which returns seconds. In WordPress, prefer time() over current_time( 'timestamp' ).
What is the year 2038 problem?
Signed 32-bit timestamps can only count to 19 January 2038 at 03:14:07 UTC. Systems that store timestamps in 32 bits will overflow after that.
Why is my date off by a few hours?
Usually a time zone was missing or applied twice: a string parsed as local time, or a local time stored as if it were UTC.
Do Unix timestamps include leap seconds?
No. Unix time treats every day as exactly 86,400 seconds, so leap seconds are not counted.
Should I store dates as timestamps or date strings?
Either works if it is UTC and consistent. Timestamps are compact and easy to compare; ISO 8601 strings with an offset are easier to read.
Which time zone does WordPress use?
The one in Settings → General. It affects displayed dates and post_date, while post_date_gmt and time() stay in UTC.
Next step
Paste a timestamp from your own logs or API into the Unix timestamp converter and confirm the unit and the UTC time. For more developer topics, browse our developer guides.
Related free tools for this topic
Unix Timestamp Converter
Convert Unix epoch timestamps to readable dates in UTC and your local time zone, or turn a date…
Merge PDF Files Online (Free, No Uploads)
Combine multiple PDF documents into a single file directly in your browser. Reorder pages, keep your files private,…
Images to PDF Converter (JPG, PNG, WebP to PDF)
Turn screenshots, mockups, and loose images into one organized, properly sized PDF. Choose A4, US Letter, or fit…
Organise PDF: Reorder, Rotate & Extract Pages (Free)
Rearrange, rotate, delete or extract specific pages from any PDF document in your browser. Use visual page controls…
PDF to Images (JPG & PNG Converter)
Convert PDF pages into crisp PNG or JPEG images in your browser. Choose resolution scale (1× to 3×)…
SVG Optimiser for Web & Elementor (Clean viewBox & currentColor)
Shrink SVG icons, preserve the viewBox, remove fixed dimensions, and make vectors adapt to Elementor colours using currentColor.…
Related resources
SaaS hero with dashboard preview
Two-column hero: headline, supporting text, two buttons and a trust line beside a visual panel. Stacks to one…
Centred SaaS hero with product window
Centred hero with a large headline, supporting text and two buttons, with optional highlights and a product window…
Dark studio hero with service highlights
Centred hero with a large headline, supporting text and two buttons, with optional highlights and a product window…
Related articles
SHA-256 vs MD5: Hashing for Checksums, Not Passwords
MD5 is broken for collision resistance; SHA-256 is not, and on our test machine it was faster. Where each fits, how to…
UUID v4 Explained: Uniqueness, Formats and Use Cases
The UUID v4 format, collision odds calculated, generation and validation tested in Node.js, Python and WordPress, and an SQLite test of random…
Minifying CSS: What It Saves and When Your Build Should Do It
We minified WordPress core stylesheets with five tools and compressed the results: what minification saves on top of gzip and Brotli, and…
Related guides
How Long Should a Meta Title Be? Pixels vs Characters Explained
"Keep it under 60 characters" is only half right. Google cuts titles by pixel width, not character count. Learn how to write…
The Default WordPress .htaccess File (and Safe Additions)
The exact .htaccess rules WordPress needs for pretty permalinks, what each line does, tested additions for HTTPS and file protection, and a…
Fluid Typography in Elementor with clamp()
Setting a heading to 56 px on desktop, 44 px on tablet and 32 px on mobile works, but text jumps between…
Comments