ulid/src/ulid.cr

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