···11package syntax
2233import (
44+ "encoding/base32"
45 "fmt"
56 "regexp"
77+ "strings"
88+ "sync"
99+ "time"
610)
1111+1212+const (
1313+ Base32SortAlphabet = "234567abcdefghijklmnopqrstuvwxyz"
1414+)
1515+1616+func Base32Sort() *base32.Encoding {
1717+ return base32.NewEncoding(Base32SortAlphabet).WithPadding(base32.NoPadding)
1818+}
719820// Represents a TID in string format, as would pass Lexicon syntax validation.
921//
···2335 return TID(raw), nil
2436}
25372626-// TODO: additional helpers: to timestamp, from timestamp, from integer, etc
3838+// Naive (unsafe) one-off TID generation with the current time.
3939+//
4040+// You should usually use a [TIDClock] to ensure monotonic output.
4141+func NewTIDNow(clockId uint) TID {
4242+ return NewTID(time.Now().UTC().UnixMicro(), clockId)
4343+}
4444+4545+func NewTIDFromInteger(v uint64) TID {
4646+ v = (0x7FFF_FFFF_FFFF_FFFF & v)
4747+ s := ""
4848+ for i := 0; i < 13; i++ {
4949+ s = string(Base32SortAlphabet[v&0x1F]) + s
5050+ v = v >> 5
5151+ }
5252+ return TID(s)
5353+}
5454+5555+// Constructs a new TID from a UNIX timestamp (in milliseconds) and clock ID value.
5656+func NewTID(unixMilis int64, clockId uint) TID {
5757+ var v uint64 = (uint64(unixMilis&0x1F_FFFF_FFFF_FFFF) << 10) | uint64(clockId&0x3FF)
5858+ return NewTIDFromInteger(v)
5959+}
6060+6161+// Returns full integer representation of this TID (not used often)
6262+func (t TID) Integer() uint64 {
6363+ s := t.String()
6464+ if len(s) != 13 {
6565+ return 0
6666+ }
6767+ var v uint64
6868+ for i := 0; i < 13; i++ {
6969+ c := strings.IndexByte(Base32SortAlphabet, s[i])
7070+ if c < 0 {
7171+ return 0
7272+ }
7373+ v = (v << 5) | uint64(c&0x1F)
7474+ }
7575+ return v
7676+}
7777+7878+// Returns the golang [time.Time] corresponding to this TID's timestamp.
7979+func (t TID) Time() time.Time {
8080+ i := t.Integer()
8181+ i = (i >> 10) & 0x1FFF_FFFF_FFFF_FFFF
8282+ return time.UnixMicro(int64(i)).UTC()
8383+}
8484+8585+// Returns the clock ID part of this TID, as an unsigned integer
8686+func (t TID) ClockID() uint {
8787+ i := t.Integer()
8888+ return uint(i & 0x3FF)
8989+}
27902891func (t TID) String() string {
2992 return string(t)
···41104 *t = tid
42105 return nil
43106}
107107+108108+// TID generator, which keeps state to ensure TID values always monotonically increase.
109109+//
110110+// Uses [sync.Mutex], so may block briefly but safe for concurrent use.
111111+type TIDClock struct {
112112+ ClockID uint
113113+ mtx sync.Mutex
114114+ lastUnixMicro int64
115115+}
116116+117117+func NewTIDClock(clockId uint) *TIDClock {
118118+ return &TIDClock{
119119+ ClockID: clockId,
120120+ }
121121+}
122122+123123+func (c *TIDClock) Next() TID {
124124+ now := time.Now().UTC().UnixMicro()
125125+ c.mtx.Lock()
126126+ if now <= c.lastUnixMicro {
127127+ now = c.lastUnixMicro + 1
128128+ }
129129+ c.lastUnixMicro = now
130130+ c.mtx.Unlock()
131131+ return NewTID(now, c.ClockID)
132132+}