diff --git a/.github/workflows/ci-build.yml b/.github/workflows/ci-build.yml index fb9ea92..04bfe03 100644 --- a/.github/workflows/ci-build.yml +++ b/.github/workflows/ci-build.yml @@ -8,6 +8,7 @@ jobs: - uses: actions-rs/toolchain@v1 with: toolchain: stable + - run: cargo check --package ulid-cli - run: cargo test - run: cargo test --all-features - run: cargo test --no-default-features --features=std diff --git a/Cargo.toml b/Cargo.toml index 5b2f883..1592c54 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,13 +14,12 @@ repository = "https://github.com/dylanhart/ulid-rs" [features] default = ["std"] -std = ["rand", "time"] +std = ["rand"] [dependencies] serde = { version = "1.0", features = ["derive"], optional = true } rand = { version = "0.8", optional = true } # -- Sync versions with the WASM target below -- -time = { version = "0.3", optional = true } uuid = { version = "1.1", optional = true } [dev-dependencies] diff --git a/README.md b/README.md index cef1091..b4c73da 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ assert_eq!(ulid, res.unwrap()); ## Crate Features -* **`std` (default)**: Flag to toggle use of `std`, `rand`, and `time`. Disable this flag for `#[no_std]` support. +* **`std` (default)**: Flag to toggle use of `std` and `rand`. Disable this flag for `#[no_std]` support. * **`serde`**: Enables serialization and deserialization of `Ulid` types via `serde`. ULIDs are serialized using their canonical 26-character representation as defined in the ULID standard. An optional `ulid_as_u128` module is provided, which enables serialization through an `Ulid`'s inner `u128` primitive type. See the [documentation][serde_mod] and [serde docs][serde_docs] for more information. * **`uuid`**: Implements infallible conversions between ULIDs and UUIDs from the [`uuid`][uuid] crate via the [`std::convert::From`][trait_from] trait. diff --git a/benches/bench.rs b/benches/bench.rs index a98db76..ed0a091 100644 --- a/benches/bench.rs +++ b/benches/bench.rs @@ -1,9 +1,9 @@ use bencher::{benchmark_group, benchmark_main, Bencher}; -use time::OffsetDateTime; +use std::time::SystemTime; use ulid::{Generator, Ulid, ULID_LEN}; fn bench_new(b: &mut Bencher) { - b.iter(|| Ulid::new()); + b.iter(Ulid::new); } fn bench_generator_generate(b: &mut Bencher) { @@ -12,7 +12,7 @@ fn bench_generator_generate(b: &mut Bencher) { } fn bench_from_time(b: &mut Bencher) { - let time = OffsetDateTime::now_utc(); + let time = SystemTime::now(); b.iter(|| Ulid::from_datetime(time)); } diff --git a/cli/Cargo.toml b/cli/Cargo.toml index e000f95..6b730ec 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -16,3 +16,4 @@ edition = "2018" [dependencies] structopt = "0.2" ulid = { version = "*", path = ".." } +time = "0.3.11" diff --git a/cli/src/bin/ulid.rs b/cli/src/bin/ulid.rs index fd41be0..c63f641 100644 --- a/cli/src/bin/ulid.rs +++ b/cli/src/bin/ulid.rs @@ -62,7 +62,7 @@ fn generate(count: u32, monotonic: bool) { fn inspect(values: &[String]) { for val in values { - let ulid = Ulid::from_string(&val); + let ulid = Ulid::from_string(val); match ulid { Ok(ulid) => { let upper_hex = format!("{:X}", ulid.0); @@ -81,7 +81,7 @@ COMPONENTS: ", ulid.to_string(), upper_hex, - ulid.datetime(), + time::OffsetDateTime::from(ulid.datetime()), ulid.timestamp_ms(), upper_hex.chars().skip(6).collect::() ); diff --git a/src/generator.rs b/src/generator.rs index fe5123d..deb0f9b 100644 --- a/src/generator.rs +++ b/src/generator.rs @@ -1,4 +1,4 @@ -use time::OffsetDateTime; +use std::time::{Duration, SystemTime}; use std::fmt; @@ -42,7 +42,7 @@ impl Generator { /// assert!(ulid1 < ulid2); /// ``` pub fn generate(&mut self) -> Result { - let now = OffsetDateTime::now_utc(); + let now = SystemTime::now(); self.generate_from_datetime(now) } @@ -53,9 +53,9 @@ impl Generator { /// # Example /// ```rust /// use ulid::Generator; - /// use time::OffsetDateTime; + /// use std::time::SystemTime; /// - /// let dt = OffsetDateTime::now_utc(); + /// let dt = SystemTime::now(); /// let mut gen = Generator::new(); /// /// let ulid1 = gen.generate_from_datetime(dt).unwrap(); @@ -64,10 +64,7 @@ impl Generator { /// assert_eq!(ulid1.datetime(), ulid2.datetime()); /// assert!(ulid1 < ulid2); /// ``` - pub fn generate_from_datetime( - &mut self, - datetime: OffsetDateTime, - ) -> Result { + pub fn generate_from_datetime(&mut self, datetime: SystemTime) -> Result { self.generate_from_datetime_with_source(datetime, &mut rand::thread_rng()) } @@ -79,7 +76,7 @@ impl Generator { /// ```rust /// use ulid::Generator; /// use ulid::Ulid; - /// use time::OffsetDateTime; + /// use std::time::SystemTime; /// use rand::prelude::*; /// /// let mut rng = StdRng::from_entropy(); @@ -94,7 +91,7 @@ impl Generator { where R: rand::Rng, { - let now = OffsetDateTime::now_utc(); + let now = SystemTime::now(); self.generate_from_datetime_with_source(now, source) } @@ -105,10 +102,10 @@ impl Generator { /// # Example /// ```rust /// use ulid::Generator; - /// use time::OffsetDateTime; + /// use std::time::SystemTime; /// use rand::prelude::*; /// - /// let dt = OffsetDateTime::now_utc(); + /// let dt = SystemTime::now(); /// let mut rng = StdRng::from_entropy(); /// let mut gen = Generator::new(); /// @@ -120,16 +117,21 @@ impl Generator { /// ``` pub fn generate_from_datetime_with_source( &mut self, - datetime: OffsetDateTime, + datetime: SystemTime, source: &mut R, ) -> Result where R: rand::Rng, { - let last_ms = self.previous.timestamp_ms() as i128; + let last_ms = self.previous.timestamp_ms(); // maybe time went backward, or it is the same ms. // increment instead of generating a new random so that it is monotonic - if (datetime.unix_timestamp_nanos() / 1_000_000) <= last_ms { + if datetime + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or(Duration::ZERO) + .as_millis() + <= u128::from(last_ms) + { if let Some(next) = self.previous.increment() { self.previous = next; return Ok(next); @@ -170,15 +172,15 @@ impl fmt::Display for MonotonicError { #[cfg(test)] mod tests { use super::*; - use ::time::Duration; + use std::time::Duration; #[test] fn test_order_monotonic() { - let dt = OffsetDateTime::now_utc(); + let dt = SystemTime::now(); let mut gen = Generator::new(); let ulid1 = gen.generate_from_datetime(dt).unwrap(); let ulid2 = gen.generate_from_datetime(dt).unwrap(); - let ulid3 = Ulid::from_datetime(dt + Duration::milliseconds(1)); + let ulid3 = Ulid::from_datetime(dt + Duration::from_millis(1)); assert_eq!(ulid1.0 + 1, ulid2.0); assert!(ulid2 < ulid3); assert!(ulid2.timestamp_ms() < ulid3.timestamp_ms()) diff --git a/src/lib.rs b/src/lib.rs index e47a358..2524d27 100755 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,11 +37,11 @@ struct ReadMeDoctest; mod base32; #[cfg(feature = "std")] -mod time; -#[cfg(feature = "std")] mod generator; #[cfg(feature = "serde")] pub mod serde; +#[cfg(feature = "std")] +mod time; #[cfg(feature = "uuid")] mod uuid; @@ -145,13 +145,13 @@ impl Ulid { /// # Example /// ```rust /// # #[cfg(feature = "std")] { - /// use ::time::OffsetDateTime; + /// use std::time::{SystemTime, Duration}; /// use ulid::Ulid; /// - /// let dt = OffsetDateTime::now_utc(); + /// let dt = SystemTime::now(); /// let ulid = Ulid::from_datetime(dt); /// - /// assert_eq!(ulid.timestamp_ms(), (dt.unix_timestamp_nanos() / 1_000_000) as u64); + /// assert_eq!(u128::from(ulid.timestamp_ms()), dt.duration_since(SystemTime::UNIX_EPOCH).unwrap_or(Duration::ZERO).as_millis()); /// # } /// ``` pub const fn timestamp_ms(&self) -> u64 { diff --git a/src/time.rs b/src/time.rs index 6a70236..b18ccfb 100644 --- a/src/time.rs +++ b/src/time.rs @@ -1,6 +1,5 @@ -use ::time::OffsetDateTime; - use crate::{bitmask, Ulid}; +use std::time::{Duration, SystemTime}; impl Ulid { /// Creates a new Ulid with the current time (UTC) @@ -12,7 +11,7 @@ impl Ulid { /// let my_ulid = Ulid::new(); /// ``` pub fn new() -> Ulid { - Ulid::from_datetime(OffsetDateTime::now_utc()) + Ulid::from_datetime(SystemTime::now()) } /// Creates a new Ulid using data from the given random number generator @@ -26,40 +25,49 @@ impl Ulid { /// let ulid = Ulid::with_source(&mut rng); /// ``` pub fn with_source(source: &mut R) -> Ulid { - Ulid::from_datetime_with_source(OffsetDateTime::now_utc(), source) + Ulid::from_datetime_with_source(SystemTime::now(), source) } /// Creates a new Ulid with the given datetime /// - /// This can be useful when migrating data to use Ulid identifiers + /// This can be useful when migrating data to use Ulid identifiers. + /// + /// This will take the maximum of the `[SystemTime]` argument and `[SystemTime::UNIX_EPOCH]` + /// as earlier times are not valid for a Ulid timestamp /// /// # Example /// ```rust - /// use time::OffsetDateTime; + /// use std::time::{SystemTime, Duration}; /// use ulid::Ulid; /// - /// let ulid = Ulid::from_datetime(OffsetDateTime::now_utc()); + /// let ulid = Ulid::from_datetime(SystemTime::now()); /// ``` - pub fn from_datetime(datetime: OffsetDateTime) -> Ulid { + pub fn from_datetime(datetime: SystemTime) -> Ulid { Ulid::from_datetime_with_source(datetime, &mut rand::thread_rng()) } /// Creates a new Ulid with the given datetime and random number generator /// + /// This will take the maximum of the `[SystemTime]` argument and `[SystemTime::UNIX_EPOCH]` + /// as earlier times are not valid for a Ulid timestamp + /// /// # Example /// ```rust - /// use time::OffsetDateTime; + /// use std::time::{SystemTime, Duration}; /// use rand::prelude::*; /// use ulid::Ulid; /// /// let mut rng = StdRng::from_entropy(); - /// let ulid = Ulid::from_datetime_with_source(OffsetDateTime::now_utc(), &mut rng); + /// let ulid = Ulid::from_datetime_with_source(SystemTime::now(), &mut rng); /// ``` - pub fn from_datetime_with_source(datetime: OffsetDateTime, source: &mut R) -> Ulid + pub fn from_datetime_with_source(datetime: SystemTime, source: &mut R) -> Ulid where R: rand::Rng, { - let timestamp = datetime.unix_timestamp_nanos() / 1_000_000; + let timestamp = datetime + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap_or(Duration::ZERO) + .as_millis(); let timebits = (timestamp & bitmask!(Self::TIME_BITS)) as u64; let msb = timebits << 16 | u64::from(source.gen::()); @@ -71,30 +79,26 @@ impl Ulid { /// /// # Example /// ```rust - /// use time::Duration; - /// use time::OffsetDateTime; + /// use std::time::{SystemTime, Duration}; /// use ulid::Ulid; /// - /// let dt = OffsetDateTime::now_utc(); + /// let dt = SystemTime::now(); /// let ulid = Ulid::from_datetime(dt); /// - /// assert!((dt - ulid.datetime()) < Duration::milliseconds(1)); + /// assert!( + /// dt + Duration::from_millis(1) >= ulid.datetime() + /// && dt - Duration::from_millis(1) <= ulid.datetime() + /// ); /// ``` - pub fn datetime(&self) -> OffsetDateTime { + pub fn datetime(&self) -> SystemTime { let stamp = self.timestamp_ms(); - let secs = stamp / 1000; - let millis = stamp % 1000; - OffsetDateTime::from_unix_timestamp_nanos( - ((secs * 1_000_000_000) + (millis * 1_000_000)) as i128, - ) - .expect("Seconds and Milliseconds are out of range") + SystemTime::UNIX_EPOCH + Duration::from_millis(stamp) } } #[cfg(test)] mod tests { use super::*; - use ::time::Duration; #[test] fn test_dynamic() { @@ -114,7 +118,7 @@ mod tests { let mut source = StepRng::new(123, 0); let u1 = Ulid::with_source(&mut source); - let dt = OffsetDateTime::now_utc() + Duration::milliseconds(1); + let dt = SystemTime::now() + Duration::from_millis(1); let u2 = Ulid::from_datetime_with_source(dt, &mut source); let u3 = Ulid::from_datetime_with_source(dt, &mut source); @@ -124,33 +128,51 @@ mod tests { #[test] fn test_order() { - let dt = OffsetDateTime::now_utc(); + let dt = SystemTime::now(); let ulid1 = Ulid::from_datetime(dt); - let ulid2 = Ulid::from_datetime(dt + Duration::milliseconds(1)); + let ulid2 = Ulid::from_datetime(dt + Duration::from_millis(1)); assert!(ulid1 < ulid2); } #[test] fn test_datetime() { - let dt = OffsetDateTime::now_utc(); + let dt = SystemTime::now(); let ulid = Ulid::from_datetime(dt); println!("{:?}, {:?}", dt, ulid.datetime()); assert!(ulid.datetime() <= dt); - assert!(ulid.datetime() + Duration::milliseconds(1) >= dt); + assert!(ulid.datetime() + Duration::from_millis(1) >= dt); } #[test] fn test_timestamp() { - let dt = OffsetDateTime::now_utc(); + let dt = SystemTime::now(); let ulid = Ulid::from_datetime(dt); - let ts = dt.unix_timestamp() as u64 * 1000 + dt.millisecond() as u64; + let ts = dt + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis(); - assert_eq!(ulid.timestamp_ms(), ts); + assert_eq!(u128::from(ulid.timestamp_ms()), ts); } #[test] fn default_is_nil() { assert_eq!(Ulid::default(), Ulid::nil()); } + + #[test] + fn nil_is_at_unix_epoch() { + assert_eq!(Ulid::nil().datetime(), SystemTime::UNIX_EPOCH); + } + + #[test] + fn truncates_at_unix_epoch() { + let before_epoch = SystemTime::UNIX_EPOCH - Duration::from_secs(100); + assert!(before_epoch < SystemTime::UNIX_EPOCH); + assert_eq!( + Ulid::from_datetime(before_epoch).datetime(), + SystemTime::UNIX_EPOCH + ); + } }