Skip to content

Commit

Permalink
add TimestampUnit to API where needed
Browse files Browse the repository at this point in the history
  • Loading branch information
ariebovenberg committed Nov 8, 2023
1 parent ce08d32 commit 499c5a7
Show file tree
Hide file tree
Showing 7 changed files with 255 additions and 131 deletions.
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ This will be the datetime parsing logic for [pydantic-core](https://github.com/p
## Usage

```rust
use speedate::{DateTime, Date, Time};
use speedate::{DateTime, Date, Time, TimestampUnit};

fn main() {
let dt = DateTime::parse_str("2022-01-01T12:13:14Z").unwrap();
let dt = DateTime::parse_str("2022-01-01T12:13:14Z", TimestampUnit::Infer).unwrap();
assert_eq!(
dt,
DateTime {
Expand Down
28 changes: 14 additions & 14 deletions benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

extern crate test;

use speedate::{Date, DateTime, Duration, Time};
use speedate::{Date, DateTime, Duration, Time, TimestampUnit};
use test::{black_box, Bencher};

#[bench]
fn compare_datetime_ok_speedate(bench: &mut Bencher) {
let s = black_box("2000-01-01T00:02:03Z");
bench.iter(|| {
let dt = DateTime::parse_str(s).unwrap();
let dt = DateTime::parse_str(s, TimestampUnit::Infer).unwrap();
black_box((
dt.date.year,
dt.date.month,
Expand Down Expand Up @@ -78,7 +78,7 @@ macro_rules! expect_error {
fn compare_datetime_error_speedate(bench: &mut Bencher) {
let s = black_box("2000-01-01T25:02:03Z");
bench.iter(|| {
let e = expect_error!(DateTime::parse_str(s));
let e = expect_error!(DateTime::parse_str(s, TimestampUnit::Infer));
black_box(e);
})
}
Expand Down Expand Up @@ -140,23 +140,23 @@ fn compare_timestamp_ok_chrono(bench: &mut Bencher) {
fn dt_custom_tz(bench: &mut Bencher) {
let s = black_box("1997-09-09T09:09:09-09:09");
bench.iter(|| {
black_box(DateTime::parse_str(s).unwrap());
black_box(DateTime::parse_str(s, TimestampUnit::Infer).unwrap());
})
}

#[bench]
fn dt_naive(bench: &mut Bencher) {
let s = black_box("1997-09-09T09:09:09");
bench.iter(|| {
black_box(DateTime::parse_str(s).unwrap());
black_box(DateTime::parse_str(s, TimestampUnit::Infer).unwrap());
})
}

#[bench]
fn date(bench: &mut Bencher) {
let s = black_box("1997-09-09");
bench.iter(|| {
black_box(Date::parse_str(s).unwrap());
black_box(Date::parse_str(s, TimestampUnit::Infer).unwrap());
})
}

Expand Down Expand Up @@ -185,15 +185,15 @@ fn x_combined(bench: &mut Bencher) {
let t3 = black_box("12:13:14.123");
let t4 = black_box("12:13:14.123456");
bench.iter(|| {
black_box(DateTime::parse_str(dt1).unwrap());
black_box(DateTime::parse_str(dt2).unwrap());
black_box(DateTime::parse_str(dt3).unwrap());
black_box(DateTime::parse_str(dt4).unwrap());
black_box(DateTime::parse_str(dt1, TimestampUnit::Infer).unwrap());
black_box(DateTime::parse_str(dt2, TimestampUnit::Infer).unwrap());
black_box(DateTime::parse_str(dt3, TimestampUnit::Infer).unwrap());
black_box(DateTime::parse_str(dt4, TimestampUnit::Infer).unwrap());

black_box(Date::parse_str(d1).unwrap());
black_box(Date::parse_str(d2).unwrap());
black_box(Date::parse_str(d3).unwrap());
black_box(Date::parse_str(d4).unwrap());
black_box(Date::parse_str(d1, TimestampUnit::Infer).unwrap());
black_box(Date::parse_str(d2, TimestampUnit::Infer).unwrap());
black_box(Date::parse_str(d3, TimestampUnit::Infer).unwrap());
black_box(Date::parse_str(d4, TimestampUnit::Infer).unwrap());

black_box(Time::parse_str(t1).unwrap());
black_box(Time::parse_str(t2).unwrap());
Expand Down
73 changes: 47 additions & 26 deletions src/date.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::fmt;

use crate::numbers::int_parse_bytes;
use crate::{get_digit_unchecked, DateTime, ParseError};
use crate::{get_digit_unchecked, ConfigError, DateTime, ParseError};

/// A Date
///
Expand All @@ -16,10 +16,10 @@ use crate::{get_digit_unchecked, DateTime, ParseError};
/// `Date` supports equality (`==`) and inequality (`>`, `<`, `>=`, `<=`) comparisons.
///
/// ```
/// use speedate::Date;
/// use speedate::{Date, TimestampUnit};
///
/// let d1 = Date::parse_str("2022-01-01").unwrap();
/// let d2 = Date::parse_str("2022-01-02").unwrap();
/// let d1 = Date::parse_str("2022-01-01", TimestampUnit::Infer).unwrap();
/// let d2 = Date::parse_str("2022-01-02", TimestampUnit::Infer).unwrap();
/// assert!(d2 > d1);
/// ```
#[derive(Debug, PartialEq, Eq, PartialOrd, Clone)]
Expand Down Expand Up @@ -50,6 +50,26 @@ const UNIX_1600: i64 = -11_676_096_000;
// 9999-12-31T23:59:59 as a unix timestamp, used as max allowed value below
const UNIX_9999: i64 = 253_402_300_799;

#[derive(Debug, Clone, Copy, Default, PartialEq)]
pub enum TimestampUnit {
Seconds,
Milliseconds,
#[default]
Infer,
}

impl TryFrom<&str> for TimestampUnit {
type Error = ConfigError;
fn try_from(value: &str) -> Result<Self, ConfigError> {
match value.to_lowercase().as_str() {
"s" => Ok(Self::Seconds),
"ms" => Ok(Self::Milliseconds),
"infer" => Ok(Self::Infer),
_ => Err(ConfigError::UnknownTimestampUnitString),
}
}
}

impl Date {
/// Parse a date from a string using RFC 3339 format
///
Expand Down Expand Up @@ -90,16 +110,16 @@ impl Date {
/// # Examples
///
/// ```
/// use speedate::Date;
/// use speedate::{Date, TimestampUnit};
///
/// let d = Date::parse_str("2020-01-01").unwrap();
/// let d = Date::parse_str("2020-01-01", TimestampUnit::Infer).unwrap();
/// assert_eq!(d.to_string(), "2020-01-01");
/// let d = Date::parse_str("1577836800").unwrap();
/// let d = Date::parse_str("1577836800", TimestampUnit::Infer).unwrap();
/// assert_eq!(d.to_string(), "2020-01-01");
/// ```
#[inline]
pub fn parse_str(str: &str) -> Result<Self, ParseError> {
Self::parse_bytes(str.as_bytes())
pub fn parse_str(str: &str, timestamp_unit: TimestampUnit) -> Result<Self, ParseError> {
Self::parse_bytes(str.as_bytes(), timestamp_unit)
}

/// Parse a date from bytes using RFC 3339 format
Expand Down Expand Up @@ -147,20 +167,20 @@ impl Date {
/// # Examples
///
/// ```
/// use speedate::Date;
/// use speedate::{Date, TimestampUnit};
///
/// let d = Date::parse_bytes(b"2020-01-01").unwrap();
/// let d = Date::parse_bytes(b"2020-01-01", TimestampUnit::Infer).unwrap();
/// assert_eq!(d.to_string(), "2020-01-01");
///
/// let d = Date::parse_bytes(b"1577836800").unwrap();
/// let d = Date::parse_bytes(b"1577836800", TimestampUnit::Infer).unwrap();
/// assert_eq!(d.to_string(), "2020-01-01");
/// ```
#[inline]
pub fn parse_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
pub fn parse_bytes(bytes: &[u8], timestamp_unit: TimestampUnit) -> Result<Self, ParseError> {
match Self::parse_bytes_rfc3339(bytes) {
Ok(d) => Ok(d),
Err(e) => match int_parse_bytes(bytes) {
Some(int) => Self::from_timestamp(int, true),
Some(int) => Self::from_timestamp(int, true, timestamp_unit),
None => Err(e),
},
}
Expand Down Expand Up @@ -188,13 +208,13 @@ impl Date {
/// # Examples
///
/// ```
/// use speedate::Date;
/// use speedate::{Date, TimestampUnit};
///
/// let d = Date::from_timestamp(1_654_560_000, true).unwrap();
/// let d = Date::from_timestamp(1_654_560_000, true, TimestampUnit::Infer).unwrap();
/// assert_eq!(d.to_string(), "2022-06-07");
/// ```
pub fn from_timestamp(timestamp: i64, require_exact: bool) -> Result<Self, ParseError> {
let (timestamp_second, _) = Self::timestamp_watershed(timestamp)?;
pub fn from_timestamp(timestamp: i64, require_exact: bool, unit: TimestampUnit) -> Result<Self, ParseError> {
let (timestamp_second, _) = Self::timestamp_into_seconds(timestamp, unit)?;
let d = Self::from_timestamp_calc(timestamp_second)?;
if require_exact {
let time_second = timestamp_second.rem_euclid(86_400);
Expand All @@ -210,9 +230,9 @@ impl Date {
/// # Example
///
/// ```
/// use speedate::Date;
/// use speedate::{Date, TimestampUnit};
///
/// let d = Date::parse_str("2022-06-07").unwrap();
/// let d = Date::parse_str("2022-06-07", TimestampUnit::Infer).unwrap();
/// assert_eq!(d.timestamp(), 1_654_560_000);
/// ```
pub fn timestamp(&self) -> i64 {
Expand Down Expand Up @@ -261,13 +281,14 @@ impl Date {
}
}

pub(crate) fn timestamp_watershed(timestamp: i64) -> Result<(i64, u32), ParseError> {
pub(crate) fn timestamp_into_seconds(timestamp: i64, unit: TimestampUnit) -> Result<(i64, u32), ParseError> {
let ts_abs = timestamp.checked_abs().ok_or(ParseError::DateTooSmall)?;
let (mut seconds, mut microseconds) = if ts_abs > MS_WATERSHED {
(timestamp / 1_000, timestamp % 1_000 * 1000)
} else {
(timestamp, 0)
};
let (mut seconds, mut microseconds) =
if unit == TimestampUnit::Milliseconds || (unit == TimestampUnit::Infer && ts_abs > MS_WATERSHED) {
(timestamp / 1_000, timestamp % 1_000 * 1000)
} else {
(timestamp, 0)
};
if microseconds < 0 {
seconds -= 1;
microseconds += 1_000_000;
Expand Down
Loading

0 comments on commit 499c5a7

Please sign in to comment.