timeflake_rs/
lib.rs

1// SPDX-FileCopyrightText: 2022 perillamint
2//
3// SPDX-License-Identifier: MIT
4
5use core::fmt;
6use core::str::FromStr;
7use rand::{thread_rng, Rng};
8use std::time::{Duration, SystemTime, UNIX_EPOCH};
9use uuid::Uuid;
10
11pub mod error;
12
13use error::TimeflakeError;
14
15pub struct Timeflake {
16    pub timestamp: Duration,
17    pub random: u128,
18}
19
20impl Timeflake {
21    pub fn parse(data: &str) -> Result<Timeflake, TimeflakeError> {
22        // currently only support uuid-format of timeflake. Sorry!
23        let uuid = match Uuid::from_str(data) {
24            Ok(x) => Ok(x),
25            Err(e) => Err(TimeflakeError::MalformedData { msg: e.to_string() }),
26        }?;
27
28        let flake = uuid.as_u128();
29
30        let timestamp = Duration::from_millis(
31            // If this fails, something is terribly wrong anyway.
32            ((flake & 0xFFFFFFFFFFFF00000000000000000000) >> 80)
33                .try_into()
34                .unwrap(),
35        );
36        let random = flake & 0x000000000000FFFFFFFFFFFFFFFFFFFF;
37
38        Ok(Self { timestamp, random })
39    }
40
41    pub fn random() -> Result<Timeflake, TimeflakeError> {
42        let time = match SystemTime::now().duration_since(UNIX_EPOCH) {
43            Ok(x) => x,
44            Err(e) => return Err(TimeflakeError::SystemTimeError { msg: e.to_string() }),
45        };
46
47        Self::from_values(time, None)
48    }
49
50    #[cfg(all(
51        feature = "std",
52        not(target_arch = "wasm32"),
53        not(target_arch = "asmjs")
54    ))]
55    pub fn from_values(
56        timestamp: Duration,
57        random_val: Option<u128>,
58    ) -> Result<Timeflake, TimeflakeError> {
59        let random = match random_val {
60            Some(x) => x,
61            None => thread_rng().gen::<u128>(),
62        };
63
64        Ok(Self { timestamp, random })
65    }
66
67    #[cfg(any(not(feature = "std"), target_arch = "wasm32", target_arch = "asmjs"))]
68    pub fn from_values(
69        timestamp: Duration,
70        random_val: Option<u128>,
71    ) -> Result<Timeflake, TimeflakeError> {
72        let random = match random_val {
73            Some(x) => x,
74            None => {
75                let mut val = [0u8; 16];
76                match thread_rng().try_fill(&mut val[..10]) {
77                    Ok(_) => {}
78                    Err(e) => return Err(TimeflakeError::RNGError { msg: e.to_string() }),
79                }
80
81                u128::from_le_bytes(val)
82            }
83        };
84
85        Ok(Self { timestamp, random })
86    }
87
88    pub fn as_u128(&self) -> u128 {
89        let timeflake = self.random & 0x000000000000FFFFFFFFFFFFFFFFFFFF;
90        let timestamp_part = self.timestamp.as_millis();
91        timeflake | timestamp_part << 80
92    }
93
94    pub fn as_uuid(&self) -> Uuid {
95        Uuid::from_u128(self.as_u128())
96    }
97}
98
99impl From<Timeflake> for u128 {
100    fn from(timeflake: Timeflake) -> Self {
101        timeflake.as_u128()
102    }
103}
104
105impl From<Timeflake> for Uuid {
106    fn from(timeflake: Timeflake) -> Self {
107        timeflake.as_uuid()
108    }
109}
110
111impl fmt::Display for Timeflake {
112    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
113        write!(f, "{}", self.as_uuid())
114    }
115}
116
117#[test]
118fn parse_test() {
119    let flake = Timeflake::from_values(Duration::from_millis(424242), Some(242424)).unwrap();
120    let flake2 = Timeflake::parse(&flake.to_string()).unwrap();
121
122    assert_eq!(flake.timestamp.as_millis(), 424242);
123    assert_eq!(flake.random, 242424);
124    assert_eq!(flake.timestamp, flake2.timestamp);
125    assert_eq!(flake.random, flake2.random);
126}
127
128#[test]
129fn example() {
130    let time = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
131    println!("{}", Timeflake::random().unwrap());
132    println!("{}", Timeflake::from_values(time, Some(0)).unwrap());
133    println!("{}", Timeflake::from_values(time, None).unwrap());
134    println!("{}", Timeflake::from_values(time, None).unwrap());
135}