├── .gitignore ├── LICENSE ├── README.md ├── ulid.go └── ulid_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | #### joe made this: http://goel.io/joe 2 | 3 | #####=== Go ===##### 4 | 5 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 6 | *.o 7 | *.a 8 | *.so 9 | 10 | # Folders 11 | _obj 12 | _test 13 | 14 | # Architecture specific extensions/prefixes 15 | *.[568vq] 16 | [568vq].out 17 | 18 | *.cgo1.go 19 | *.cgo2.c 20 | _cgo_defun.c 21 | _cgo_gotypes.go 22 | _cgo_export.* 23 | 24 | _testmain.go 25 | 26 | *.exe 27 | *.test 28 | *.prof 29 | 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Dario Castañé, https://dario.im 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Universally Unique Lexicographically Sortable Identifier 2 | ![Project status](https://img.shields.io/badge/version-0.0.3-yellow.svg) 3 | [![Go Report Card](https://goreportcard.com/badge/imdario/go-ulid)](https://goreportcard.com/report/imdario/go-ulid) 4 | [![GoDoc](https://godoc.org/github.com/imdario/go-ulid?status.svg)](https://godoc.org/github.com/imdario/go-ulid) 5 | [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://raw.githubusercontent.com/imdario/go-ulid/master/LICENSE) 6 | 7 | [alizain/ulid](https://github.com/alizain/ulid) port to Golang (binary format implemented). 8 | 9 | ## Why ULID? 10 | 11 | Check out [ULID's README](https://github.com/alizain/ulid/blob/master/README.md). 12 | 13 | ## About this implementation 14 | 15 | **Important:** this repository has been archived. Please use [oklog/ulid](https://github.com/oklog/ulid) implementation. 16 | 17 | I ported it to see how fast would be the same algorithm in Go. Also, it is cryptographically secure using crypto/rand. 18 | 19 | ### Installation 20 | 21 | ``` 22 | go get github.com/imdario/go-ulid 23 | ``` 24 | 25 | ### Usage 26 | 27 | ``` 28 | import ( 29 | "github.com/imdario/go-ulid" 30 | ) 31 | 32 | // ... 33 | 34 | u := ulid.New() 35 | ``` 36 | 37 | ### Performance 38 | 39 | On a Intel Core 2 Duo 6600 @ 2.40 GHz, Windows 10 and Go 1.6.3: 40 | 41 | ``` 42 | BenchmarkULID-2 1000000 1029 ns/op 16 B/op 1 allocs/op 43 | BenchmarkEncodedULID-2 1000000 1249 ns/op 48 B/op 2 allocs/op 44 | BenchmarkSingleEncodedULID-2 10000000 206 ns/op 32 B/op 1 allocs/op 45 | ``` 46 | 47 | Approx. 800.640 op/s, 46 times faster than [Javascript original implementation](https://github.com/alizain/ulid#performance). 48 | 49 | #### How does it compare to UUID? 50 | 51 | Using [google/uuid](https://github.com/google/uuid): 52 | 53 | ``` 54 | BenchmarkUUID-2 1000000 1041 ns/op 16 B/op 1 allocs/op 55 | BenchmarkEncodedUUID-2 1000000 1407 ns/op 64 B/op 2 allocs/op 56 | BenchmarkSingleEncodedUUID-2 5000000 302 ns/op 48 B/op 1 allocs/op 57 | ``` 58 | 59 | go-ulid is about 12% faster than Google's UUID! 60 | -------------------------------------------------------------------------------- /ulid.go: -------------------------------------------------------------------------------- 1 | package ulid 2 | 3 | import ( 4 | "crypto/rand" 5 | "encoding/binary" 6 | "time" 7 | ) 8 | 9 | // A ULID is a 16 byte Universally Unique Lexicographically Sortable Identifier 10 | type ULID [16]byte 11 | 12 | const ( 13 | // Crockford"s Base32 14 | // https://en.wikipedia.org/wiki/Base32 15 | alphabet = "0123456789ABCDEFGHJKMNPQRSTVWXYZ" 16 | alphabetSize = int64(len(alphabet)) 17 | encodedTimeLength = 10 18 | encodedRandLength = 16 19 | ) 20 | 21 | var ( 22 | rander = rand.Reader // random function 23 | // Nil as empty value to handle errors 24 | Nil ULID 25 | ) 26 | 27 | // New is creates a new random ULID or panics. New is equivalent to 28 | // the expression 29 | // 30 | // ulid.Must(ulid.NewRandom()) 31 | func New() ULID { 32 | return Must(NewRandom()) 33 | } 34 | 35 | // Must returns ulid if err is nil and panics otherwise. 36 | func Must(ulid ULID, err error) ULID { 37 | if err != nil { 38 | panic(err) 39 | } 40 | return ulid 41 | } 42 | 43 | // NewRandom returns a ULID (binary implementation) or panics. 44 | // 45 | // The strength of the ULIDs is based on the strength of the crypto/rand 46 | // package. 47 | func NewRandom() (ULID, error) { 48 | var ( 49 | ulid ULID 50 | ) 51 | err := setRandom(&ulid) 52 | if err != nil { 53 | return Nil, err 54 | } 55 | setTime(&ulid, time.Now()) 56 | return ulid, err 57 | } 58 | 59 | func setTime(ulid *ULID, t time.Time) { 60 | var x, y byte 61 | timestamp := uint64(t.UnixNano() / int64(time.Millisecond)) 62 | // Backups [6] and [7] bytes to override them with their original values later. 63 | x, y, ulid[6], ulid[7] = ulid[6], ulid[7], x, y 64 | binary.LittleEndian.PutUint64(ulid[:], timestamp) 65 | // Truncates at the 6th byte as designed in the original spec (48 bytes). 66 | ulid[6], ulid[7] = x, y 67 | } 68 | 69 | func setRandom(ulid *ULID) (err error) { 70 | _, err = rand.Read(ulid[6:]) 71 | return 72 | } 73 | 74 | // String returns the string form of ulid (26 characters, non-standard base 32) 75 | func (ulid ULID) String() string { 76 | var ( 77 | buf [26]byte 78 | x, y byte 79 | ) 80 | // Backups [6] and [7] bytes to override them with their original values later. 81 | x, y, ulid[6], ulid[7] = ulid[6], ulid[7], x, y 82 | timestamp := int64(binary.LittleEndian.Uint64(ulid[:8])) 83 | // This is useful to shave some nanoseconds from copy() operations. 84 | ulid[6], ulid[7] = x, y 85 | for x := encodedTimeLength - 1; x >= 0; x-- { 86 | mod := timestamp % alphabetSize 87 | buf[x] = alphabet[mod] 88 | timestamp = (timestamp - mod) / alphabetSize 89 | } 90 | for x := encodedTimeLength; x < len(ulid); x++ { 91 | buf[x] = alphabet[int64(ulid[x])%alphabetSize] 92 | } 93 | return string(buf[:]) 94 | } 95 | -------------------------------------------------------------------------------- /ulid_test.go: -------------------------------------------------------------------------------- 1 | package ulid 2 | 3 | import ( 4 | "testing" 5 | "time" 6 | ) 7 | 8 | func TestSetTime(t *testing.T) { 9 | var ( 10 | ulid ULID 11 | ts = time.Unix(1469918176, 385000000) 12 | ) 13 | tm := ts.UnixNano() / int64(time.Millisecond) 14 | if tm != 1469918176385 { 15 | t.Fatalf("expected 1469918176385, got '%v'", tm) 16 | } 17 | setTime(&ulid, ts) 18 | et := ulid.String() 19 | if et[:10] != "01ARYZ6S41" { 20 | t.Fatalf("expected '01ARYZ6S41', got '%s'", et[:10]) 21 | } 22 | } 23 | 24 | func TestSetTimeAfterSetRandom(t *testing.T) { 25 | var ( 26 | ulid ULID 27 | ts = time.Unix(1469918176, 385000000) 28 | ) 29 | tm := ts.UnixNano() / int64(time.Millisecond) 30 | if tm != 1469918176385 { 31 | t.Fatalf("expected 1469918176385, got '%v'", tm) 32 | } 33 | setRandom(&ulid) 34 | setTime(&ulid, ts) 35 | et := ulid.String() 36 | if et[:10] != "01ARYZ6S41" { 37 | t.Fatalf("expected '01ARYZ6S41', got '%s'", et[:10]) 38 | } 39 | } 40 | 41 | func BenchmarkULID(b *testing.B) { 42 | for i := 0; i < b.N; i++ { 43 | New() 44 | } 45 | } 46 | 47 | func BenchmarkEncodedULID(b *testing.B) { 48 | for i := 0; i < b.N; i++ { 49 | _ = New().String() 50 | } 51 | } 52 | 53 | func BenchmarkSingleEncodedULID(b *testing.B) { 54 | u := New() 55 | b.ResetTimer() 56 | for i := 0; i < b.N; i++ { 57 | _ = u.String() 58 | } 59 | } 60 | --------------------------------------------------------------------------------