use core::{
cmp::Ordering,
fmt,
hash::{Hash, Hasher},
str::FromStr,
};
use utcnow::UtcTime;
use error::{Error, Result};
use num_bigint::BigUint;
use num_traits::ToPrimitive;
use rand::Rng;
#[cfg(feature = "uuid")]
use uuid::Uuid;
#[cfg(test)]
mod tests;
pub mod error;
pub const BASE62: &str = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
pub const HEX: &str = "0123456789abcdef";
pub const MAX_TIMESTAMP: u64 = 281474976710655;
pub const MAX_RANDOM: &str = "1208925819614629174706175";
pub const MAX_TIMEFLAKE: &str = "340282366920938463463374607431768211455";
#[derive(Debug, Clone)]
pub struct Timeflake {
bytes: [u8; 16],
int_value: BigUint,
}
impl Timeflake {
#[must_use]
pub fn new_random<R: Rng>(rng: &mut R) -> Self {
let utc_time = UtcTime::now().unwrap();
let now = utc_time.as_millis() as u64;
let mut random_bytes = [0u8; 10];
rng.fill(&mut random_bytes);
let random = BigUint::from_bytes_be(&random_bytes);
Self::from_components(now, &random).unwrap()
}
#[must_use]
pub fn from_bytes(bytes: [u8; 16]) -> Result<Self> {
let int_value = bytes_to_biguint(&bytes);
if int_value > max_timeflake_biguint() {
return Err(Error::InvalidFlake);
}
Ok(Timeflake { bytes, int_value })
}
#[must_use]
pub fn from_bytes_checked(bytes: [u8; 16]) -> Self {
Self::from_bytes(bytes).unwrap()
}
#[must_use]
pub fn from_components(timestamp: u64, random: &BigUint) -> Result<Self> {
if timestamp > MAX_TIMESTAMP {
return Err(Error::InvalidTimestamp(timestamp));
}
if random > &max_random_biguint() {
return Err(Error::InvalidRandom);
}
let ts_biguint = BigUint::from(timestamp);
let int_value = (ts_biguint << 80) | random;
let bytes = biguint_to_bytes(&int_value)?;
Ok(Timeflake { bytes, int_value })
}
#[must_use]
pub fn from_components_checked(timestamp: u64, random: &BigUint) -> Self {
Self::from_components(timestamp, random).unwrap()
}
#[must_use]
pub fn from_base62<S: AsRef<str>>(s: S) -> Result<Self> {
let decoded = match base62::decode(s.as_ref()) {
Ok(bytes) => bytes,
Err(_) => {
return Err(Error::ParseError {
input: s.as_ref().to_string(),
reason: "Invalid base62 encoding".to_string(),
});
}
};
let int_value = BigUint::from_bytes_be(&decoded.to_be_bytes());
if int_value > max_timeflake_biguint() {
return Err(Error::InvalidFlake);
}
let bytes = biguint_to_bytes(&int_value)?;
Ok(Timeflake { bytes, int_value })
}
#[must_use]
pub fn from_base62_checked<S: AsRef<str>>(s: S) -> Self {
Self::from_base62(s).unwrap()
}
#[must_use]
pub fn from_bigint(value: BigUint) -> Result<Self> {
let bytes = biguint_to_bytes(&value)?;
Self::from_bytes(bytes)
}
#[must_use]
pub fn from_bigint_checked(value: BigUint) -> Self {
Self::from_bigint(value).unwrap()
}
#[cfg(feature = "uuid")]
#[must_use]
pub fn from_uuid(uuid: Uuid) -> Result<Self> {
Self::from_bytes(uuid.into_bytes())
}
#[cfg(feature = "uuid")]
#[must_use]
pub fn from_uuid_checked(uuid: Uuid) -> Self {
Self::from_uuid(uuid).unwrap()
}
#[cfg(feature = "uuid")]
pub fn to_uuid(&self) -> Uuid {
Uuid::from_bytes(self.bytes)
}
pub fn to_base62(&self) -> String {
let bytes = u128::from_be_bytes(self.bytes);
let encoded = base62::encode(bytes);
let padding = 22;
if encoded.len() < padding {
let zeros = "0".repeat(padding - encoded.len());
return zeros + &encoded;
}
encoded
}
pub fn timestamp(&self) -> u64 {
let shifted: BigUint = &self.int_value >> 80;
shifted.to_u64().unwrap()
}
pub fn random(&self) -> BigUint {
&self.int_value & &max_random_biguint()
}
pub fn to_hex(&self) -> String {
hex::encode(self.bytes)
}
pub fn to_bytes(&self) -> &[u8; 16] {
&self.bytes
}
pub fn to_bigint(&self) -> &BigUint {
&self.int_value
}
}
impl FromStr for Timeflake {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
if s.len() == 32 && s.chars().all(|c| HEX.contains(c)) {
let bytes = hex::decode(s).map_err(|e| Error::ParseError {
input: s.to_string(),
reason: format!("Invalid hex: {}", e),
})?;
if bytes.len() != 16 {
return Err(Error::ParseError {
input: s.to_string(),
reason: format!("Expected 16 bytes, got {}", bytes.len()),
});
}
let mut array = [0u8; 16];
array.copy_from_slice(&bytes);
return Self::from_bytes(array);
}
if s.len() <= 22 && s.chars().all(|c| c.is_ascii_alphanumeric()) {
return Self::from_base62(s);
}
Err(Error::ParseError {
input: s.to_string(),
reason: "String must be either a 32-character hex string or a base62 string"
.to_string(),
})
}
}
impl PartialEq for Timeflake {
fn eq(&self, other: &Self) -> bool {
self.int_value == other.int_value
}
}
impl Eq for Timeflake {}
impl PartialOrd for Timeflake {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Timeflake {
fn cmp(&self, other: &Self) -> Ordering {
self.int_value.cmp(&other.int_value)
}
}
impl Hash for Timeflake {
fn hash<H: Hasher>(&self, state: &mut H) {
self.bytes.hash(state);
}
}
impl fmt::Display for Timeflake {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.to_base62())
}
}
#[inline(always)]
fn bytes_to_biguint(bytes: &[u8; 16]) -> BigUint {
let mut result = BigUint::from(0u8);
for &byte in bytes {
result = (result << 8) | BigUint::from(byte);
}
result
}
#[inline(always)]
fn biguint_to_bytes(n: &BigUint) -> Result<[u8; 16]> {
let bytes = n.to_bytes_be();
let mut result = [0u8; 16];
if bytes.len() > 16 {
return Err(Error::ConversionError(format!(
"BigUint is too large to fit in 16 bytes (got {} bytes)",
bytes.len()
)));
}
let offset = 16 - bytes.len();
result[offset..].copy_from_slice(&bytes);
Ok(result)
}
#[inline(always)]
pub fn max_random_biguint() -> BigUint {
BigUint::parse_bytes(MAX_RANDOM.as_bytes(), 10).unwrap()
}
#[inline(always)]
pub fn max_timeflake_biguint() -> BigUint {
BigUint::parse_bytes(MAX_TIMEFLAKE.as_bytes(), 10).unwrap()
}