2019-05-31 11:07:56 +02:00
|
|
|
// Package sid provides the ability to generate Sortable Identifiers. These are also universally unique.
|
|
|
|
//
|
|
|
|
// id1 := sid.Id()
|
|
|
|
// id2 := sid.Id()
|
|
|
|
// fmt.Printf("id1 = %s\n", id1)
|
|
|
|
// fmt.Printf("id2 = %s\n", id2)
|
|
|
|
// // -> "id1 = 1IeSBAWW83j-2wgJ4PUtlAr"
|
|
|
|
// // -> "id2 = 1IeSBAWW9kK-0cDG64GQgGJ"
|
|
|
|
//
|
|
|
|
// This package is simple and only provides one function. The aim here is not pure speed, it is for an easy use-case
|
|
|
|
// without having to worry about goroutines and locking.
|
|
|
|
package sid
|
|
|
|
|
|
|
|
import (
|
2019-08-30 09:55:06 +02:00
|
|
|
"fmt"
|
2019-05-31 11:07:56 +02:00
|
|
|
"math/rand"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"sync"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
rand.Seed(time.Now().UTC().UnixNano())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Remember the lastTime so that if (by chance) we get the same NanoSecond, we just incrememt the last random number.
|
|
|
|
var mu = &sync.Mutex{}
|
|
|
|
var lastTime int64
|
|
|
|
var lastRand int64
|
|
|
|
var chars = make([]string, 11, 11)
|
|
|
|
|
|
|
|
// 64 chars but ordered by ASCII
|
|
|
|
const base64 string = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~"
|
|
|
|
|
2019-08-30 09:55:06 +02:00
|
|
|
func toStr(n int64) string {
|
2019-05-31 11:07:56 +02:00
|
|
|
// now do the generation (backwards, so we just %64 then /64 along the way)
|
|
|
|
for i := 10; i >= 0; i-- {
|
2019-08-30 09:55:06 +02:00
|
|
|
index := n % 64
|
2019-05-31 11:07:56 +02:00
|
|
|
chars[i] = string(base64[index])
|
2019-08-30 09:55:06 +02:00
|
|
|
n = n / 64
|
2019-05-31 11:07:56 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return strings.Join(chars, "")
|
|
|
|
}
|
|
|
|
|
2019-08-30 09:55:06 +02:00
|
|
|
func toBase32(n int64) string {
|
|
|
|
b32 := strconv.FormatInt(n, 32)
|
|
|
|
|
|
|
|
for len(b32) < 13 {
|
|
|
|
b32 = "0" + b32
|
|
|
|
}
|
|
|
|
|
|
|
|
// log.Printf("b32=%s\n", b32)
|
|
|
|
|
|
|
|
return b32
|
|
|
|
}
|
|
|
|
|
|
|
|
func toHex(n int64) string {
|
|
|
|
hex := fmt.Sprintf("%x", n)
|
|
|
|
|
|
|
|
for len(hex) < 16 {
|
|
|
|
hex = "0" + hex
|
|
|
|
}
|
|
|
|
|
|
|
|
return hex
|
|
|
|
}
|
|
|
|
|
2019-05-31 11:07:56 +02:00
|
|
|
// IdBase64 returns a 23 char string based on timestamp and a random number. The format is "XXXXXXXXXXX-YYYYYYYYYYY"
|
|
|
|
// where X is the timestamp and Y is the random number. If (by any chance) this is called in the same nanosecond, the
|
|
|
|
// random number is incremented instead of a new one being generated. This makes sure that two consecutive Ids
|
|
|
|
// generated in the same goroutine also ensure those Ids are also sortable.
|
|
|
|
//
|
|
|
|
// It is safe to call from different goroutines since it has it's own locking.
|
|
|
|
func IdBase64() string {
|
|
|
|
// lock for lastTime, lastRand, and chars
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
|
|
|
now := time.Now().UTC().UnixNano()
|
|
|
|
var r int64
|
|
|
|
|
|
|
|
// if we have the same time, just inc lastRand, else create a new one
|
|
|
|
if now == lastTime {
|
|
|
|
lastRand++
|
|
|
|
} else {
|
|
|
|
lastRand = rand.Int63()
|
|
|
|
}
|
|
|
|
r = lastRand
|
|
|
|
|
|
|
|
// remember this for next time
|
|
|
|
lastTime = now
|
|
|
|
|
|
|
|
return toStr(now) + "-" + toStr(r)
|
|
|
|
}
|
|
|
|
|
2019-08-30 09:55:06 +02:00
|
|
|
// IdBase32 returns a 27 char string based on timestamp and a random number. The format is
|
|
|
|
// "XXXXXXXXXXXXX-YYYYYYYYYYYYY" where X is the timestamp and Y is the random number. If (by any chance) this is called
|
|
|
|
// in the same nanosecond, the random number is incremented instead of a new one being generated. This makes sure that
|
|
|
|
// two consecutive Ids generated in the same goroutine also ensure those Ids are also sortable.
|
|
|
|
//
|
|
|
|
// It is safe to call from different goroutines since it has it's own locking.
|
|
|
|
func IdBase32() string {
|
|
|
|
// lock for lastTime, lastRand, and chars
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
|
|
|
now := time.Now().UTC().UnixNano()
|
|
|
|
var r int64
|
|
|
|
|
|
|
|
// if we have the same time, just inc lastRand, else create a new one
|
|
|
|
if now == lastTime {
|
|
|
|
lastRand++
|
|
|
|
} else {
|
|
|
|
lastRand = rand.Int63()
|
|
|
|
}
|
|
|
|
r = lastRand
|
|
|
|
|
|
|
|
// remember this for next time
|
|
|
|
lastTime = now
|
|
|
|
|
|
|
|
return toBase32(now) + "-" + toBase32(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// IdHex returns a char string based on timestamp and a random number. The format is
|
|
|
|
// "XXXXXXXXXXXXXXXX-YYYYYYYYYYYYYYYY" where X is the timestamp and Y is the random number. If (by any chance) this is
|
|
|
|
// called in the same nanosecond, the random number is incremented instead of a new one being generated. This makes
|
|
|
|
// sure that two consecutive Ids generated in the same goroutine also ensure those Ids are also sortable.
|
|
|
|
//
|
|
|
|
// It is safe to call from different goroutines since it has it's own locking.
|
|
|
|
func IdHex() string {
|
|
|
|
// lock for lastTime, lastRand, and chars
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
|
|
|
now := time.Now().UTC().UnixNano()
|
|
|
|
var r int64
|
|
|
|
|
|
|
|
// if we have the same time, just inc lastRand, else create a new one
|
|
|
|
if now == lastTime {
|
|
|
|
lastRand++
|
|
|
|
} else {
|
|
|
|
lastRand = rand.Int63()
|
|
|
|
}
|
|
|
|
r = lastRand
|
|
|
|
|
|
|
|
// remember this for next time
|
|
|
|
lastTime = now
|
|
|
|
|
|
|
|
return toHex(now) + "-" + toHex(r)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Id returns a 39 char string based on timestamp and a random number. The format is
|
|
|
|
// "XXXXXXXXXXXXXXXXXXX-YYYYYYYYYYYYYYYYYYY" where X is the timestamp and Y is the random number. If (by any chance)
|
|
|
|
// this is called in the same nanosecond, the random number is incremented instead of a new one being generated. This
|
|
|
|
// makes sure that two consecutive Ids generated in the same goroutine also ensure those Ids are also sortable.
|
2019-05-31 11:07:56 +02:00
|
|
|
//
|
|
|
|
// It is safe to call from different goroutines since it has it's own locking.
|
|
|
|
func Id() string {
|
|
|
|
// lock for lastTime, lastRand, and chars
|
|
|
|
mu.Lock()
|
|
|
|
defer mu.Unlock()
|
|
|
|
|
|
|
|
now := time.Now().UTC().UnixNano()
|
|
|
|
var r int64
|
|
|
|
|
|
|
|
// if we have the same time, just inc lastRand, else create a new one
|
|
|
|
if now == lastTime {
|
|
|
|
lastRand++
|
|
|
|
} else {
|
|
|
|
lastRand = rand.Int63()
|
|
|
|
}
|
|
|
|
r = lastRand
|
|
|
|
|
|
|
|
// remember this for next time
|
|
|
|
lastTime = now
|
|
|
|
|
|
|
|
nowStr := strconv.FormatInt(now, 10)
|
|
|
|
rStr := strconv.FormatInt(r, 10)
|
|
|
|
|
|
|
|
for len(rStr) < 19 {
|
|
|
|
rStr = "0" + rStr
|
|
|
|
}
|
|
|
|
|
|
|
|
return nowStr + "-" + rStr
|
|
|
|
}
|