Skip to content

Commit b5439d8

Browse files
authored
Merge pull request #210 from yuankunzhang/replace-chrono-with-jiff-3
(jiff series #3) refactor: use `(i64, u32)` for fractional seconds in relative time parsing
2 parents a8e26ab + d2e6e78 commit b5439d8

File tree

4 files changed

+62
-79
lines changed

4 files changed

+62
-79
lines changed

src/items/builder.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -304,9 +304,9 @@ impl DateTimeBuilder {
304304
relative::Relative::Minutes(x) => {
305305
dt += chrono::Duration::try_minutes(x.into())?;
306306
}
307-
// Seconds are special because they can be given as a float.
308-
relative::Relative::Seconds(x) => {
309-
dt += chrono::Duration::try_seconds(x as i64)?;
307+
relative::Relative::Seconds(s, ns) => {
308+
dt += chrono::Duration::seconds(s);
309+
dt += chrono::Duration::nanoseconds(ns.into());
310310
}
311311
}
312312
}

src/items/epoch.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,8 @@ pub(crate) struct Timestamp {
3737
pub(crate) nanosecond: u32,
3838
}
3939

40-
/// Parse a timestamp in the form of `1234567890` or `-1234567890.12345` or
41-
/// `1234567890,12345`.
40+
/// Parse a timestamp in the form of `@1234567890` or `@-1234567890.12345` or
41+
/// `@1234567890,12345`.
4242
pub(crate) fn parse(input: &mut &str) -> ModalResult<Timestamp> {
4343
(s("@"), opt(s(one_of(['-', '+']))), sec_and_nsec)
4444
.verify_map(|(_, sign, (sec, nsec))| {

src/items/primitive.rs

Lines changed: 0 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -114,27 +114,6 @@ where
114114
.parse_next(input)
115115
}
116116

117-
/// Parse a float number.
118-
///
119-
/// Rationale for not using `winnow::ascii::float`: the `float` parser provided
120-
/// by winnow accepts E-notation numbers (e.g., `1.23e4`), whereas GNU date
121-
/// rejects such numbers. To remain compatible with GNU date, we provide a
122-
/// custom implementation that only accepts inputs like [+-]?[0-9]+(\.[0-9]+)?.
123-
pub(super) fn float<'a, E>(input: &mut &'a str) -> winnow::Result<f64, E>
124-
where
125-
E: ParserError<&'a str>,
126-
{
127-
(
128-
opt(one_of(['+', '-'])),
129-
digit1,
130-
opt(preceded(one_of(['.', ',']), digit1)),
131-
)
132-
.void()
133-
.take()
134-
.verify_map(|s: &str| s.replace(",", ".").parse().ok())
135-
.parse_next(input)
136-
}
137-
138117
/// Parse a colon preceded by whitespace
139118
pub(super) fn colon<'a, E>(input: &mut &'a str) -> winnow::Result<(), E>
140119
where

src/items/relative.rs

Lines changed: 57 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -37,87 +37,90 @@ use winnow::{
3737
ModalResult, Parser,
3838
};
3939

40-
use super::{
41-
ordinal::ordinal,
42-
primitive::{float, s},
43-
};
40+
use super::{epoch::sec_and_nsec, ordinal::ordinal, primitive::s};
4441

4542
#[derive(Clone, Copy, Debug, PartialEq)]
46-
pub enum Relative {
43+
pub(crate) enum Relative {
4744
Years(i32),
4845
Months(i32),
4946
Days(i32),
5047
Hours(i32),
5148
Minutes(i32),
52-
// Seconds are special because they can be given as a float
53-
Seconds(f64),
49+
Seconds(i64, u32),
5450
}
5551

56-
impl Relative {
57-
// TODO: determine how to handle multiplication overflows,
58-
// using saturating_mul for now.
59-
fn mul(self, n: i32) -> Self {
60-
match self {
61-
Self::Years(x) => Self::Years(n.saturating_mul(x)),
62-
Self::Months(x) => Self::Months(n.saturating_mul(x)),
63-
Self::Days(x) => Self::Days(n.saturating_mul(x)),
64-
Self::Hours(x) => Self::Hours(n.saturating_mul(x)),
65-
Self::Minutes(x) => Self::Minutes(n.saturating_mul(x)),
66-
Self::Seconds(x) => Self::Seconds(f64::from(n) * x),
52+
impl From<Relative> for jiff::Span {
53+
fn from(relative: Relative) -> Self {
54+
match relative {
55+
Relative::Years(years) => jiff::Span::new().years(years),
56+
Relative::Months(months) => jiff::Span::new().months(months),
57+
Relative::Days(days) => jiff::Span::new().days(days),
58+
Relative::Hours(hours) => jiff::Span::new().hours(hours),
59+
Relative::Minutes(minutes) => jiff::Span::new().minutes(minutes),
60+
Relative::Seconds(seconds, nanoseconds) => {
61+
jiff::Span::new().seconds(seconds).nanoseconds(nanoseconds)
62+
}
6763
}
6864
}
6965
}
7066

71-
pub fn parse(input: &mut &str) -> ModalResult<Relative> {
67+
pub(super) fn parse(input: &mut &str) -> ModalResult<Relative> {
7268
alt((
7369
s("tomorrow").value(Relative::Days(1)),
7470
s("yesterday").value(Relative::Days(-1)),
7571
// For "today" and "now", the unit is arbitrary
7672
s("today").value(Relative::Days(0)),
7773
s("now").value(Relative::Days(0)),
7874
seconds,
79-
other,
75+
displacement,
8076
))
8177
.parse_next(input)
8278
}
8379

8480
fn seconds(input: &mut &str) -> ModalResult<Relative> {
8581
(
86-
opt(alt((s(float), ordinal.map(|x| x as f64)))),
82+
opt(alt((s('+').value(1), s('-').value(-1)))),
83+
sec_and_nsec,
8784
s(alpha1).verify(|s: &str| matches!(s, "seconds" | "second" | "sec" | "secs")),
8885
ago,
8986
)
90-
.map(|(n, _, ago)| Relative::Seconds(n.unwrap_or(1.0) * if ago { -1.0 } else { 1.0 }))
87+
.verify_map(|(sign, (sec, nsec), _, ago)| {
88+
let sec = i64::try_from(sec).ok()?;
89+
let sign = sign.unwrap_or(1) * if ago { -1 } else { 1 };
90+
let (second, nanosecond) = match (sign, nsec) {
91+
(-1, 0) => (-sec, 0),
92+
// Truncate towards minus infinity.
93+
(-1, _) => ((-sec).checked_sub(1)?, 1_000_000_000 - nsec),
94+
_ => (sec, nsec),
95+
};
96+
Some(Relative::Seconds(second, nanosecond))
97+
})
9198
.parse_next(input)
9299
}
93100

94-
fn other(input: &mut &str) -> ModalResult<Relative> {
95-
(opt(ordinal), integer_unit, ago)
96-
.map(|(n, unit, ago)| unit.mul(n.unwrap_or(1) * if ago { -1 } else { 1 }))
101+
fn displacement(input: &mut &str) -> ModalResult<Relative> {
102+
(opt(ordinal), s(alpha1), ago)
103+
.verify_map(|(n, unit, ago): (Option<i32>, &str, bool)| {
104+
let multipler = n.unwrap_or(1) * if ago { -1 } else { 1 };
105+
Some(match unit.strip_suffix('s').unwrap_or(unit) {
106+
"year" => Relative::Years(multipler),
107+
"month" => Relative::Months(multipler),
108+
"fortnight" => Relative::Days(14 * multipler),
109+
"week" => Relative::Days(7 * multipler),
110+
"day" => Relative::Days(multipler),
111+
"hour" => Relative::Hours(multipler),
112+
"minute" | "min" => Relative::Minutes(multipler),
113+
"second" | "sec" => Relative::Seconds(multipler as i64, 0),
114+
_ => return None,
115+
})
116+
})
97117
.parse_next(input)
98118
}
99119

100120
fn ago(input: &mut &str) -> ModalResult<bool> {
101121
opt(s("ago")).map(|o| o.is_some()).parse_next(input)
102122
}
103123

104-
fn integer_unit(input: &mut &str) -> ModalResult<Relative> {
105-
s(alpha1)
106-
.verify_map(|s: &str| {
107-
Some(match s.strip_suffix('s').unwrap_or(s) {
108-
"year" => Relative::Years(1),
109-
"month" => Relative::Months(1),
110-
"fortnight" => Relative::Days(14),
111-
"week" => Relative::Days(7),
112-
"day" => Relative::Days(1),
113-
"hour" => Relative::Hours(1),
114-
"minute" | "min" => Relative::Minutes(1),
115-
_ => return None,
116-
})
117-
})
118-
.parse_next(input)
119-
}
120-
121124
#[cfg(test)]
122125
mod tests {
123126
use super::{parse, Relative};
@@ -126,16 +129,17 @@ mod tests {
126129
fn all() {
127130
for (s, rel) in [
128131
// Seconds
129-
("second", Relative::Seconds(1.0)),
130-
("sec", Relative::Seconds(1.0)),
131-
("seconds", Relative::Seconds(1.0)),
132-
("secs", Relative::Seconds(1.0)),
133-
("second ago", Relative::Seconds(-1.0)),
134-
("3 seconds", Relative::Seconds(3.0)),
135-
("3.5 seconds", Relative::Seconds(3.5)),
136-
// ("+3.5 seconds", Relative::Seconds(3.5)),
137-
("3.5 seconds ago", Relative::Seconds(-3.5)),
138-
("-3.5 seconds ago", Relative::Seconds(3.5)),
132+
("second", Relative::Seconds(1, 0)),
133+
("sec", Relative::Seconds(1, 0)),
134+
("seconds", Relative::Seconds(1, 0)),
135+
("secs", Relative::Seconds(1, 0)),
136+
("second ago", Relative::Seconds(-1, 0)),
137+
("3 seconds", Relative::Seconds(3, 0)),
138+
("3.5 seconds", Relative::Seconds(3, 500_000_000)),
139+
("-3.5 seconds", Relative::Seconds(-4, 500_000_000)),
140+
("+3.5 seconds", Relative::Seconds(3, 500_000_000)),
141+
("3.5 seconds ago", Relative::Seconds(-4, 500_000_000)),
142+
("-3.5 seconds ago", Relative::Seconds(3, 500_000_000)),
139143
// Minutes
140144
("minute", Relative::Minutes(1)),
141145
("minutes", Relative::Minutes(1)),
@@ -181,7 +185,7 @@ mod tests {
181185
("now", Relative::Days(0)),
182186
// This something
183187
("this day", Relative::Days(0)),
184-
("this second", Relative::Seconds(0.0)),
188+
("this second", Relative::Seconds(0, 0)),
185189
("this year", Relative::Years(0)),
186190
// Weird stuff
187191
("next week ago", Relative::Days(-7)),

0 commit comments

Comments
 (0)