1 //! The implementation for Version 7 UUIDs. 2 //! 3 //! Note that you need to enable the `v7` Cargo feature 4 //! in order to use this module. 5 6 use crate::{rng, std::convert::TryInto, timestamp::Timestamp, Builder, Uuid}; 7 8 impl Uuid { 9 /// Create a new version 7 UUID using the current time value. 10 /// 11 /// This method is a convenient alternative to [`Uuid::new_v7`] that uses the current system time 12 /// as the source timestamp. All UUIDs generated through this method by the same process are 13 /// guaranteed to be ordered by their creation. 14 #[cfg(feature = "std")] now_v7() -> Self15 pub fn now_v7() -> Self { 16 Self::new_v7(Timestamp::now( 17 crate::timestamp::context::shared_context_v7(), 18 )) 19 } 20 21 /// Create a new version 7 UUID using a time value and random bytes. 22 /// 23 /// When the `std` feature is enabled, you can also use [`Uuid::now_v7`]. 24 /// 25 /// Note that usage of this method requires the `v7` feature of this crate 26 /// to be enabled. 27 /// 28 /// Also see [`Uuid::now_v7`] for a convenient way to generate version 7 29 /// UUIDs using the current system time. 30 /// 31 /// # Examples 32 /// 33 /// A v7 UUID can be created from a unix [`Timestamp`] plus a 128 bit 34 /// random number. When supplied as such, the data will be combined 35 /// to ensure uniqueness and sortability at millisecond granularity. 36 /// 37 /// ```rust 38 /// # use uuid::{Uuid, Timestamp, NoContext}; 39 /// let ts = Timestamp::from_unix(NoContext, 1497624119, 1234); 40 /// 41 /// let uuid = Uuid::new_v7(ts); 42 /// 43 /// assert!( 44 /// uuid.hyphenated().to_string().starts_with("015cb15a-86d8-7") 45 /// ); 46 /// ``` 47 /// 48 /// A v7 UUID can also be created with a counter to ensure batches of 49 /// UUIDs created together remain sortable: 50 /// 51 /// ```rust 52 /// # use uuid::{Uuid, Timestamp, ContextV7}; 53 /// let context = ContextV7::new(); 54 /// let uuid1 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234)); 55 /// let uuid2 = Uuid::new_v7(Timestamp::from_unix(&context, 1497624119, 1234)); 56 /// 57 /// assert!(uuid1 < uuid2); 58 /// ``` 59 /// 60 /// # References 61 /// 62 /// * [UUID Version 7 in RFC 9562](https://www.ietf.org/rfc/rfc9562.html#section-5.7) new_v7(ts: Timestamp) -> Self63 pub fn new_v7(ts: Timestamp) -> Self { 64 let (secs, nanos) = ts.to_unix(); 65 let millis = (secs * 1000).saturating_add(nanos as u64 / 1_000_000); 66 67 let mut counter_and_random = rng::u128(); 68 69 let (mut counter, counter_bits) = ts.counter(); 70 71 debug_assert!(counter_bits <= 128); 72 73 let mut counter_bits = counter_bits as u32; 74 75 // If the counter intersects the variant field then shift around it. 76 // This ensures that any bits set in the counter that would intersect 77 // the variant are still preserved 78 if counter_bits > 12 { 79 let mask = u128::MAX << (counter_bits - 12); 80 81 counter = (counter & !mask) | ((counter & mask) << 2); 82 83 counter_bits += 2; 84 } 85 86 counter_and_random &= u128::MAX.overflowing_shr(counter_bits).0; 87 counter_and_random |= counter 88 .overflowing_shl(128u32.saturating_sub(counter_bits)) 89 .0; 90 91 Builder::from_unix_timestamp_millis( 92 millis, 93 &counter_and_random.to_be_bytes()[..10].try_into().unwrap(), 94 ) 95 .into_uuid() 96 } 97 } 98 99 #[cfg(test)] 100 mod tests { 101 use super::*; 102 103 use crate::{std::string::ToString, ClockSequence, NoContext, Variant, Version}; 104 105 #[cfg(all( 106 target_arch = "wasm32", 107 target_vendor = "unknown", 108 target_os = "unknown" 109 ))] 110 use wasm_bindgen_test::*; 111 112 #[test] 113 #[cfg_attr( 114 all( 115 target_arch = "wasm32", 116 target_vendor = "unknown", 117 target_os = "unknown" 118 ), 119 wasm_bindgen_test 120 )] test_new()121 fn test_new() { 122 let ts: u64 = 1645557742000; 123 124 let seconds = ts / 1000; 125 let nanos = ((ts % 1000) * 1_000_000) as u32; 126 127 let uuid = Uuid::new_v7(Timestamp::from_unix(NoContext, seconds, nanos)); 128 let uustr = uuid.hyphenated().to_string(); 129 130 assert_eq!(uuid.get_version(), Some(Version::SortRand)); 131 assert_eq!(uuid.get_variant(), Variant::RFC4122); 132 assert!(uuid.hyphenated().to_string().starts_with("017f22e2-79b0-7")); 133 134 // Ensure parsing the same UUID produces the same timestamp 135 let parsed = Uuid::parse_str(uustr.as_str()).unwrap(); 136 137 assert_eq!(uuid, parsed); 138 } 139 140 #[test] 141 #[cfg_attr( 142 all( 143 target_arch = "wasm32", 144 target_vendor = "unknown", 145 target_os = "unknown" 146 ), 147 wasm_bindgen_test 148 )] 149 #[cfg(feature = "std")] test_now()150 fn test_now() { 151 let uuid = Uuid::now_v7(); 152 153 assert_eq!(uuid.get_version(), Some(Version::SortRand)); 154 assert_eq!(uuid.get_variant(), Variant::RFC4122); 155 } 156 157 #[test] 158 #[cfg_attr( 159 all( 160 target_arch = "wasm32", 161 target_vendor = "unknown", 162 target_os = "unknown" 163 ), 164 wasm_bindgen_test 165 )] test_sorting()166 fn test_sorting() { 167 let time1: u64 = 1_496_854_535; 168 let time_fraction1: u32 = 812_000_000; 169 170 let time2 = time1 + 4000; 171 let time_fraction2 = time_fraction1; 172 173 let uuid1 = Uuid::new_v7(Timestamp::from_unix(NoContext, time1, time_fraction1)); 174 let uuid2 = Uuid::new_v7(Timestamp::from_unix(NoContext, time2, time_fraction2)); 175 176 assert!(uuid1.as_bytes() < uuid2.as_bytes()); 177 assert!(uuid1.to_string() < uuid2.to_string()); 178 } 179 180 #[test] 181 #[cfg_attr( 182 all( 183 target_arch = "wasm32", 184 target_vendor = "unknown", 185 target_os = "unknown" 186 ), 187 wasm_bindgen_test 188 )] test_new_timestamp_roundtrip()189 fn test_new_timestamp_roundtrip() { 190 let time: u64 = 1_496_854_535; 191 let time_fraction: u32 = 812_000_000; 192 193 let ts = Timestamp::from_unix(NoContext, time, time_fraction); 194 195 let uuid = Uuid::new_v7(ts); 196 197 let decoded_ts = uuid.get_timestamp().unwrap(); 198 199 assert_eq!(ts.to_unix(), decoded_ts.to_unix()); 200 } 201 202 #[test] 203 #[cfg_attr( 204 all( 205 target_arch = "wasm32", 206 target_vendor = "unknown", 207 target_os = "unknown" 208 ), 209 wasm_bindgen_test 210 )] test_new_max_context()211 fn test_new_max_context() { 212 struct MaxContext; 213 214 #[cfg(test)] 215 impl ClockSequence for MaxContext { 216 type Output = u128; 217 218 fn generate_sequence(&self, _seconds: u64, _nanos: u32) -> Self::Output { 219 u128::MAX 220 } 221 222 fn usable_bits(&self) -> usize { 223 128 224 } 225 } 226 227 let time: u64 = 1_496_854_535; 228 let time_fraction: u32 = 812_000_000; 229 230 // Ensure we don't overflow here 231 let ts = Timestamp::from_unix(MaxContext, time, time_fraction); 232 233 let uuid = Uuid::new_v7(ts); 234 235 assert_eq!(uuid.get_version(), Some(Version::SortRand)); 236 assert_eq!(uuid.get_variant(), Variant::RFC4122); 237 238 let decoded_ts = uuid.get_timestamp().unwrap(); 239 240 assert_eq!(ts.to_unix(), decoded_ts.to_unix()); 241 } 242 } 243