snowflake

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 12, 2025 License: MIT Imports: 5 Imported by: 0

README

Discord Snowflake

A high-performance, zero-dependency Go library for working with Discord snowflake IDs.

Go Reference Go Report Card Coverage

Installation

go get github.com/goland-express/snowflake

Quick Start

package main

import (
    "fmt"
    "time"
    "github.com/goland-express/snowflake"
)

func main() {
    // Parse a snowflake from string
    id, _ := snowflake.Parse("175928847299117063")
    
    // Extract timestamp
    fmt.Println("Created at:", id.Time())
    // Output: Created at: 2016-04-30 11:18:25.796 -0300 -03
    
    // Get components
    fmt.Println("Worker ID:", id.WorkerID())
    fmt.Println("Process ID:", id.ProcessID())
    fmt.Println("Sequence:", id.Sequence())
    
    // Create a new snowflake
    newID := snowflake.NewFromTimestamp(time.Now())
    
    // Compare snowflakes
    if id.Before(newID) {
        fmt.Println("id was created before newID")
    }
}

API Overview

Parsing
// Parse from string
id, err := snowflake.Parse("175928847299117063")

// Parse with validation (checks if within Discord's valid range)
id, err := snowflake.ParseStrict("175928847299117063")

// Parse and panic on error
id := snowflake.MustParse("175928847299117063")

// Parse from environment variable
id := snowflake.Getenv("DISCORD_CHANNEL_ID")
id, found := snowflake.LookupEnv("DISCORD_CHANNEL_ID")
Creating
// Create from timestamp (worker, process, sequence = 0)
id := snowflake.NewFromTimestamp(time.Now())

// Create with all components
id := snowflake.New(time.Now(), workerID, processID, sequence)

// Create a range for time-based queries
minID, maxID := snowflake.NewRange(startTime, endTime)
// Use in SQL: WHERE id BETWEEN minID AND maxID
Extracting Information
// Get timestamp
timestamp := id.Time()

// Get individual components
workerID := id.WorkerID()    // 5 bits
processID := id.ProcessID()  // 5 bits
sequence := id.Sequence()    // 12 bits

// Get all at once
deconstructed := id.Deconstruct()
fmt.Printf("Time: %v, Worker: %d, Process: %d, Seq: %d\n",
    deconstructed.Time,
    deconstructed.WorkerID,
    deconstructed.ProcessID,
    deconstructed.Sequence)
Validation
// Check if zero
if id.IsZero() {
    fmt.Println("Empty snowflake")
}

// Check if non-zero
if id.Valid() {
    fmt.Println("Valid snowflake")
}

// Check if within Discord's theoretical valid range
if id.IsValid() {
    fmt.Println("Valid Discord snowflake")
}
Comparison
id1 := snowflake.ID(100)
id2 := snowflake.ID(200)

// Compare
if id1.Before(id2) { }
if id1.After(id2) { }
if id1.Equal(id2) { }

// Three-way comparison (-1, 0, 1)
result := id1.Compare(id2)

// Check if in range (inclusive)
if id.IsBetween(minID, maxID) { }
JSON
type Message struct {
    ID      snowflake.ID `json:"id"`
    Content string       `json:"content"`
}

// Marshals as string: {"id":"175928847299117063"}
msg := Message{ID: 175928847299117063, Content: "Hello"}
json.Marshal(msg)

// Unmarshals from string or number
json.Unmarshal([]byte(`{"id":"175928847299117063"}`), &msg)
json.Unmarshal([]byte(`{"id":175928847299117063}`), &msg)

// Allow unquoted numbers (off by default)
snowflake.AllowUnquoted = true
String Conversion
// Convert to string
str := id.String()

// Implements fmt.Stringer
fmt.Printf("ID: %s\n", id)

Constants

snowflake.Epoch                  // Discord epoch (1420070400000)
snowflake.MinID                  // Minimum possible ID (0)
snowflake.MaxID                  // Maximum possible ID
snowflake.FirstDiscordSnowflake  // First real Discord snowflake (175928847299117063)

Snowflake Format

Discord snowflakes are 64-bit unsigned integers with the following structure:

 64                                          22     17     12          0
 ┌───────────────────────────────────────────┬──────┬──────┬──────────┐
 │         Timestamp (42 bits)               │Worker│Process│Increment│
 │    Milliseconds since Discord Epoch       │ (5)  │ (5)  │  (12)    │
 └───────────────────────────────────────────┴──────┴──────┴──────────┘
  • Timestamp (42 bits): Milliseconds since Discord Epoch (January 1, 2015)
  • Worker ID (5 bits): Internal worker ID (0-31)
  • Process ID (5 bits): Internal process ID (0-31)
  • Increment (12 bits): Sequence number (0-4095) for IDs generated in the same millisecond

Performance

Benchmarks:

BenchmarkParse-12              100000000    11.72 ns/op    0 B/op    0 allocs/op
BenchmarkNew-12               1000000000     0.24 ns/op    0 B/op    0 allocs/op
BenchmarkString-12              61432237    19.20 ns/op    0 B/op    0 allocs/op
BenchmarkTime-12              1000000000     0.24 ns/op    0 B/op    0 allocs/op
BenchmarkMarshalJSON-12         58816952    20.28 ns/op    0 B/op    0 allocs/op
BenchmarkUnmarshalJSON-12       44850418    26.43 ns/op    0 B/op    0 allocs/op
BenchmarkComparison-12        1000000000     0.24 ns/op    0 B/op    0 allocs/op

Use Cases

Time-based Queries
// Get all messages from yesterday
start := time.Now().Add(-24 * time.Hour)
end := time.Now()
minID, maxID := snowflake.NewRange(start, end)

// SQL: SELECT * FROM messages WHERE id BETWEEN ? AND ?
db.Query("SELECT * FROM messages WHERE id BETWEEN ? AND ?", minID, maxID)
Sorting
// Sort messages by snowflake (chronologically)
sort.Slice(messages, func(i, j int) bool {
    return messages[i].ID.Before(messages[j].ID)
})
Validation
// Validate user input
id, err := snowflake.ParseStrict(userInput)
if err != nil {
    return fmt.Errorf("invalid Discord ID: %w", err)
}

Contributing

Pull requests are welcome. For major changes, open an issue first to discuss what you want to change.

License

MIT

Documentation

Index

Constants

View Source
const (
	// Epoch is the custom epoch time (Unix time in milliseconds) used as the starting point
	// for the timestamp component of the snowflake ID (January 1, 2015 00:00:00.000 UTC).
	Epoch = 1420070400000
	// TimestampShift is the number of bits to shift the timestamp component.
	TimestampShift = 22
	// WorkerIDShift is the number of bits to shift the worker ID component.
	WorkerIDShift = 17
	// ProcessIDShift is the number of bits to shift the process ID component.
	ProcessIDShift = 12
	// WorkerIDMask is the bitmask for the 5-bit worker ID component.
	WorkerIDMask = 0x1F
	// ProcessIDMask is the bitmask for the 5-bit process ID component.
	ProcessIDMask = 0x1F
	// SequenceMask is the bitmask for the 12-bit sequence component.
	SequenceMask = 0xFFF
)
View Source
const (
	// MinID is the smallest possible snowflake ID (0).
	MinID = ID(0)
	// MaxID is the largest possible snowflake ID.
	MaxID = ID(^uint64(0))
	// FirstDiscordSnowflake is the ID of the first-ever Discord snowflake.
	FirstDiscordSnowflake = ID(175928847299117063)
)

Variables

View Source
var (
	// ErrInvalidSnowflake is returned when a string contains invalid characters during parsing.
	ErrInvalidSnowflake = errors.New("invalid snowflake")
	// ErrOutOfRange is returned when a parsed snowflake ID is outside the valid time range.
	ErrOutOfRange = errors.New("snowflake out of valid range")
	// AllowUnquoted determines if JSON unmarshaling should allow unquoted numeric strings.
	AllowUnquoted = false
)

Functions

This section is empty.

Types

type DeconstructedSnowflake

type DeconstructedSnowflake struct {
	Time      time.Time
	WorkerID  uint8
	ProcessID uint8
	Sequence  uint16
}

DeconstructedSnowflake contains the properties used by Discord for each snowflake ID.

type ID

type ID uint64

ID is a 64-bit unsigned integer representing a unique snowflake identifier.

func Getenv

func Getenv(key string) ID

Getenv returns the value of the environment variable named by the key and parses it as a snowflake. If the key is not found or the value is unparsable, it returns ID(0).

func LookupEnv

func LookupEnv(key string) (ID, bool)

LookupEnv returns the value of the environment variable named by the key and parses it as a snowflake. It returns the ID and a boolean indicating if the key was found in the environment.

func MustParse

func MustParse(str string) ID

MustParse parses a string into a snowflake ID and panics on error.

func New

func New(timestamp time.Time, workerID, processID uint8, sequence uint16) ID

New creates a new snowflake ID from all components: timestamp, worker ID, process ID, and sequence.

func NewFromTimestamp

func NewFromTimestamp(timestamp time.Time) ID

NewFromTimestamp creates a new snowflake ID from timestamp with worker, process, and sequence set to 0.

func NewRange

func NewRange(start, end time.Time) (min, max ID)

NewRange creates min and max snowflake IDs for a time range (inclusive). The max ID is generated by setting the worker, process, and sequence bits to their maximum value (0x3FFFFF).

func Parse

func Parse(str string) (ID, error)

Parse converts a string representation of a snowflake ID into an ID type. It accepts "null" as ID(0).

func ParseStrict

func ParseStrict(str string) (ID, error)

ParseStrict parses and validates a snowflake is within Discord's valid range. It returns ErrOutOfRange if the ID's time component is outside expected boundaries.

func (ID) After

func (id ID) After(other ID) bool

After returns true if this snowflake ID was created after the other (i.e., numerically larger).

func (ID) Before

func (id ID) Before(other ID) bool

Before returns true if this snowflake ID was created before the other (i.e., numerically smaller).

func (ID) Compare

func (id ID) Compare(other ID) int

Compare returns -1 if id < other, 0 if id == other, 1 if id > other.

func (ID) Deconstruct

func (id ID) Deconstruct() DeconstructedSnowflake

Deconstruct returns DeconstructedSnowflake with all components of the ID.

func (ID) Equal

func (id ID) Equal(other ID) bool

Equal returns true if both snowflakes are equal.

func (ID) IsBetween

func (id ID) IsBetween(start, end ID) bool

IsBetween returns true if the snowflake is between start and end (inclusive).

func (ID) IsValid

func (id ID) IsValid() bool

IsValid returns true if the snowflake is within Discord's theoretical valid range. The range check ensures the ID is not before the Epoch and not unrealistically far into the future (24 hours from now).

func (ID) IsZero

func (id ID) IsZero() bool

IsZero returns true if the snowflake is ID(0).

func (ID) MarshalJSON

func (id ID) MarshalJSON() ([]byte, error)

MarshalJSON marshals the snowflake ID into a JSON string. Performance (~20ns, 1 heap allocation for the return slice). Note: Benchmarks may show 0 allocs due to compiler optimizations when the result is discarded, but real-world usage will have 1 allocation for the returned []byte.

func (ID) ProcessID

func (id ID) ProcessID() uint8

ProcessID returns the id of the process the snowflake was created on, using a 5-bit mask.

func (ID) Sequence

func (id ID) Sequence() uint16

Sequence returns the sequence number of the snowflake, using a 12-bit mask.

func (ID) String

func (id ID) String() string

String returns a string representation of the snowflake ID.

func (ID) Time

func (id ID) Time() time.Time

Time returns the time.Time the snowflake was created by extracting the timestamp component. It returns an empty time.Time{} for ID(0).

func (*ID) UnmarshalJSON

func (id *ID) UnmarshalJSON(data []byte) error

UnmarshalJSON unmarshals the snowflake ID from a JSON string. It handles quoted strings and optionally unquoted strings if AllowUnquoted is true. It unmarshals "null" or "0" as ID(0).

func (ID) Valid

func (id ID) Valid() bool

Valid returns true if the snowflake is greater than 0.

func (ID) WorkerID

func (id ID) WorkerID() uint8

WorkerID returns the id of the worker the snowflake was created on, using a 5-bit mask.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL