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  }