Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ASCII ordered Base58Map to get sorted IDs and custom time input for NewNode #65

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 42 additions & 1 deletion snowflake.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const encodeBase32Map = "ybndrfg8ejkmcpqxot1uwisza345h769"

var decodeBase32Map [256]byte

const encodeBase58Map = "123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
const encodeBase58Map = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"

var decodeBase58Map [256]byte

Expand Down Expand Up @@ -131,6 +131,47 @@ func NewNode(node int64) (*Node, error) {
return &n, nil
}

func NewNodeWithStartTime(node int64, startTime time.Time) (*Node, error) {
if NodeBits+StepBits > 22 {
return nil, errors.New("Remember, you have a total 22 bits to share between Node/Step")
}

startUnixMilli := startTime.UnixMilli()
if startUnixMilli > time.Now().UnixMilli() {
return nil, errors.New("Start time cannot be greater than time.Now()")

}
startEpoch := startTime.UnixMilli()
Epoch = startEpoch
// re-calc in case custom NodeBits or StepBits were set
// DEPRECATED: the below block will be removed in a future release.
mu.Lock()
nodeMax = -1 ^ (-1 << NodeBits)
nodeMask = nodeMax << StepBits
stepMask = -1 ^ (-1 << StepBits)
timeShift = NodeBits + StepBits
nodeShift = StepBits
mu.Unlock()

n := Node{}
n.node = node
n.nodeMax = -1 ^ (-1 << NodeBits)
n.nodeMask = n.nodeMax << StepBits
n.stepMask = -1 ^ (-1 << StepBits)
n.timeShift = NodeBits + StepBits
n.nodeShift = StepBits

if n.node < 0 || n.node > n.nodeMax {
return nil, errors.New("Node number must be between 0 and " + strconv.FormatInt(n.nodeMax, 10))
}

var curTime = time.Now()
// add time.Duration to curTime to make sure we use the monotonic clock if available
n.epoch = curTime.Add(time.Unix(startEpoch/1000, (startEpoch%1000)*1000000).Sub(curTime))

return &n, nil
}

// Generate creates and returns a unique snowflake ID
// To help guarantee uniqueness
// - Make sure your system is keeping accurate system time
Expand Down
44 changes: 44 additions & 0 deletions snowflake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"reflect"
"testing"
"time"
)

//******************************************************************************
Expand All @@ -23,6 +24,23 @@ func TestNewNode(t *testing.T) {

}

func TestNewNodeWithStartTime(t *testing.T) {
_, err := NewNodeWithStartTime(0, time.Now().Add(-1*time.Hour)) // valid node and valid past time
if err != nil {
t.Fatalf("error creating NewNode, %s", err)
}

_, err = NewNodeWithStartTime(1025, time.Now().Add(-1*time.Hour)) // invalid node but valid past time
if err == nil {
t.Fatalf("no error creating NewNode, %s", err)
}

_, err = NewNodeWithStartTime(7, time.Now().Add(10*time.Hour)) // valid node but invalid future time
if err == nil {
t.Fatalf("no error creating NewNode, %s", err)
}
}

// lazy check if Generate will create duplicate IDs
// would be good to later enhance this with more smarts
func TestGenerateDuplicateID(t *testing.T) {
Expand Down Expand Up @@ -320,6 +338,32 @@ func TestIntBytes(t *testing.T) {

}

func TestID_Time(t *testing.T) {
node, err := NewNodeWithStartTime(1000, time.Now().Add(-1*24*2000*time.Hour))
if err != nil {
t.Fatalf("error creating NewNode, %s", err)
}

timeForID, err := time.Parse(time.RFC3339, time.Now().Format(time.RFC3339))
int64Id := node.Generate().Int64()
snowFlakeID := ID(int64Id)
if timeForID.UnixMilli() > snowFlakeID.Time() { // timeForId is generated first so should be less than int64Id
t.Fatalf("error matching time, %s", err)
}

node, err = NewNodeWithStartTime(1000, time.Now().Add(-1*time.Hour))
if err != nil {
t.Fatalf("error creating NewNode, %s", err)
}

int64Id = node.Generate().Int64()
snowFlakeID = ID(int64Id)
if int64Id < snowFlakeID.Time() {
t.Fatalf("error matching time, %s", err)
}

}

//******************************************************************************
// Marshall Test Methods

Expand Down