Email me

Guide · 7 min read

Filed under Rust App development Guides

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.

Glowing bar chart titled How far a UUIDv7's time can be off, in minutes between the time inside the ID and the real clock: Python standard library after the clock steps back 78, Rust uuid, npm uuid and Go after the clock steps back 60, uuid6 on PyPI with a healthy clock 16.6, LiosK libraries 0, .NET CreateVersion7 0.
The decode is always the same 48 bits. Whether those bits are the real time depends on the library that made the ID. Data: our tests of 10 libraries, Oct 23 2026. Rendered by Tensorust with the Proof Engine from our own test data.

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
  1. Key numbers
  2. Check an ID right here
  3. Before you start: the time inside is a claim, not a record
  4. Confirm it is a version 7 UUID
  5. Read the first 12 hex digits as milliseconds
  6. Decode it in Python
  7. Decode it in JavaScript or Rust
  8. Decode it in PostgreSQL
  9. Sanity-check the result before you trust it
  10. What goes wrong
  11. Related
  12. 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

Diagram of a real UUIDv7, 01a16c37-766b-72b0-b69a-ea45923ae7ca, with the first 12 hex digits 01a16c37766b highlighted as 48 bits of unix_ts_ms equal to 1,792,816,936,555 milliseconds since 1970, which is 2026-10-24 04:42:16.555 UTC; the version digit 7 highlighted; and the remaining 74 bits labelled as variant, random bits and an optional counter with no time in them.
One real ID, made by Python 3.14 on Oct 24 2026. Only the yellow part is time. Diagram: Tensorust.

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:

Terminal output of our Python 3.14 script: the standard library ID decodes to 2026-10-24 04:42:16.555, matching the clock; uuid6 decodes to 04:45:36.623, 199.7 seconds ahead after 200,000 IDs; the PyPI package uuid7 0.1.0 decodes to 2202-09-14; uuid-utils decodes to 04:42:16.884, matching the clock.
Same decoder, four libraries, three kinds of answer. Our run, Oct 24 2026, output typeset. Credit: Tensorust.

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):

Terminal output from PostgreSQL 18.6: uuid_extract_timestamp(uuidv7()) returns 2026-10-24 04:42:17.865+00; for gen_random_uuid(), a v4, it returns NULL; for uuidv7(interval '-1 hour') it returns 03:42:17.866, one hour earlier; for the PyPI uuid7 package ID it returns 2202-09-14 19:06:01.396; the bit(48) cast trick returns the same 2202 value.
Postgres 18 decodes correctly, including IDs that were backdated on purpose or made by a pre-standard library. Our run, output typeset. Credit: Tensorust.
-- 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.

Troubleshooting

What goes wrong

  • Wrong digit count. Slice 12 hex digits after removing dashes, not the first 13 characters of the string; 01a16c37-766b is 13 characters with the dash.
  • Seconds vs milliseconds. JavaScript's Date wants milliseconds; Python's fromtimestamp and Postgres to_timestamp want seconds, so divide by 1,000.
  • Bytes from .NET. Guid.ToByteArray() writes the first 8 bytes little-endian. Decode from the string form, or use ToByteArray(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.

Sources

All checked October 24, 2026.

Primary

Our tests

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

Back to The Manual →   All articles →

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.

Up next

The article this answer comes fromPick a UUIDv7 library that keeps order: 10 tested, .NET fails 50%We tested 10 UUIDv7 libraries with 1M IDs each and a clock jump. .NET put 50% out of order, uuid6 ran 16.6 min ahead. The 2 that passed, plus free data.Read it next →

Get the receipts 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.

Did this answer your question?

Popular in the shop

All 124 tools →

Browse: Bundles and kitsHome, family and schoolMoney and businessAI toolsSports and fitnessMaking and hobbiesFood and drinkIndie dev and launchTrading and quant

Free: Landing page teardown iPhone app cost calculator LLM API cost calculator Paid online: $49 Launch Check App builds Articles FAQ