FormatterHQ

Identifiers

Snowflake IDs Explained: How to Decode the Timestamp

How Snowflake IDs pack a millisecond timestamp, machine ID and sequence into 64 bits, and how to decode them by hand or in code without losing precision.

FormatterHQ Team Published Updated 6 min read

A Snowflake ID looks like an ordinary large integer, but it is really a packed record: most of its bits are a millisecond timestamp, and the rest identify the machine that created it and a per-millisecond counter. Once you know the layout, you can decode a Snowflake ID timestamp with two bit operations and an addition. This guide explains the 64-bit structure, the epochs used by Twitter/X and Discord, and how to do the decoding yourself in JavaScript, Python, PHP and SQL. If you only need the answer for a specific ID, paste it into the Snowflake ID decoder.

The 64-bit layout of a Snowflake ID

Twitter introduced the scheme so that many servers could create unique, roughly time-ordered IDs without coordinating with each other. The classic layout, read from the most significant bit down, is:

BitsFieldPurpose
1Sign bitAlways 0, so the ID stays positive in a signed 64-bit integer
41TimestampMilliseconds elapsed since a custom epoch
10Machine IDTwitter splits it into 5 bits of datacenter and 5 bits of worker
12SequenceCounter for IDs created by the same worker in the same millisecond

Because the timestamp occupies the high bits, shifting the ID right by 22 places (10 + 12) removes everything except the time. The remaining fields are recovered by shifting and masking with 0x1F (5 bits) or 0xFFF (12 bits).

The 12-bit sequence allows 4,096 IDs per worker per millisecond. When a worker runs out, a typical generator waits for the next millisecond before issuing more.

Epochs: why the same number means different dates

The timestamp is not counted from the Unix epoch (1970) but from an epoch chosen by each system, which buys decades of extra range. The two most common public layouts are:

PlatformEpoch (Unix ms)Epoch (UTC)Low 22 bits
Twitter / X12888349746572010-11-04 01:42:54.6575 datacenter, 5 worker, 12 sequence
Discord14200704000002015-01-01 00:00:005 internal worker, 5 internal process, 12 increment

Discord documents its format in the Discord developer reference. Decoding an ID with the wrong epoch still produces a plausible-looking date, just years off, so always confirm where an ID came from. The decoder offers presets for both platforms, several others, and a custom epoch for your own services.

How to decode a Snowflake ID timestamp by hand

Take 175928847299117063, the example ID from Discord's documentation. Decoding it takes four steps:

  1. Shift right 22 bits: 175928847299117063 >> 22 = 41944705796. This is milliseconds since the Discord epoch.
  2. Add the epoch: 41944705796 + 1420070400000 = 1462015105796, a normal Unix timestamp in milliseconds.
  3. Convert to a date: 1462015105796 ms is 2016-04-30T11:18:25.796Z.
  4. Extract the other fields: worker (id >> 17) & 0x1F = 1, process (id >> 12) & 0x1F = 0, increment id & 0xFFF = 7.

The same ID in the Snowflake decoder with the Discord preset produces this output, including the full binary so you can see where each field starts:

# Layout: Discord, epoch 2015-01-01T00:00:00.000Z

175928847299117063
  Timestamp:        2016-04-30T11:18:25.796Z  (10.4 years ago)
  Unix ms:          1462015105796
  Internal worker:  1
  Internal process: 0
  Increment:        7
  Binary:           00000010 01110001 00000110 01011010 11000001 00000010 00000000 00000111

The relative age in brackets depends on the day you run it. To view the Unix milliseconds in another time zone, use the Unix timestamp converter; to experiment with the shifts and masks, try the bitwise calculator or view the ID in base 2 with the decimal to binary converter.

Decoding Snowflakes in code

JavaScript (BigInt)

const DISCORD_EPOCH = 1420070400000n;
function decodeSnowflake(idString, epoch = DISCORD_EPOCH) {
  const id = BigInt(idString);
  const ms = (id >> 22n) + epoch;
  return {
    date: new Date(Number(ms)).toISOString(),
    worker: Number((id >> 17n) & 0x1Fn),
    process: Number((id >> 12n) & 0x1Fn),
    sequence: Number(id & 0xFFFn),
  };
}
decodeSnowflake('175928847299117063');
// { date: '2016-04-30T11:18:25.796Z', worker: 1, process: 0, sequence: 7 }

Python

from datetime import datetime, timezone
TWITTER_EPOCH = 1288834974657
def decode(snowflake, epoch=TWITTER_EPOCH):
    sid = int(snowflake)
    ms = (sid >> 22) + epoch
    return {
        'time': datetime.fromtimestamp(ms / 1000, tz=timezone.utc).isoformat(timespec='milliseconds'),
        'datacenter': (sid >> 17) & 0x1F,
        'worker': (sid >> 12) & 0x1F,
        'sequence': sid & 0xFFF,
    }
decode('1541815603606036480')
# {'time': '2022-06-28T16:07:40.105+00:00', 'datacenter': 11, 'worker': 26, 'sequence': 0}

Python integers have arbitrary precision, so no special type is needed.

PHP and PostgreSQL

On 64-bit PHP builds, integers are signed 64-bit, which holds any Snowflake whose sign bit is 0:

$id = (int) '175928847299117063';
$ms = ($id >> 22) + 1420070400000;
$dt = DateTimeImmutable::createFromFormat('U.v', sprintf('%d.%03d', intdiv($ms, 1000), $ms % 1000));
echo $dt->format('Y-m-d\TH:i:s.v\Z'); // 2016-04-30T11:18:25.796Z

In PostgreSQL, a bigint column can be decoded directly in a query:

SELECT to_timestamp(((175928847299117063::bigint >> 22) + 1420070400000) / 1000.0) AT TIME ZONE 'UTC';
-- 2016-04-30 11:18:25.796

Why JavaScript numbers corrupt Snowflake IDs

A JavaScript Number is a 64-bit float with a 53-bit mantissa, so integers are exact only up to Number.MAX_SAFE_INTEGER (9007199254740991, which is 2^53 − 1). Snowflakes created more than about 25 days after their epoch are larger than that, and JSON.parse silently rounds them:

JSON.parse('{"id": 175928847299117063}').id
// 175928847299117060  (the last digits changed)

The rounded value points to a different ID, which leads to lookups that fail for no obvious reason. The fixes are simple:

  • Transport IDs as strings. Twitter's API added id_str for this reason, and Discord's API returns snowflakes as strings.
  • Convert with BigInt(idString) only when you need arithmetic, as in the example above.
  • Store IDs in a BIGINT database column, never in a floating-point or double column.

Custom epochs: decoding IDs from your own service

Many in-house ID generators copy the Twitter layout but pick their own epoch, often the date the project launched. To decode such IDs, choose the Custom epoch option and enter the epoch in Unix milliseconds. With an epoch of 1704067200000 (2024-01-01 UTC), the tool gives:

# Layout: Twitter / X (custom epoch), epoch 2024-01-01T00:00:00.000Z

158868548466192689
  Timestamp:  2025-03-14T09:26:53.589Z  (1.6 years ago)
  Unix ms:    1741944413589
  Datacenter: 7
  Worker:     19
  Sequence:   305
  Binary:     00000010 00110100 01101010 00011010 01100101 01001111 00110001 00110001

The custom option assumes the classic 41/5/5/12 split. If your generator allocates the low 22 bits differently, the timestamp is still correct, but you will need to re-slice the lower fields yourself.

Ordering, the 69-year limit and clock skew

Snowflakes sort roughly by creation time, which makes them good primary keys for feeds and message logs. A few properties are worth knowing:

  • Range queries by time: the smallest possible ID for a moment is (ms − epoch) << 22. For Discord at 2016-05-01 00:00 UTC that is 176120502681600000, so id >= 176120502681600000 selects everything from that day onward.
  • Ordering is only approximate across machines: two IDs from the same millisecond on different workers sort by their worker bits, not by which was actually created first.
  • The range is finite: 41 bits of milliseconds cover about 69.7 years. Twitter's IDs run out in July 2080 and Discord's in September 2084, after which the timestamp would spill into the sign bit. The decoder reports a number with that bit set (anything above 9223372036854775807) as not a Snowflake.
  • Clock skew matters: if a server's clock jumps backwards, it could reissue a timestamp it has already used. Well-behaved generators refuse to issue IDs or wait until the clock catches up. A decoded date is only as accurate as the generating machine's clock.

Key takeaways

To decode a Snowflake, shift right by 22 bits, add the platform's epoch and read the result as Unix milliseconds. Keep IDs as strings or BigInt in JavaScript, and double-check the epoch before trusting a date. For identifiers that are 128-bit and text-based instead of 64-bit integers, see the companion guide ULID vs UUID and the ULID decoder.

  • snowflake id
  • distributed ids
  • discord
  • twitter
  • bigint

Frequently asked questions

How do I get the date from a Discord or Twitter ID?
Shift the ID right by 22 bits, add the platform's epoch in milliseconds (1420070400000 for Discord, 1288834974657 for Twitter/X) and treat the result as a Unix timestamp in milliseconds. The Snowflake ID decoder does this for you with the matching preset.
Why does my Snowflake ID end in different digits after JSON parsing?
JavaScript numbers only hold integers exactly up to 2^53 − 1, and most Snowflakes are larger, so JSON.parse rounds them. Keep IDs as strings and convert with BigInt when you need to do arithmetic.
Are Snowflake IDs strictly ordered by time?
Only roughly. IDs from one worker increase over time, but IDs created in the same millisecond on different workers are ordered by their worker bits. Clock differences between servers can also cause small ordering inversions.
Can I find out which server created a Snowflake?
The machine bits tell you the datacenter, worker or process number that the generator was configured with. They are internal numbers, so they do not reveal a hostname or location unless you know how that system assigns them.

Tools used in this guide