Skip to content

Commit 24fa78a

Browse files
authored
Merge pull request #168 from yuankunzhang/nanoseconds
feat: support nanoseconds
2 parents b5b1fd2 + c0ada32 commit 24fa78a

File tree

4 files changed

+34
-11
lines changed

4 files changed

+34
-11
lines changed

src/items/mod.rs

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,17 +190,19 @@ pub fn parse(input: &mut &str) -> ModalResult<Vec<Item>> {
190190
Ok(items)
191191
}
192192

193+
#[allow(clippy::too_many_arguments)]
193194
fn new_date(
194195
year: i32,
195196
month: u32,
196197
day: u32,
197198
hour: u32,
198199
minute: u32,
199200
second: u32,
201+
nano: u32,
200202
offset: FixedOffset,
201203
) -> Option<DateTime<FixedOffset>> {
202204
let newdate = NaiveDate::from_ymd_opt(year, month, day)
203-
.and_then(|naive| naive.and_hms_opt(hour, minute, second))?;
205+
.and_then(|naive| naive.and_hms_nano_opt(hour, minute, second, nano))?;
204206

205207
Some(DateTime::<FixedOffset>::from_local(newdate, offset))
206208
}
@@ -220,7 +222,8 @@ fn with_timezone_restore(
220222
.with_year(copy.year())?
221223
.with_hour(copy.hour())?
222224
.with_minute(copy.minute())?
223-
.with_second(copy.second())?;
225+
.with_second(copy.second())?
226+
.with_nanosecond(copy.nanosecond())?;
224227
Some(x)
225228
}
226229

@@ -274,6 +277,7 @@ fn at_date_inner(date: Vec<Item>, at: DateTime<FixedOffset>) -> Option<DateTime<
274277
d.hour(),
275278
d.minute(),
276279
d.second(),
280+
d.nanosecond(),
277281
*d.offset(),
278282
)?;
279283
}
@@ -299,6 +303,7 @@ fn at_date_inner(date: Vec<Item>, at: DateTime<FixedOffset>) -> Option<DateTime<
299303
hour,
300304
minute,
301305
second as u32,
306+
(second.fract() * 10f64.powi(9)).round() as u32,
302307
offset,
303308
)?;
304309
}
@@ -320,6 +325,7 @@ fn at_date_inner(date: Vec<Item>, at: DateTime<FixedOffset>) -> Option<DateTime<
320325
hour,
321326
minute,
322327
second as u32,
328+
(second.fract() * 10f64.powi(9)).round() as u32,
323329
offset,
324330
)?;
325331
}
@@ -495,6 +501,16 @@ mod tests {
495501
test_eq_fmt("%Y-%m-%d %H:%M:%S %:z", "Jul 17 06:14:49 2024 GMT"),
496502
);
497503

504+
assert_eq!(
505+
"2024-07-17 06:14:49.567 +00:00",
506+
test_eq_fmt("%Y-%m-%d %H:%M:%S%.f %:z", "Jul 17 06:14:49.567 2024 GMT"),
507+
);
508+
509+
assert_eq!(
510+
"2024-07-17 06:14:49.567 +00:00",
511+
test_eq_fmt("%Y-%m-%d %H:%M:%S%.f %:z", "Jul 17 06:14:49,567 2024 GMT"),
512+
);
513+
498514
assert_eq!(
499515
"2024-07-17 06:14:49 -03:00",
500516
test_eq_fmt("%Y-%m-%d %H:%M:%S %:z", "Jul 17 06:14:49 2024 BRT"),

src/items/primitive.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,9 +120,13 @@ pub(super) fn float<'a, E>(input: &mut &'a str) -> winnow::Result<f64, E>
120120
where
121121
E: ParserError<&'a str>,
122122
{
123-
(opt(one_of(['+', '-'])), digit1, opt(preceded('.', digit1)))
123+
(
124+
opt(one_of(['+', '-'])),
125+
digit1,
126+
opt(preceded(one_of(['.', ',']), digit1)),
127+
)
124128
.void()
125129
.take()
126-
.verify_map(|s: &str| s.parse().ok())
130+
.verify_map(|s: &str| s.replace(",", ".").parse().ok())
127131
.parse_next(input)
128132
}

src/items/time.rs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ use std::fmt::Display;
4141

4242
use chrono::FixedOffset;
4343
use winnow::{
44-
ascii::{digit1, float},
44+
ascii::digit1,
4545
combinator::{alt, opt, peek, preceded},
4646
error::{ContextError, ErrMode, StrContext, StrContextValue},
4747
seq,
@@ -53,7 +53,7 @@ use winnow::{
5353
use crate::ParseDateTimeError;
5454

5555
use super::{
56-
primitive::{dec_uint, s},
56+
primitive::{dec_uint, float, s},
5757
relative,
5858
};
5959

@@ -226,7 +226,14 @@ fn minute(input: &mut &str) -> ModalResult<u32> {
226226

227227
/// Parse a number of seconds (preceded by whitespace)
228228
fn second(input: &mut &str) -> ModalResult<f64> {
229-
s(float).verify(|x| *x < 60.0).parse_next(input)
229+
s(float)
230+
.verify(|x| *x < 60.0)
231+
.map(|x| {
232+
// Truncates the fractional part of seconds to 9 digits.
233+
let factor = 10f64.powi(9);
234+
(x * factor).trunc() / factor
235+
})
236+
.parse_next(input)
230237
}
231238

232239
pub(crate) fn timezone(input: &mut &str) -> ModalResult<Offset> {

tests/time.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,10 @@ pub fn check_time(input: &str, expected: &str, format: &str, base: Option<DateTi
3434
#[case::full_time_with_spaces("12 : 34 : 56", "12:34:56.000000000")]
3535
#[case::full_time_midnight("00:00:00", "00:00:00.000000000")]
3636
#[case::full_time_almost_midnight("23:59:59", "23:59:59.000000000")]
37-
/* TODO: https://github.com/uutils/parse_datetime/issues/165
3837
#[case::full_time_decimal_seconds("12:34:56.666", "12:34:56.666000000")]
3938
#[case::full_time_decimal_seconds("12:34:56.999999999", "12:34:56.999999999")]
4039
#[case::full_time_decimal_seconds("12:34:56.9999999999", "12:34:56.999999999")]
4140
#[case::full_time_decimal_seconds_after_comma("12:34:56,666", "12:34:56.666000000")]
42-
*/
4341
#[case::without_seconds("12:34", "12:34:00.000000000")]
4442
fn test_time_24h_format(#[case] input: &str, #[case] expected: &str) {
4543
check_time(input, expected, "%H:%M:%S%.9f", None);
@@ -54,10 +52,8 @@ fn test_time_24h_format(#[case] input: &str, #[case] expected: &str) {
5452
#[case::full_time_capital("12:34:56pm", "12:34:56.000000000")]
5553
#[case::full_time_midnight("00:00:00", "00:00:00.000000000")]
5654
#[case::full_time_almost_midnight("23:59:59", "23:59:59.000000000")]
57-
/* TODO: https://github.com/uutils/parse_datetime/issues/165
5855
#[case::full_time_decimal_seconds("12:34:56.666pm", "12:34:56.666000000")]
5956
#[case::full_time_decimal_seconds_after_comma("12:34:56,666pm", "12:34:56.666000000")]
60-
*/
6157
#[case::without_seconds("12:34pm", "12:34:00.000000000")]
6258
fn test_time_12h_format(#[case] input: &str, #[case] expected: &str) {
6359
check_time(input, expected, "%H:%M:%S%.9f", None);

0 commit comments

Comments
 (0)