git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/ulid/ulid.go (about) 1 package ulid 2 3 import ( 4 "bytes" 5 "crypto/rand" 6 "database/sql/driver" 7 "fmt" 8 "time" 9 10 "git.sr.ht/~pingoo/stdx/base32" 11 "git.sr.ht/~pingoo/stdx/uuid" 12 ) 13 14 // TODO 15 type ULID [Size]byte 16 17 const ( 18 Size = 16 19 StringSize = 26 20 ) 21 22 var ( 23 Nil ULID 24 ) 25 26 func New() ULID { 27 ulid, err := NewErr() 28 if err != nil { 29 panic(err) 30 } 31 return ulid 32 } 33 34 func NewErr() (ULID, error) { 35 var ulid ULID 36 37 err := ulid.setRandom() 38 if err != nil { 39 return Nil, err 40 } 41 42 ulid.setTime(time.Now()) 43 return ulid, nil 44 } 45 46 func (ulid *ULID) setTime(t time.Time) { 47 timestamp := uint64(t.UnixNano() / int64(time.Millisecond)) 48 (*ulid)[0] = byte(timestamp >> 40) 49 (*ulid)[1] = byte(timestamp >> 32) 50 (*ulid)[2] = byte(timestamp >> 24) 51 (*ulid)[3] = byte(timestamp >> 16) 52 (*ulid)[4] = byte(timestamp >> 8) 53 (*ulid)[5] = byte(timestamp) 54 // var x, y byte 55 // timestamp := uint64(t.UnixNano() / int64(time.Millisecond)) 56 // // Backups [6] and [7] bytes to override them with their original values later. 57 // x, y, ulid[6], ulid[7] = ulid[6], ulid[7], x, y 58 // binary.LittleEndian.PutUint64(ulid[:], timestamp) 59 // // Truncates at the 6th byte as designed in the original spec (48 bytes). 60 // ulid[6], ulid[7] = x, y 61 } 62 63 func (ulid *ULID) setRandom() (err error) { 64 _, err = rand.Read(ulid[6:]) 65 return 66 } 67 68 func Parse(input string) (ret ULID, err error) { 69 var retBytes []byte 70 71 retBytes, err = base32.DecodeString(input) 72 if err != nil { 73 return 74 } 75 if len(retBytes) != Size { 76 err = fmt.Errorf("invalid ULID (got %d bytes)", len(input)) 77 return 78 } 79 80 copy(ret[:], retBytes) 81 return 82 } 83 84 func ParseBytes(input []byte) (ret ULID, err error) { 85 switch len(input) { 86 case Size: 87 copy(ret[:], input) 88 case StringSize: 89 ret, err = Parse(string(input)) 90 default: 91 err = fmt.Errorf("invalid ULID (got %d bytes)", len(input)) 92 } 93 94 return 95 } 96 97 // MarshalText implements encoding.TextMarshaler. 98 func (ulid ULID) MarshalText() ([]byte, error) { 99 str := ulid.String() 100 return []byte(str), nil 101 } 102 103 // UnmarshalText implements encoding.TextUnmarshaler. 104 func (ulid *ULID) UnmarshalText(data []byte) error { 105 id, err := ParseBytes(data) 106 if err != nil { 107 return err 108 } 109 *ulid = id 110 return nil 111 } 112 113 // String returns the string form of ulid 114 func (ulid ULID) String() string { 115 return base32.EncodeToString(ulid[:]) 116 } 117 118 // UUIDString returns the UUID string form of ulid, xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx 119 func (ulid ULID) UUIDString() string { 120 uuid := uuid.UUID(ulid) 121 return uuid.String() 122 } 123 124 // MarshalBinary implements encoding.BinaryMarshaler. 125 func (ulid ULID) MarshalBinary() ([]byte, error) { 126 return ulid[:], nil 127 } 128 129 func (ulid ULID) Bytes() []byte { 130 return ulid[:] 131 } 132 133 // TODO: improve 134 // Scan implements sql.Scanner so ULIDs can be read from databases transparently. 135 // Currently, database types that map to string and []byte are supported. Please 136 // consult database-specific driver documentation for matching types. 137 func (ulid *ULID) Scan(src interface{}) (err error) { 138 var uuid uuid.UUID 139 err = uuid.Scan(src) 140 *ulid = ULID(uuid) 141 return 142 } 143 144 // Value implements sql.Valuer so that ULIDs can be written to databases 145 // transparently. Currently, ULIDs map to strings. Please consult 146 // database-specific driver documentation for matching types. 147 func (ulid ULID) Value() (driver.Value, error) { 148 return ulid.UUIDString(), nil 149 } 150 151 func (ulid ULID) Equal(other ULID) bool { 152 return bytes.Equal(ulid[:], ulid[:]) 153 }