@@ -9,6 +9,24 @@ use regex::Regex;
99use serde:: { de:: Error , Deserialize , Deserializer , Serialize , Serializer } ;
1010use std:: { cmp, ops:: Deref , str:: FromStr , sync:: OnceLock } ;
1111
12+ use super :: LimitedU32 ;
13+
14+ // Reference: https://github.com/bluesky-social/indigo/blob/9e3b84fdbb20ca4ac397a549e1c176b308f7a6e1/repo/tid.go#L11-L19
15+ fn s32_encode ( mut i : u64 ) -> String {
16+ const S32_CHAR : & [ u8 ] = b"234567abcdefghijklmnopqrstuvwxyz" ;
17+
18+ let mut s = String :: with_capacity ( 13 ) ;
19+ for _ in 0 ..13 {
20+ let c = i & 0x1F ;
21+ s. push ( S32_CHAR [ c as usize ] as char ) ;
22+
23+ i >>= 5 ;
24+ }
25+
26+ // Reverse the string to convert it to big-endian format.
27+ s. chars ( ) . rev ( ) . collect ( )
28+ }
29+
1230/// Common trait implementations for Lexicon string formats that are newtype wrappers
1331/// around `String`.
1432macro_rules! string_newtype {
@@ -410,7 +428,7 @@ impl Serialize for Language {
410428
411429/// A [Timestamp Identifier].
412430///
413- /// [Timestamp Identifier]: https://atproto.com/specs/record-key#record-key-type- tid
431+ /// [Timestamp Identifier]: https://atproto.com/specs/tid
414432#[ derive( Clone , Debug , PartialEq , Eq , Serialize , Hash ) ]
415433#[ serde( transparent) ]
416434pub struct Tid ( String ) ;
@@ -436,6 +454,35 @@ impl Tid {
436454 }
437455 }
438456
457+ /// Construct a new timestamp with the specified clock ID.
458+ ///
459+ /// If you have multiple clock sources, you can use `clkid` to distinguish between them
460+ /// and hint to other implementations that the timestamp cannot be compared with other
461+ /// timestamps from other sources.
462+ /// If you are only using a single clock source, you can just specify `0` for `clkid`.
463+ pub fn from_datetime ( clkid : LimitedU32 < 1023 > , time : chrono:: DateTime < chrono:: Utc > ) -> Self {
464+ let time = time. timestamp_micros ( ) as u64 ;
465+
466+ // The TID is laid out as follows:
467+ // 0TTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTTTTTTTTTTT TTTTTTCCCCCCCCCC
468+ let tid = ( time << 10 ) & 0x7FFF_FFFF_FFFF_FC00 | ( Into :: < u32 > :: into ( clkid) as u64 & 0x3FF ) ;
469+ Self ( s32_encode ( tid) )
470+ }
471+
472+ /// Construct a new [Tid] that represents the current time.
473+ ///
474+ /// If you have multiple clock sources, you can use `clkid` to distinguish between them
475+ /// and hint to other implementations that the timestamp cannot be compared with other
476+ /// timestamps from other sources.
477+ /// If you are only using a single clock source, you can just specify `0` for `clkid`.
478+ ///
479+ /// _Warning:_ It's possible that this function will return the same time more than once.
480+ /// If it's important that these values be unique, you will want to repeatedly call this
481+ /// function until a different time is returned.
482+ pub fn now ( clkid : LimitedU32 < 1023 > ) -> Self {
483+ Self :: from_datetime ( clkid, chrono:: Utc :: now ( ) )
484+ }
485+
439486 /// Returns the TID as a string slice.
440487 pub fn as_str ( & self ) -> & str {
441488 self . 0 . as_str ( )
@@ -766,6 +813,21 @@ mod tests {
766813 }
767814 }
768815
816+ #[ test]
817+ fn tid_encode ( ) {
818+ assert_eq ! ( s32_encode( 0 ) , "2222222222222" ) ;
819+ assert_eq ! ( s32_encode( 1 ) , "2222222222223" ) ;
820+ }
821+
822+ #[ test]
823+ fn tid_construct ( ) {
824+ let tid = Tid :: from_datetime (
825+ 0 . try_into ( ) . unwrap ( ) ,
826+ chrono:: DateTime :: from_timestamp ( 1738430999 , 0 ) . unwrap ( ) ,
827+ ) ;
828+ assert_eq ! ( tid. as_str( ) , "3lh5234mwy222" ) ;
829+ }
830+
769831 #[ test]
770832 fn valid_tid ( ) {
771833 for valid in [ "3jzfcijpj2z2a" , "7777777777777" , "3zzzzzzzzzzzz" ] {
0 commit comments