What are Discord Snowflakes?
Discord Snowflakes are a form of unique identifier used by Discord to track and reference various entities such as messages, users, and servers. The system is based on Twitter's Snowflake concept, which generates unique IDs that are also time sortable.
Snowflake Structure
The actual ID (or “snowflake”) is a 64-bit integer, and its structure is important. In the provided format:
- The first part is the time since the custom epoch, left-shifted 22 bits. This gives the IDs their time-sortable property.
- The next parts are the worker ID and process ID, which are also left-shifted by 17 and 12 bits, respectively.
- The last part is the increment for that millisecond.
Code
Python:
from dataclasses import dataclass
from datetime import datetime, timezone, timedelta
from typing import ClassVar, Tuple
from time import time
@dataclass
class SnowflakeGenerator:
EPOCH: ClassVar[datetime] = datetime(1970, 1, 1, tzinfo=timezone.utc) + timedelta(milliseconds=1420070400000)
worker_id: int = 1
process_id: int = 1
increment: int = 0
@classmethod
def generate_snowflake(cls) -> int:
now = int(time() * 1000)
elapsed = now - int(cls.EPOCH.timestamp() * 1000)
# Construct the snowflake ID
snowflake = (elapsed << 22) | (cls.worker_id << 17) | (cls.process_id << 12) | cls.increment
cls.increment = (cls.increment + 1) & 0xFFF # Increment the counter, and wrap it every 4096
return snowflake
@classmethod
def reverse_snowflake(cls, snowflake: int) -> Tuple[datetime, int, int, int]:
timestamp = (snowflake >> 22) + int(cls.EPOCH.timestamp() * 1000)
worker_id = (snowflake & 0x3E0000) >> 17
process_id = (snowflake & 0x1F000) >> 12
increment = snowflake & 0xFFF
return (datetime.fromtimestamp(timestamp / 1000, tz=timezone.utc), worker_id, process_id, increment)
Golang:
package main
import (
"fmt"
"time"
)
const (
discordEpoch int64 = 1420070400000 // Discord epoch (Jan 1, 2015) in milliseconds
workerIDShift = 17
processIDShift = 12
incrementMask = 0xFFF
)
var (
workerID int64 = 1
processID int64 = 1
increment int64 = 0
)
type SnowflakeInfo struct {
Time time.Time
WorkerID int64
ProcessID int64
Increment int64
}
func generateSnowflake() int64 {
now := time.Now().UnixNano() / int64(time.Millisecond)
elapsed := now - discordEpoch
snowflake := (elapsed << 22) | (workerID << workerIDShift) | (processID << processIDShift) | increment
increment = (increment + 1) & incrementMask
return snowflake
}
func reverseSnowflake(snowflake int64) SnowflakeInfo {
timestamp := (snowflake >> 22) + discordEpoch
workerID := (snowflake & (0x3E0000)) >> workerIDShift
processID := (snowflake & (0x1F000)) >> processIDShift
increment := snowflake & incrementMask
return SnowflakeInfo{
Time: time.Unix(timestamp/1000, (timestamp%1000)*int64(time.Millisecond)),
WorkerID: workerID,
ProcessID: processID,
Increment: increment,
}
}
func main() {
for i := 0; i < 100; i++ {
snowflakeID := generateSnowflake()
fmt.Printf("Generated Snowflake ID: %d\n", snowflakeID)
snowflakeDetails := reverseSnowflake(snowflakeID)
fmt.Printf("Details of Reversed Snowflake: Timestamp - %s, Worker ID - %d, Process ID - %d, Increment - %d\n",
snowflakeDetails.Time, snowflakeDetails.WorkerID, snowflakeDetails.ProcessID, snowflakeDetails.Increment)
time.Sleep(time.Millisecond)
}
}