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 22229de
Show file tree
Hide file tree
Showing 7 changed files with 248 additions and 123 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
12 changes: 6 additions & 6 deletions benches/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

extern crate test;

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

#[bench]
Expand Down Expand Up @@ -156,7 +156,7 @@ fn dt_naive(bench: &mut Bencher) {
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 @@ -190,10 +190,10 @@ fn x_combined(bench: &mut Bencher) {
black_box(DateTime::parse_str(dt3).unwrap());
black_box(DateTime::parse_str(dt4).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
74 changes: 48 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,14 @@ 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> {
// TODO fix
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 +231,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 +282,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
64 changes: 32 additions & 32 deletions src/datetime.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use crate::numbers::{float_parse_bytes, IntFloat};
use crate::TimeConfigBuilder;
use crate::{time::TimeConfig, Date, ParseError, Time};
use crate::{TimeConfigBuilder, TimestampUnit};
use std::cmp::Ordering;
use std::fmt;
use std::time::SystemTime;
Expand Down Expand Up @@ -46,10 +46,10 @@ impl PartialOrd for DateTime {
/// # Examples
///
/// ```
/// use speedate::DateTime;
/// use speedate::{DateTime, TimestampUnit};
///
/// let dt1 = DateTime::parse_str("2020-02-03T04:05:06.07").unwrap();
/// let dt2 = DateTime::parse_str("2020-02-03T04:05:06.08").unwrap();
/// let dt1 = DateTime::parse_str("2020-02-03T04:05:06.07", TimestampUnit::Infer).unwrap();
/// let dt2 = DateTime::parse_str("2020-02-03T04:05:06.08", TimestampUnit::Infer).unwrap();
///
/// assert!(dt2 > dt1);
/// ```
Expand Down Expand Up @@ -86,23 +86,23 @@ impl PartialOrd for DateTime {
/// ## Timezone Examples
///
/// ```
/// use speedate::DateTime;
/// use speedate::{DateTime, TimestampUnit};
///
/// let dt_uk_3pm = DateTime::parse_str("2000-01-01T15:00:00Z").unwrap();
/// let dt_france_4pm = DateTime::parse_str("2000-01-01T16:00:00+01:00").unwrap();
/// let dt_uk_3pm = DateTime::parse_str("2000-01-01T15:00:00Z", TimestampUnit::Infer).unwrap();
/// let dt_france_4pm = DateTime::parse_str("2000-01-01T16:00:00+01:00", TimestampUnit::Infer).unwrap();
///
/// assert!(dt_uk_3pm >= dt_france_4pm); // the two dts are actually the same instant
/// assert!(dt_uk_3pm <= dt_france_4pm); // the two dts are actually the same instant
/// assert_ne!(dt_uk_3pm, dt_france_4pm); // no equal because timezones much match for equality
///
/// let dt_uk_330pm = DateTime::parse_str("2000-01-01T15:30:00Z").unwrap();
/// let dt_uk_330pm = DateTime::parse_str("2000-01-01T15:30:00Z", TimestampUnit::Infer).unwrap();
///
/// assert!(dt_uk_330pm > dt_uk_3pm);
/// assert!(dt_uk_330pm > dt_france_4pm);
///
/// // as described in point 1 above, naïve datetimes are assumed to
/// // have the same timezone as the non-naïve
/// let dt_naive_330pm = DateTime::parse_str("2000-01-01T15:30:00").unwrap();
/// let dt_naive_330pm = DateTime::parse_str("2000-01-01T15:30:00", TimestampUnit::Infer).unwrap();
/// assert!(dt_uk_3pm < dt_naive_330pm);
/// assert!(dt_france_4pm > dt_naive_330pm);
/// ```
Expand Down Expand Up @@ -130,9 +130,9 @@ impl DateTime {
/// # Examples
///
/// ```
/// use speedate::{DateTime, Date, Time};
/// use speedate::{DateTime, Date, Time, TimestampUnit};
///
/// 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 Expand Up @@ -194,16 +194,16 @@ impl DateTime {
/// # Examples
///
/// ```
/// use speedate::DateTime;
/// use speedate::{DateTime, TimestampUnit};
///
/// 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.to_string(), "2022-01-01T12:13:14Z");
///
/// let dt = DateTime::parse_str("1641039194").unwrap();
/// let dt = DateTime::parse_str("1641039194", TimestampUnit::Infer).unwrap();
/// assert_eq!(dt.to_string(), "2022-01-01T12:13:14");
/// ```
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 datetime from bytes using RFC 3339 format
///
Expand Down Expand Up @@ -297,16 +297,16 @@ impl DateTime {
/// # Examples
///
/// ```
/// use speedate::{DateTime, Date, Time};
/// use speedate::{DateTime, Date, Time, TimestampUnit};
///
/// let dt = DateTime::parse_bytes(b"2022-01-01T12:13:14Z").unwrap();
/// let dt = DateTime::parse_bytes(b"2022-01-01T12:13:14Z", TimestampUnit::Infer).unwrap();
/// assert_eq!(dt.to_string(), "2022-01-01T12:13:14Z");
///
/// let dt = DateTime::parse_bytes(b"1641039194").unwrap();
/// let dt = DateTime::parse_bytes(b"1641039194", TimestampUnit::Infer).unwrap();
/// assert_eq!(dt.to_string(), "2022-01-01T12:13:14");
/// ```
pub fn parse_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
DateTime::parse_bytes_with_config(bytes, &TimeConfigBuilder::new().build())
pub fn parse_bytes(bytes: &[u8], timestamp_unit: TimestampUnit) -> Result<Self, ParseError> {
DateTime::parse_bytes_with_config(bytes, &TimeConfigBuilder::new().timestamp_unit(timestamp_unit).build())
}

/// Same as `DateTime::parse_bytes` but supporting TimeConfig
Expand Down Expand Up @@ -378,7 +378,7 @@ impl DateTime {
timestamp_microsecond: u32,
config: &TimeConfig,
) -> Result<Self, ParseError> {
let (mut second, extra_microsecond) = Date::timestamp_watershed(timestamp)?;
let (mut second, extra_microsecond) = Date::timestamp_into_seconds(timestamp, config.timestamp_unit)?;
let mut total_microsecond = timestamp_microsecond
.checked_add(extra_microsecond)
.ok_or(ParseError::TimeTooLarge)?;
Expand Down Expand Up @@ -477,9 +477,9 @@ impl DateTime {
/// # Examples
///
/// ```
/// use speedate::DateTime;
/// use speedate::{DateTime, TimestampUnit};
///
/// let dt = DateTime::parse_str("2022-01-01T12:13:14Z").unwrap();
/// let dt = DateTime::parse_str("2022-01-01T12:13:14Z", TimestampUnit::Infer).unwrap();
///
/// let dt2 = dt.with_timezone_offset(Some(-8 * 3600)).unwrap();
/// assert_eq!(dt2.to_string(), "2022-01-01T12:13:14-08:00");
Expand All @@ -503,9 +503,9 @@ impl DateTime {
/// # Examples
///
/// ```
/// use speedate::DateTime;
/// use speedate::{DateTime, TimestampUnit};
///
/// let dt_z = DateTime::parse_str("2000-01-01T15:00:00Z").unwrap();
/// let dt_z = DateTime::parse_str("2000-01-01T15:00:00Z", TimestampUnit::Infer).unwrap();
///
/// let dt_utc_plus2 = dt_z.in_timezone(7200).unwrap();
/// assert_eq!(dt_utc_plus2.to_string(), "2000-01-01T17:00:00+02:00");
Expand All @@ -529,13 +529,13 @@ impl DateTime {
/// # Examples
///
/// ```
/// use speedate::DateTime;
/// use speedate::{DateTime, TimestampUnit};
///
/// let dt = DateTime::from_timestamp(1_654_619_320, 123).unwrap();
/// assert_eq!(dt.to_string(), "2022-06-07T16:28:40.000123");
/// assert_eq!(dt.timestamp(), 1_654_619_320);
///
/// let dt = DateTime::parse_str("1970-01-02T00:00").unwrap();
/// let dt = DateTime::parse_str("1970-01-02T00:00", TimestampUnit::Infer).unwrap();
/// assert_eq!(dt.timestamp(), 24 * 3600);
/// ```
pub fn timestamp(&self) -> i64 {
Expand All @@ -551,15 +551,15 @@ impl DateTime {
/// # Examples
///
/// ```
/// use speedate::DateTime;
/// use speedate::{DateTime, TimestampUnit};
///
/// let dt_naive = DateTime::parse_str("1970-01-02T00:00").unwrap();
/// let dt_naive = DateTime::parse_str("1970-01-02T00:00", TimestampUnit::Infer).unwrap();
/// assert_eq!(dt_naive.timestamp_tz(), 24 * 3600);
///
/// let dt_zulu = DateTime::parse_str("1970-01-02T00:00Z").unwrap();
/// let dt_zulu = DateTime::parse_str("1970-01-02T00:00Z", TimestampUnit::Infer).unwrap();
/// assert_eq!(dt_zulu.timestamp_tz(), 24 * 3600);
///
/// let dt_plus_1 = DateTime::parse_str("1970-01-02T00:00+01:00").unwrap();
/// let dt_plus_1 = DateTime::parse_str("1970-01-02T00:00+01:00", TimestampUnit::Infer).unwrap();
/// assert_eq!(dt_plus_1.timestamp_tz(), 23 * 3600);
/// ```
pub fn timestamp_tz(&self) -> i64 {
Expand Down
Loading

0 comments on commit 22229de

Please sign in to comment.