Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support explicitly parsing timestamps as seconds/milliseconds #54

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading