Skip to content

Commit c47ce68

Browse files
yuankunzhangcakebaker
authored andcommitted
fix: support float timestamp values
1 parent 77e1ab2 commit c47ce68

File tree

4 files changed

+49
-10
lines changed

4 files changed

+49
-10
lines changed

src/items/builder.rs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use super::{date, relative, time, weekday};
1313
#[derive(Debug, Default)]
1414
pub struct DateTimeBuilder {
1515
base: Option<DateTime<FixedOffset>>,
16-
timestamp: Option<i32>,
16+
timestamp: Option<f64>,
1717
date: Option<date::Date>,
1818
time: Option<time::Time>,
1919
weekday: Option<weekday::Weekday>,
@@ -35,7 +35,7 @@ impl DateTimeBuilder {
3535

3636
/// Timestamp value is exclusive to other date/time components. Caller of
3737
/// the builder must ensure that it is not combined with other items.
38-
pub(super) fn set_timestamp(mut self, ts: i32) -> Result<Self, &'static str> {
38+
pub(super) fn set_timestamp(mut self, ts: f64) -> Result<Self, &'static str> {
3939
self.timestamp = Some(ts);
4040
Ok(self)
4141
}
@@ -117,10 +117,24 @@ impl DateTimeBuilder {
117117
)?;
118118

119119
if let Some(ts) = self.timestamp {
120-
dt = chrono::Utc
121-
.timestamp_opt(ts.into(), 0)
122-
.unwrap()
123-
.with_timezone(&dt.timezone());
120+
// TODO: How to make the fract -> nanosecond conversion more precise?
121+
// Maybe considering using the
122+
// [rust_decimal](https://crates.io/crates/rust_decimal) crate?
123+
match chrono::Utc.timestamp_opt(ts as i64, (ts.fract() * 10f64.powi(9)).round() as u32)
124+
{
125+
chrono::MappedLocalTime::Single(t) => {
126+
// If the timestamp is valid, we can use it directly.
127+
dt = t.with_timezone(&dt.timezone());
128+
}
129+
chrono::MappedLocalTime::Ambiguous(earliest, _latest) => {
130+
// TODO: When there is a fold in the local time, which value
131+
// do we choose? For now, we use the earliest one.
132+
dt = earliest.with_timezone(&dt.timezone());
133+
}
134+
chrono::MappedLocalTime::None => {
135+
return None; // Invalid timestamp
136+
}
137+
}
124138
}
125139

126140
if let Some(date::Date { year, month, day }) = self.date {

src/items/epoch.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,33 @@
33

44
use winnow::{combinator::preceded, ModalResult, Parser};
55

6-
use super::primitive::{dec_int, s};
6+
use super::primitive::{float, s};
77

88
/// Parse a timestamp in the form of `@1234567890`.
9-
pub fn parse(input: &mut &str) -> ModalResult<i32> {
10-
s(preceded("@", dec_int)).parse_next(input)
9+
pub fn parse(input: &mut &str) -> ModalResult<f64> {
10+
s(preceded("@", float)).parse_next(input)
11+
}
12+
13+
#[cfg(test)]
14+
mod tests {
15+
use super::parse;
16+
17+
fn float_eq(a: f64, b: f64) -> bool {
18+
(a - b).abs() < f64::EPSILON
19+
}
20+
21+
#[test]
22+
fn float() {
23+
let mut input = "@1234567890";
24+
assert!(float_eq(parse(&mut input).unwrap(), 1234567890.0));
25+
26+
let mut input = "@1234567890.12345";
27+
assert!(float_eq(parse(&mut input).unwrap(), 1234567890.12345));
28+
29+
let mut input = "@1234567890,12345";
30+
assert!(float_eq(parse(&mut input).unwrap(), 1234567890.12345));
31+
32+
let mut input = "@-1234567890.12345";
33+
assert_eq!(parse(&mut input).unwrap(), -1234567890.12345);
34+
}
1135
}

src/items/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ use crate::ParseDateTimeError;
5858

5959
#[derive(PartialEq, Debug)]
6060
pub(crate) enum Item {
61-
Timestamp(i32),
61+
Timestamp(f64),
6262
Year(u32),
6363
DateTime(combined::DateTime),
6464
Date(date::Date),

src/items/primitive.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ where
8484
///
8585
/// Inputs like [+-]?0[0-9]* (e.g., `+012`) are therefore rejected. We provide a
8686
/// custom implementation to support such zero-prefixed integers.
87+
#[allow(unused)]
8788
pub(super) fn dec_int<'a, E>(input: &mut &'a str) -> winnow::Result<i32, E>
8889
where
8990
E: ParserError<&'a str>,

0 commit comments

Comments
 (0)