75 lines
1.8 KiB
Crystal
75 lines
1.8 KiB
Crystal
require "base32"
|
|
|
|
module Ulid
|
|
VERSION = "0.1.1"
|
|
|
|
class ULID
|
|
class Error < Exception; end
|
|
|
|
include Comparable(ULID)
|
|
|
|
BYTE_COUNT = 16
|
|
|
|
def_hash @bytes
|
|
|
|
getter bytes : Bytes
|
|
|
|
def initialize(bytes : Bytes)
|
|
initialize(bytes, true)
|
|
end
|
|
|
|
private def initialize(bytes : Bytes, copy : Bool)
|
|
raise Error.new "Not enough bytes, #{BYTE_COUNT} required" if bytes.size < BYTE_COUNT
|
|
raise Error.new "Too many bytes, #{BYTE_COUNT} required" if bytes.size > BYTE_COUNT
|
|
@bytes = copy ? Bytes.new(BYTE_COUNT, read_only: true) { |i| bytes[i] } : bytes
|
|
end
|
|
|
|
def initialize(time : Time, random : Bytes)
|
|
ms = time.to_unix_ms
|
|
bytes = Bytes.new(BYTE_COUNT, read_only: true) do |i|
|
|
i < 6 ? (ms >> (8 * (5 - i)) & 0xFF).to_u8 : random[i - 6]
|
|
end
|
|
initialize(bytes, false)
|
|
end
|
|
|
|
def initialize(time : Time, *, generator : Random = Random::PCG32)
|
|
random = generate_random_bytes(generator)
|
|
initialize(time, random)
|
|
end
|
|
|
|
def initialize(*, generator : Random = Random::PCG32.new)
|
|
random = generate_random_bytes(generator)
|
|
initialize(Time.utc, random)
|
|
end
|
|
|
|
def initialize(str : String)
|
|
initialize(Base32.decode(str, Base32::Crockford))
|
|
end
|
|
|
|
def to_s(io : IO) : Void
|
|
io << Base32.encode(@bytes, Base32::Crockford)
|
|
end
|
|
|
|
def inspect(io : IO) : Void
|
|
io << "#<" << self.class.name << " "
|
|
to_s(io)
|
|
io << ">"
|
|
end
|
|
|
|
def time : Time
|
|
return time if time = @time
|
|
ms = 0u64
|
|
6.times { |i| ms |= @bytes[5 - i].to_u64 << (i * 8) }
|
|
@time = Time.unix_ms(ms)
|
|
end
|
|
|
|
def <=>(other : ULID)
|
|
@bytes.<=>(other.@bytes)
|
|
end
|
|
|
|
private def generate_random_bytes(generator : Random)
|
|
Bytes.new(10) { |i| generator.rand(256).to_u8 }
|
|
end
|
|
end
|
|
end
|