1#![cfg_attr(docsrs, feature(doc_cfg))]
2#[cfg(feature = "chrono")]
35use chrono::{DateTime, NaiveDateTime, Utc};
36use std::time::{SystemTime, UNIX_EPOCH};
37
38const EPOCH: u64 = 1735689600000;
40
41fn current_epoch() -> Result<u64, String> {
47 let mut now = SystemTime::now()
48 .duration_since(UNIX_EPOCH)
49 .unwrap()
50 .as_millis() as u64;
51 if now < EPOCH {
52 return Err("Your device time is incorrect.".to_owned());
53 }
54 now = now - EPOCH;
55 Ok(now)
56}
57
58pub(crate) struct HoraParams {
59 machine_id: u8,
60 epoch: u64,
61 sequence: u16,
62}
63
64pub struct HoraGenerator {
78 machine_id: u8,
80 sequence: u16,
82 last_gen: u64,
84}
85
86impl HoraGenerator {
87 pub fn new(machine_id: u8) -> Result<Self, String> {
88 let epoch = current_epoch()?;
89 let epoch = rescale_epoch(epoch);
90 Ok(Self {
91 machine_id,
92 sequence: 0,
93 last_gen: epoch,
94 })
95 }
96
97 pub fn next(&mut self) -> HoraId {
99 loop {
100 let epoch = current_epoch().unwrap();
101 let scaled_epoch = rescale_epoch(epoch);
102 if scaled_epoch > self.last_gen {
103 self.sequence = 0;
104 }
105
106 self.sequence += 1;
108 let params = HoraParams {
109 machine_id: self.machine_id,
110 epoch,
111 sequence: self.sequence + 1,
112 };
113 let id = HoraId::with_params(params);
114 self.last_gen = scaled_epoch;
115 break id;
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Hash)]
122pub struct HoraId {
123 inner: [u8; 8],
124}
125
126impl HoraId {
127 pub fn new(machine_id: Option<u8>) -> Result<Self, String> {
134 let epoch = current_epoch()?;
135 let params = HoraParams {
136 machine_id: machine_id.unwrap_or(0),
137 epoch,
138 sequence: 0,
139 };
140 let id = Self::with_params(params);
141 Ok(id)
142 }
143
144 pub fn rand() -> Result<Self, String> {
149 let epoch = current_epoch()?;
150 let params = HoraParams {
151 machine_id: rand::random::<u8>(),
152 epoch,
153 sequence: rand::random::<u16>(),
154 };
155 let id = Self::with_params(params);
156 Ok(id)
157 }
158
159 fn with_params(params: HoraParams) -> Self {
166 let high = (params.epoch / 1000) as u32;
167 let low = (params.epoch % 1000) as u16;
168
169 let mut tuid = [0u8; 8];
171
172 let bytes = high.to_be_bytes();
174 tuid[0] = bytes[0];
175 tuid[1] = bytes[1];
176 tuid[2] = bytes[2];
177 tuid[3] = bytes[3];
178 tuid[4] = rescale_low(low);
180
181 tuid[5] = params.machine_id;
183
184 let sequence_high = ((params.sequence >> 8) & 0xFF) as u8;
186 let sequence_low = (params.sequence & 0xFF) as u8;
187
188 tuid[6] = sequence_high;
189 tuid[7] = sequence_low;
190
191 Self { inner: tuid }
192 }
193
194 pub fn to_u64(&self) -> u64 {
196 u64::from_be_bytes(self.inner)
197 }
198
199 pub fn from_u64(num: u64) -> Option<Self> {
201 let d: [u8; 8] = num.to_be_bytes();
202 let id = Self { inner: d };
203 Some(id)
204 }
205
206 pub fn to_string(&self) -> String {
208 format!(
209 "{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
210 self.inner[0],
211 self.inner[1],
212 self.inner[2],
213 self.inner[3],
214 self.inner[4],
215 self.inner[5],
216 self.inner[6],
217 self.inner[7]
218 )
219 }
220
221 pub fn from_str(s: &str) -> Option<Self> {
223 if s.len() != 16 {
224 return None;
225 }
226 let num = u64::from_str_radix(s, 16).ok()?;
227 let bytes: [u8; 8] = num.to_be_bytes();
228 let id = Self { inner: bytes };
229 Some(id)
230 }
231
232 pub fn as_bytes(&self) -> &[u8] {
234 &self.inner
235 }
236
237 #[cfg(feature = "chrono")]
239 #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
240 pub fn to_datetime(&self) -> NaiveDateTime {
241 let mut high = [0; 4];
242 for i in 0..4 {
243 high[i] = self.inner[i];
244 }
245 let high = u32::from_be_bytes(high);
246 let low = u8::from_be_bytes([self.inner[4]]);
247 let low = upscale_low(low);
248
249 let timestamp = (high as u64 * 1000) + low as u64 + EPOCH;
250 NaiveDateTime::from_timestamp_millis(timestamp as i64).unwrap()
251 }
252
253 #[cfg(feature = "chrono")]
255 #[cfg_attr(docsrs, doc(cfg(feature = "chrono")))]
256 pub fn to_utc(&self) -> DateTime<Utc> {
257 let timestamp = self.to_datetime();
258 DateTime::<Utc>::from_utc(timestamp, Utc)
259 }
260}
261
262fn rescale_epoch(value: u64) -> u64 {
263 let high = value / 1000;
264 let low = (value % 1000) as u16;
265 let low = (low as f32) * 0.256;
266 let low = low as u64;
267 high * 1000 + low
268}
269
270fn rescale_low(value: u16) -> u8 {
272 let new_val = (value as f32) * (256.0) / (1000.0);
273 new_val as u8
274}
275
276#[allow(dead_code)]
278fn upscale_low(value: u8) -> u16 {
279 let new_val = (value as f32) * (1000.0) / 256.0;
280 new_val as u16
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286 #[cfg(feature = "chrono")]
287 use chrono::Timelike;
288
289 #[test]
290 fn it_works() {
291 let id = HoraId::new(None);
292 assert!(id.is_ok());
293 }
294
295 #[test]
296 fn random() {
297 let id1 = HoraId::rand();
298 assert!(id1.is_ok());
299 let id2 = HoraId::rand();
300 assert!(id2.is_ok());
301 assert_ne!(id1.unwrap(), id2.unwrap());
302 }
303
304 #[test]
305 fn strings() {
306 let source_id = HoraId::new(None).unwrap();
307 let s = source_id.to_string();
308 let id = HoraId::from_str(&s);
309 let derived_id = id.unwrap();
310 assert_eq!(source_id.to_string(), derived_id.to_string());
311 }
312
313 #[test]
314 fn u64s() {
315 let num = 57630818184577258;
316 let id = HoraId::from_u64(num);
317 assert!(id.is_some());
318 let id = id.unwrap();
319 assert_eq!(id.to_u64(), num);
320 }
321
322 #[test]
323 fn eq() {
324 let num = 57630818184577258;
325 let id = HoraId::from_u64(num).unwrap();
326 let id2 = HoraId::from_u64(num).unwrap();
327 assert_eq!(id, id2);
328 }
329
330 #[test]
331 fn clone() {
332 let num = 57630818184577258;
333 let id = HoraId::from_u64(num).unwrap();
334 let id2 = id.clone();
335 assert_eq!(id, id2);
336 }
337
338 #[cfg(feature = "chrono")]
339 #[test]
340 fn chrono() {
341 let id = HoraId::new(None).unwrap();
342 let time = id.to_utc();
343 let now = Utc::now();
344 assert_eq!(now.date_naive(), time.date_naive());
345 assert_eq!(now.hour(), time.hour());
346 assert_eq!(now.minute(), time.minute());
347 assert_eq!(now.second(), time.second());
348 }
349
350 #[test]
351 fn rescaling() {
352 assert_eq!(rescale_low(0), 0);
353 assert_eq!(rescale_low(1), 0);
354 assert_eq!(rescale_low(5), 1);
355 assert_eq!(rescale_low(498), 127);
356 assert_eq!(rescale_low(500), 128);
357 assert_eq!(rescale_low(995), 254);
358 assert_eq!(rescale_low(997), 255);
359 assert_eq!(rescale_low(999), 255);
360 }
361
362 #[test]
363 fn rescale() {
364 let value = upscale_low(rescale_low(500));
365 assert_eq!(value, 500);
366 }
367
368 #[test]
369 fn epoch_rescaling() {
370 let value = 1672531200000;
372 assert_eq!(rescale_epoch(value), value);
373 assert_eq!(rescale_epoch(1672531200003), 1672531200000);
375 assert_eq!(rescale_epoch(1672531200005), 1672531200001);
377 assert_eq!(rescale_epoch(1672531200006), 1672531200001);
378 assert_eq!(rescale_epoch(1672531200998), 1672531200255);
380 assert_eq!(rescale_epoch(1672531200999), 1672531200255);
381 }
382}
383
384#[cfg(test)]
385mod gen_tests {
386 use super::*;
387
388 #[cfg(feature = "chrono")]
389 #[test]
390 fn it_works() {
391 let generator = HoraGenerator::new(1);
392 assert!(generator.is_ok());
393 let mut generator = generator.unwrap();
394 generator.next();
395 }
396}