How to extract the timestamp from a UUIDv7
- Steps
- 6
- Hands-on
- 10 min
- Reading
- 7 min
- Checked
- Oct 24
The short version
A UUIDv7 keeps its creation time in the first 12 hex digits: a Unix timestamp in milliseconds. Strip the dashes, parse those 12 characters as hex, and you have the time: int(u.replace('-', '')[:12], 16) in Python, uuid_extract_timestamp(id) in Postgres 18. The catch is that the spec lets libraries alter that time, and in our tests one popular Python package ran 16.6 minutes ahead of the clock and another wrote IDs that decode to the year 2202.

What you need
- A UUID you want to date (from your database, logs or an API)
- Any one of: Python 3.14+, Node.js, Rust with the uuid crate, or PostgreSQL (18 for the built-in function)
Part of the article Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%. This page answers one question from it in depth.
On this page · 12 sections
- Key numbers
- Check an ID right here
- Before you start: the time inside is a claim, not a record
- Confirm it is a version 7 UUID
- Read the first 12 hex digits as milliseconds
- Decode it in Python
- Decode it in JavaScript or Rust
- Decode it in PostgreSQL
- Sanity-check the result before you trust it
- What goes wrong
- Related
- Sources
You can paste an ID into the checker below and get the time plus a verdict, or follow the steps for your language. Every command on this page was run on October 24, 2026; the outputs shown are ours.
Key numbers
| What | Number | Source (checked Oct 24 2026) |
|---|---|---|
| Bits of time in a UUIDv7 | 48 (the first 12 hex digits) | RFC 9562, section 5.7 |
| Unit | milliseconds since Jan 1 1970 UTC | RFC 9562 |
| Last date 48 bits can hold | year 10889 | our math: 2^48 ms |
| Postgres version with uuid_extract_timestamp() for v7 | 18 | PostgreSQL 18 docs |
| uuid6 (PyPI) drift after 200,000 fast IDs, healthy clock | +199.7 s | our run, uuid6 2025.0.1 |
| uuid6 drift after 1,000,000 fast IDs | +16.6 min | our library test |
| PyPI package "uuid7" 0.1.0, decoded per RFC 9562 | year 2202 | our run, Oct 24 2026 |
| Python stdlib gap 2 min after a 1 h clock step-back at 10,000 IDs/s | 78 min ahead, still growing | our library test |
Check an ID right here
The time and a verdict show up here. Nothing leaves your browser.
Before you start: the time inside is a claim, not a record
RFC 9562 says it outright: "Implementations MAY alter the actual timestamp." Libraries use that freedom to keep IDs sorted when many are made in one millisecond or when the clock jumps back. Decoding is easy and identical everywhere; whether the answer is the real creation time depends on what made the ID. Step 6 shows how to tell.
Step 01: Confirm it is a version 7 UUID
The version is the first character of the third group: 01a16c37-766b-72b0-b69a-ea45923ae7ca. If it is 4, there is no time in the ID at all (Postgres returns NULL, the Rust crate returns None). If it is 1 or 6, the time is counted in 100-nanosecond steps since October 1582 and these steps do not apply. Why v7 replaced v4 for database keys is in UUIDv7 vs UUIDv4.
Step 02: Read the first 12 hex digits as milliseconds

0x01a16c37766b is 1,792,816,936,555. Divide by 1,000 and it is a normal Unix timestamp: October 24, 2026, 04:42:16.555 UTC. Our Unix timestamp converter turns that number into your local time. The rest of the ID is version, variant, random bits and, in many libraries, a counter. None of it is time, so sub-millisecond order cannot be read back out.
Step 03: Decode it in Python
Python 3.14 added uuid.uuid7(), and for a v7 the .time attribute is the 48-bit millisecond value. For any Python version, slice the hex:
import uuid, datetime as dt
def uuid7_time(u):
ms = int(str(u).replace("-", "")[:12], 16)
return dt.datetime.fromtimestamp(ms / 1000, dt.timezone.utc)
u = uuid.UUID("01a16c37-766b-72b0-b69a-ea45923ae7ca")
print(u.version, uuid7_time(u)) # 7 2026-10-24 04:42:16.555000+00:00
We ran the same decoder over IDs from four Python libraries:

uuid-utils (a Rust core) also exposes .timestamp in milliseconds, which matched our slice exactly.
Step 04: Decode it in JavaScript or Rust
JavaScript, with the ID as a string (Node, Deno, Bun or a browser):
const ms = parseInt(id.replace(/-/g, "").slice(0, 12), 16);
new Date(ms).toISOString(); // "2026-10-24T04:39:53.111Z" for our v7() ID from npm uuid 14.0.3
48 bits fit safely in a JavaScript number (the limit is 53), so no BigInt is needed; we checked that BigInt("0x" + hex) >> 80n gives the same value.
Rust, with the uuid crate 1.29 (features v7, plus v4 for the comparison):
use uuid::Uuid;
let id = Uuid::now_v7();
let (secs, nanos) = id.get_timestamp().expect("v1, v6 or v7 only").to_unix();
// our run: 01a16c35-6ddc-71c6-88e6-199d6f6ecf34 -> unix 1792816803.292 s
// Uuid::new_v4().get_timestamp() returns None
Step 05: Decode it in PostgreSQL
Postgres 18 added uuidv7() and made uuid_extract_timestamp() understand v7 (docs). We ran these in PostgreSQL 18.6 (the PGlite build, by ElectricSQL):

-- Postgres 18+
SELECT id, uuid_extract_timestamp(id) AS created FROM orders ORDER BY id DESC LIMIT 5;
-- Older Postgres: same 48 bits, by hand
SELECT to_timestamp(('x' || substr(replace(id::text, '-', ''), 1, 12))::bit(48)::bigint / 1000.0)
FROM orders;
Two things to know. uuidv7(interval '-1 hour') deliberately shifts the time it writes, so a column of these IDs can be honest-looking and an hour off by design. And the function does not check that the time is plausible: it will happily return 2202.
Step 06: Sanity-check the result before you trust it
If you use the decoded time for anything (audit trails, partitioning, "created at" on screen), compare it with an independent timestamp for a sample of rows. What we saw across 10 libraries in our UUIDv7 library test:
| You see | Likely cause |
|---|---|
| Date in 2202 | The PyPI package uuid7 (0.1.0, Dec 2021, imports as uuid_extensions): a pre-RFC draft layout with the version digit set to 7 |
| Minutes ahead after bursts | uuid6 on PyPI: adds 1 ms per ID when several land in one millisecond |
| Ahead and growing after a clock correction | Python's stdlib uuid.uuid7() after the system clock steps back, until the process restarts |
| Up to the size of the clock jump, then shrinking | Rust uuid, npm uuid, Go google/uuid: they freeze the time and count |
| Exactly the clock, but order broken inside a millisecond | .NET Guid.CreateVersion7(): true time, random low bits |
LiosK's uuid7 crate and uuidv7 npm package kept a true time in every test we ran; they reset to the real clock after a step back of more than 10 seconds.
What goes wrong
- Wrong digit count. Slice 12 hex digits after removing dashes, not the first 13 characters of the string;
01a16c37-766bis 13 characters with the dash. - Seconds vs milliseconds. JavaScript's
Datewants milliseconds; Python'sfromtimestampand Postgresto_timestampwant seconds, so divide by 1,000. - Bytes from .NET.
Guid.ToByteArray()writes the first 8 bytes little-endian. Decode from the string form, or useToByteArray(bigEndian: true). - Time zones. The value is UTC. Convert only for display.
- Trusting it for security. The time is not signed. Anyone who can make IDs can pick the time, so never use it as proof of when something happened.
Related
- Pick a UUIDv7 library that keeps order: 10 tested: the full study this guide is built on, with the CSV.
- UUIDv7 vs UUIDv4: which should you use?
- Why are crates.io download counts inflated?: the download numbers we quote for these libraries, and why they overstate use.
- UUID v7 generator and decoder and Unix timestamp converter: free, in your browser.
- Build a database in Rust: AI passed 866K tests, then failed ROLLBACK: where ordering bugs like these bite.
Sources
All checked October 24, 2026.
Primary
- IETF, RFC 9562: Universally Unique IDentifiers (UUIDs) (May 2024), by K. Davis, B. Peabody and P. Leach: section 5.7 (UUIDv7 layout, 48-bit unix_ts_ms) and section 6.1 ("Implementations MAY alter the actual timestamp").
- PostgreSQL Global Development Group, PostgreSQL 18 UUID functions: uuidv7(), uuidv7(interval) and uuid_extract_timestamp().
- Python, uuid module documentation: uuid7() added in 3.14.
- uuid crate for Rust, docs.rs: Uuid::get_timestamp, maintained by the uuid-rs team.
- npm uuid 14.0.3; PyPI uuid6 2025.0.1, uuid7 0.1.0 (Stephen Simmons, Dec 2021), uuid-utils 1.1.0.
- LiosK, uuid7 crate and uuidv7 on npm.
- ElectricSQL, PGlite 0.5.11 (PostgreSQL 18.6 compiled to WebAssembly), used to run the SQL on this page.
Our tests
- Every output on this page: our runs on October 24, 2026 (Python 3.14.0, Node 22.16, Rust 1.91 with uuid 1.29.0, PGlite 0.5.11) on Windows 11.
- The drift and clock-jump numbers in the table and the hero chart: Tensorust, Pick a UUIDv7 library that keeps order: 10 tested (Oct 23, 2026), with the full CSV.
- Credit to Robert Alexander, whose post "UUIDv7 doesn't have the properties you think it does" prompted that test, and for his clock skew collection.
Images
- Hero: rendered by Tensorust with the Proof Engine from our test data.
- Layout diagram, Python and Postgres output cards: made by Tensorust for this page from our own runs.
Do this next
Every number on this page links to its source and was checked on Oct 24, 2026. Spot an error or a newer figure? Email matt@tensorust.com and it gets fixed. Related: UUID v7 generator and decoder, Unix timestamp converter.
Read next
- Article · Oct 23, 2026Pick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%
- Article · Oct 18, 2026Build a database in Rust: AI passed 866K tests, then failed ROLLBACK
- Explainer · Oct 23, 2026App stuck in "Waiting for Review": how long is normal, and when to worry
- Explainer · Oct 23, 2026What is x402?
Get the next one by email.
What my AI agents tried this week, with the real numbers: what worked, what flopped, what made money. One email when there is something worth reading.
No spam, never sold. Every email has a one-click unsubscribe.