About Snowflake ID Parser
A Snowflake ID is a 64-bit integer with structure: a timestamp in the high bits, then a machine or shard identifier, then a per-millisecond sequence counter. Twitter introduced the format so that many machines could mint sortable, unique IDs with no coordination, and Discord, Instagram and several others adopted variants of it.
The layout is not universal, which is the first thing to establish when parsing one. Twitter's original used 41 bits of milliseconds since a custom epoch (1288834974657, or 4 November 2010), 10 bits of machine ID and 12 bits of sequence. Discord uses the same bit split with its own epoch of 1420070400000 (1 January 2015). Instagram used a different arrangement entirely, with a shard ID and fewer sequence bits. Parsing with the wrong epoch gives a timestamp that is years out.
The custom epoch exists to buy headroom: 41 bits of milliseconds is about 69 years, so starting the count in 2010 rather than 1970 pushes the exhaustion date to 2079 instead of 2039. It also means a raw Snowflake timestamp is meaningless without knowing which epoch to add back.
The practical hazard with Snowflake IDs has nothing to do with parsing: they exceed 2^53, so any JavaScript code that reads one as a JSON number silently loses its low digits. This is why Twitter's API returns both id and id_str, and why Discord IDs are strings in every payload. If you are handling them in JavaScript, keep them as strings or BigInt from the moment they arrive.
Parsing happens in your browser and nothing is transmitted.
How to use the Snowflake ID Parser
- Paste the Snowflake ID - as a string, since large ones lose precision as a number.
- Pick the epoch: Twitter (1288834974657), Discord (1420070400000), or a custom one for your own generator.
- Read the decoded timestamp, machine or shard ID, and sequence number.
- If the timestamp is decades out, the epoch is wrong - that is almost always the explanation.
Examples
-
Snowflake ID
1749450000000000000
Snowflake ID Parser in code
The same operation this tool performs, in the languages you are most likely to need it.
const DISCORD_EPOCH = 1420070400000n; // 2015-01-01
const TWITTER_EPOCH = 1288834974657n; // 2010-11-04
function parseSnowflake(id, epoch = DISCORD_EPOCH) {
const n = BigInt(id); // MUST be BigInt, see below
return {
timestamp: new Date(Number((n >> 22n) + epoch)),
workerId: Number((n >> 17n) & 0x1fn),
processId: Number((n >> 12n) & 0x1fn),
sequence: Number(n & 0xfffn),
};
}
// Why BigInt is mandatory:
Number("1234567890123456789"); // 1234567890123456800 <- corrupted
BigInt("1234567890123456789"); // exact
// So JSON.parse alone is unsafe for a payload containing Snowflakes:
JSON.parse('{"id":1234567890123456789}').id; // already wrong
// The API should send it as a string: {"id":"1234567890123456789"}
from datetime import datetime, timezone
DISCORD_EPOCH = 1420070400000
def parse_snowflake(sid: int, epoch: int = DISCORD_EPOCH) -> dict:
return {
"timestamp": datetime.fromtimestamp(((sid >> 22) + epoch) / 1000,
tz=timezone.utc),
"worker_id": (sid >> 17) & 0x1F,
"process_id": (sid >> 12) & 0x1F,
"sequence": sid & 0xFFF,
}
# Python integers are arbitrary precision, so no precision loss -
# this is purely a JavaScript problem.
print(parse_snowflake(175928847299117063))
-- Because the timestamp is in the high bits, a Snowflake range
-- query is also a time range query - no separate index needed.
-- Discord epoch:
SELECT ((1420070400000 + (id >> 22)) / 1000) AS created_epoch
FROM messages;
-- Everything after a given instant
SELECT * FROM messages
WHERE id > ((unix_timestamp('2026-08-01') * 1000 - 1420070400000) << 22);
-- Store as BIGINT, never as a floating-point type - a DOUBLE
-- cannot hold a Snowflake exactly.
When you need this
- Working out when a Discord message or Twitter post was created from its ID alone.
- Debugging a duplicate-ID report by checking which worker produced them.
- Confirming a JavaScript client has not corrupted an ID by reading it as a number.
- Building a time-range query against a Snowflake-keyed table.
Common problems and what causes them
- Precision loss in JavaScript
- Snowflakes exceed 2^53, so reading one as a JSON number silently changes its low digits - and the value still looks plausible. Keep them as strings or BigInt from the moment they arrive. This is why Twitter's API has an id_str field and Discord sends IDs as strings.
- The wrong epoch
- Twitter's epoch is 1288834974657 and Discord's is 1420070400000. Using one for the other puts the decoded timestamp several years out. A timestamp in 1970 usually means no epoch was added at all.
- Assuming a universal bit layout
- Twitter used 41/10/12 bits for timestamp/machine/sequence; Instagram used a different split with a shard ID. Confirm the layout for the system that generated the ID before trusting the decoded fields.
- Treating a Snowflake as a secret
- The timestamp is recoverable by anyone holding the ID, and sequential IDs reveal creation rate and volume. That is often fine, and occasionally an information leak worth avoiding.
- Clock skew and duplicate IDs
- A generator whose clock moves backwards can reissue IDs it has already produced. Real implementations refuse to generate while the clock is behind their last-seen timestamp, which is why a Snowflake service can stall after an NTP correction.
- Sequence exhaustion
- 12 bits gives 4,096 IDs per millisecond per worker. Beyond that the generator must wait for the next millisecond, which shows up as latency spikes under extreme load rather than as duplicate IDs.
FAQ
- How do I get the creation time from a Snowflake ID?
- Shift right by 22 bits to isolate the millisecond timestamp, then add the generator's custom epoch - 1420070400000 for Discord, 1288834974657 for Twitter. Without adding the epoch you get a date in 1970.
- Why does my Snowflake ID change value in JavaScript?
- It exceeds 2^53, the largest integer a double can represent exactly, so JSON.parse rounds it. The corrupted value still looks like a valid ID, which makes it a nasty bug. Use strings or BigInt end to end.
- Are Snowflake IDs sortable?
- Yes - the timestamp occupies the high bits, so numeric order is chronological order. That is the main reason for the design, and it means an ID range query doubles as a time range query.
- What is the difference between Twitter and Discord Snowflakes?
- The same 41/10/12 bit layout but different epochs: Twitter starts at 4 November 2010, Discord at 1 January 2015. Parse with the wrong one and the timestamp is several years off.
- When do Snowflake IDs run out?
- 41 bits of milliseconds is about 69 years from the custom epoch - 2079 for Twitter, 2084 for Discord. The more immediate limit is 4,096 IDs per millisecond per worker.
Related reading
- UUID vs auto-increment keys
- Timestamp converter
- ULID generator similar idea, 128 bits