You've already forked parse_datetime
mirror of
https://github.com/uutils/parse_datetime.git
synced 2026-06-10 16:13:15 -07:00
Refactor parse_datetime to only expose parse_datetime function
Create new file parse_relative_time.rs with the relative time helper function. Renames from_str to parse_datetime and parse_relative time. Adds function parse_datetime_at_date.
This commit is contained in:
@@ -181,10 +181,4 @@ jobs:
|
||||
run: |
|
||||
## Run it
|
||||
cd fuzz
|
||||
cargo +nightly fuzz run fuzz_from_str -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||
- name: Run fuzz_parse_datetime_from_str for XX seconds
|
||||
shell: bash
|
||||
run: |
|
||||
## Run it
|
||||
cd fuzz
|
||||
cargo +nightly fuzz run fuzz_parse_datetime_from_str -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||
cargo +nightly fuzz run fuzz_parse_datetime -- -max_total_time=${{ env.RUN_FOR }} -detect_leaks=0
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "parse_datetime"
|
||||
description = " parsing human-readable relative time strings and converting them to a Duration"
|
||||
description = "parsing human-readable time strings and converting them to a DateTime"
|
||||
version = "0.4.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
[](https://github.com/uutils/parse_datetime/blob/main/LICENSE)
|
||||
[](https://codecov.io/gh/uutils/parse_datetime)
|
||||
|
||||
A Rust crate for parsing human-readable relative time strings and converting them to a `Duration`, or parsing human-readable datetime strings and converting them to a `DateTime`.
|
||||
A Rust crate for parsing human-readable relative time strings and human-readable datetime strings and converting them to a `DateTime`.
|
||||
|
||||
## Features
|
||||
|
||||
@@ -23,23 +23,23 @@ Add this to your `Cargo.toml`:
|
||||
parse_datetime = "0.4.0"
|
||||
```
|
||||
|
||||
Then, import the crate and use the `from_str` and `from_str_at_date` functions:
|
||||
Then, import the crate and use the `parse_datetime_at_date` function:
|
||||
|
||||
```rs
|
||||
use parse_datetime::{from_str, from_str_at_date};
|
||||
use chrono::Duration;
|
||||
use chrono::{Duration, Local};
|
||||
use parse_datetime::parse_datetime_at_date;
|
||||
|
||||
let duration = from_str("+3 days");
|
||||
assert_eq!(duration.unwrap(), Duration::days(3));
|
||||
let now = Local::now();
|
||||
let after = parse_datetime_at_date(now, "+3 days");
|
||||
|
||||
let today = Utc::today().naive_utc();
|
||||
let yesterday = today - Duration::days(1);
|
||||
assert_eq!(
|
||||
from_str_at_date(yesterday, "2 days").unwrap(),
|
||||
Duration::days(1)
|
||||
(now + Duration::days(3)).naive_utc(),
|
||||
after.unwrap().naive_utc()
|
||||
);
|
||||
```
|
||||
|
||||
For DateTime parsing, import the `parse_datetime` module:
|
||||
|
||||
```rs
|
||||
use parse_datetime::parse_datetime::from_str;
|
||||
use chrono::{Local, TimeZone};
|
||||
@@ -50,7 +50,7 @@ assert_eq!(dt.unwrap(), Local.with_ymd_and_hms(2021, 2, 14, 6, 37, 47).unwrap())
|
||||
|
||||
### Supported Formats
|
||||
|
||||
The `from_str` and `from_str_at_date` functions support the following formats for relative time:
|
||||
The `parse_datetime` and `parse_datetime_at_date` functions support absolute datetime and the ollowing relative times:
|
||||
|
||||
- `num` `unit` (e.g., "-1 hour", "+3 days")
|
||||
- `unit` (e.g., "hour", "day")
|
||||
@@ -60,34 +60,28 @@ The `from_str` and `from_str_at_date` functions support the following formats fo
|
||||
- use "ago" for the past
|
||||
- use "next" or "last" with `unit` (e.g., "next week", "last year")
|
||||
- combined units with "and" or "," (e.g., "2 years and 1 month", "1 day, 2 hours" or "2 weeks 1 second")
|
||||
- unix timestamps (for example "@0" "@1344000")
|
||||
|
||||
`num` can be a positive or negative integer.
|
||||
`unit` can be one of the following: "fortnight", "week", "day", "hour", "minute", "min", "second", "sec" and their plural forms.
|
||||
|
||||
## Return Values
|
||||
|
||||
### Duration
|
||||
### parse_datetime and parse_datetime_at_date
|
||||
|
||||
The `from_str` and `from_str_at_date` functions return:
|
||||
|
||||
- `Ok(Duration)` - If the input string can be parsed as a relative time
|
||||
- `Err(ParseDurationError)` - If the input string cannot be parsed as a relative time
|
||||
|
||||
This function will return `Err(ParseDurationError::InvalidInput)` if the input string
|
||||
cannot be parsed as a relative time.
|
||||
|
||||
### parse_datetime
|
||||
|
||||
The `from_str` function returns:
|
||||
The `parse_datetime` and `parse_datetime_at_date` function return:
|
||||
|
||||
- `Ok(DateTime<FixedOffset>)` - If the input string can be parsed as a datetime
|
||||
- `Err(ParseDurationError::InvalidInput)` - If the input string cannot be parsed
|
||||
- `Err(ParseDateTimeError::InvalidInput)` - If the input string cannot be parsed
|
||||
|
||||
## Fuzzer
|
||||
|
||||
To run the fuzzer:
|
||||
|
||||
```
|
||||
$ cargo fuzz run fuzz_from_str
|
||||
$ cd fuzz
|
||||
$ cargo install cargo-fuzz
|
||||
$ cargo +nightly fuzz run fuzz_parse_datetime
|
||||
```
|
||||
|
||||
## License
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
corpus
|
||||
+3
-9
@@ -1,5 +1,5 @@
|
||||
[package]
|
||||
name = "fuzz_from_str"
|
||||
name = "fuzz_parse_datetime"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
@@ -16,13 +16,7 @@ chrono = "0.4"
|
||||
path = "../"
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_from_str"
|
||||
path = "fuzz_targets/from_str.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
[[bin]]
|
||||
name = "fuzz_parse_datetime_from_str"
|
||||
path = "fuzz_targets/parse_datetime_from_str.rs"
|
||||
name = "fuzz_parse_datetime"
|
||||
path = "fuzz_targets/parse_datetime.rs"
|
||||
test = false
|
||||
doc = false
|
||||
|
||||
@@ -4,5 +4,5 @@ use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let s = std::str::from_utf8(data).unwrap_or("");
|
||||
let _ = parse_datetime::from_str(s);
|
||||
let _ = parse_datetime::parse_datetime(s);
|
||||
});
|
||||
@@ -1,8 +0,0 @@
|
||||
#![no_main]
|
||||
|
||||
use libfuzzer_sys::fuzz_target;
|
||||
|
||||
fuzz_target!(|data: &[u8]| {
|
||||
let s = std::str::from_utf8(data).unwrap_or("");
|
||||
let _ = parse_datetime::parse_datetime::from_str(s);
|
||||
});
|
||||
+318
-299
File diff suppressed because it is too large
Load Diff
@@ -1,279 +0,0 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
use chrono::{DateTime, FixedOffset, Local, LocalResult, NaiveDateTime, TimeZone};
|
||||
|
||||
use crate::ParseDurationError;
|
||||
|
||||
/// Formats that parse input can take.
|
||||
/// Taken from `touch` coreutils
|
||||
mod format {
|
||||
pub(crate) const ISO_8601: &str = "%Y-%m-%d";
|
||||
pub(crate) const ISO_8601_NO_SEP: &str = "%Y%m%d";
|
||||
pub(crate) const POSIX_LOCALE: &str = "%a %b %e %H:%M:%S %Y";
|
||||
pub(crate) const YYYYMMDDHHMM_DOT_SS: &str = "%Y%m%d%H%M.%S";
|
||||
pub(crate) const YYYYMMDDHHMMSS: &str = "%Y-%m-%d %H:%M:%S.%f";
|
||||
pub(crate) const YYYYMMDDHHMMS: &str = "%Y-%m-%d %H:%M:%S";
|
||||
pub(crate) const YYYY_MM_DD_HH_MM: &str = "%Y-%m-%d %H:%M";
|
||||
pub(crate) const YYYYMMDDHHMM: &str = "%Y%m%d%H%M";
|
||||
pub(crate) const YYYYMMDDHHMM_OFFSET: &str = "%Y%m%d%H%M %z";
|
||||
pub(crate) const YYYYMMDDHHMM_UTC_OFFSET: &str = "%Y%m%d%H%MUTC%z";
|
||||
pub(crate) const YYYYMMDDHHMM_ZULU_OFFSET: &str = "%Y%m%d%H%MZ%z";
|
||||
pub(crate) const YYYYMMDDHHMM_HYPHENATED_OFFSET: &str = "%Y-%m-%d %H:%M %z";
|
||||
pub(crate) const YYYYMMDDHHMMS_T_SEP: &str = "%Y-%m-%dT%H:%M:%S";
|
||||
pub(crate) const UTC_OFFSET: &str = "UTC%#z";
|
||||
pub(crate) const ZULU_OFFSET: &str = "Z%#z";
|
||||
}
|
||||
|
||||
/// Loosely parses a time string and returns a `DateTime` representing the
|
||||
/// absolute time of the string.
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// * `s` - A string slice representing the time.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use chrono::{DateTime, Utc, TimeZone};
|
||||
/// let time = parse_datetime::parse_datetime::from_str("2023-06-03 12:00:01Z");
|
||||
/// assert_eq!(time.unwrap(), Utc.with_ymd_and_hms(2023, 06, 03, 12, 00, 01).unwrap());
|
||||
/// ```
|
||||
///
|
||||
/// # Supported formats
|
||||
///
|
||||
/// The function supports the following formats for time:
|
||||
///
|
||||
/// * ISO formats
|
||||
/// * timezone offsets, e.g., "UTC-0100"
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// * `Ok(DateTime<FixedOffset>)` - If the input string can be parsed as a time
|
||||
/// * `Err(ParseDurationError)` - If the input string cannot be parsed as a relative time
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// This function will return `Err(ParseDurationError::InvalidInput)` if the input string
|
||||
/// cannot be parsed as a relative time.
|
||||
///
|
||||
pub fn from_str<S: AsRef<str> + Clone>(s: S) -> Result<DateTime<FixedOffset>, ParseDurationError> {
|
||||
// TODO: Replace with a proper customiseable parsing solution using `nom`, `grmtools`, or
|
||||
// similar
|
||||
|
||||
// Formats with offsets don't require NaiveDateTime workaround
|
||||
for fmt in [
|
||||
format::YYYYMMDDHHMM_OFFSET,
|
||||
format::YYYYMMDDHHMM_HYPHENATED_OFFSET,
|
||||
format::YYYYMMDDHHMM_UTC_OFFSET,
|
||||
format::YYYYMMDDHHMM_ZULU_OFFSET,
|
||||
] {
|
||||
if let Ok(parsed) = DateTime::parse_from_str(s.as_ref(), fmt) {
|
||||
return Ok(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse formats with no offset, assume local time
|
||||
for fmt in [
|
||||
format::YYYYMMDDHHMMS_T_SEP,
|
||||
format::YYYYMMDDHHMM,
|
||||
format::YYYYMMDDHHMMS,
|
||||
format::YYYYMMDDHHMMSS,
|
||||
format::YYYY_MM_DD_HH_MM,
|
||||
format::YYYYMMDDHHMM_DOT_SS,
|
||||
format::POSIX_LOCALE,
|
||||
] {
|
||||
if let Ok(parsed) = NaiveDateTime::parse_from_str(s.as_ref(), fmt) {
|
||||
if let Ok(dt) = naive_dt_to_fixed_offset(parsed) {
|
||||
return Ok(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse epoch seconds
|
||||
if s.as_ref().bytes().next() == Some(b'@') {
|
||||
if let Ok(parsed) = NaiveDateTime::parse_from_str(&s.as_ref()[1..], "%s") {
|
||||
if let Ok(dt) = naive_dt_to_fixed_offset(parsed) {
|
||||
return Ok(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let ts = s.as_ref().to_owned() + "0000";
|
||||
// Parse date only formats - assume midnight local timezone
|
||||
for fmt in [format::ISO_8601, format::ISO_8601_NO_SEP] {
|
||||
let f = fmt.to_owned() + "%H%M";
|
||||
if let Ok(parsed) = NaiveDateTime::parse_from_str(&ts, &f) {
|
||||
if let Ok(dt) = naive_dt_to_fixed_offset(parsed) {
|
||||
return Ok(dt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse offsets. chrono doesn't provide any functionality to parse
|
||||
// offsets, so instead we replicate parse_date behaviour by getting
|
||||
// the current date with local, and create a date time string at midnight,
|
||||
// before trying offset suffixes
|
||||
let local = Local::now();
|
||||
let ts = format!("{}", local.format("%Y%m%d")) + "0000" + s.as_ref();
|
||||
for fmt in [format::UTC_OFFSET, format::ZULU_OFFSET] {
|
||||
let f = format::YYYYMMDDHHMM.to_owned() + fmt;
|
||||
if let Ok(parsed) = DateTime::parse_from_str(&ts, &f) {
|
||||
return Ok(parsed);
|
||||
}
|
||||
}
|
||||
|
||||
// Default parse and failure
|
||||
s.as_ref()
|
||||
.parse()
|
||||
.map_err(|_| (ParseDurationError::InvalidInput))
|
||||
}
|
||||
|
||||
// Convert NaiveDateTime to DateTime<FixedOffset> by assuming the offset
|
||||
// is local time
|
||||
fn naive_dt_to_fixed_offset(dt: NaiveDateTime) -> Result<DateTime<FixedOffset>, ()> {
|
||||
let now = Local::now();
|
||||
match now.offset().from_local_datetime(&dt) {
|
||||
LocalResult::Single(dt) => Ok(dt),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
static TEST_TIME: i64 = 1613371067;
|
||||
|
||||
#[cfg(test)]
|
||||
mod iso_8601 {
|
||||
use std::env;
|
||||
|
||||
use crate::{
|
||||
parse_datetime::from_str, parse_datetime::tests::TEST_TIME, ParseDurationError,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_t_sep() {
|
||||
env::set_var("TZ", "UTC");
|
||||
let dt = "2021-02-15T06:37:47";
|
||||
let actual = from_str(dt);
|
||||
assert_eq!(actual.unwrap().timestamp(), TEST_TIME);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_sep() {
|
||||
env::set_var("TZ", "UTC");
|
||||
let dt = "2021-02-15 06:37:47";
|
||||
let actual = from_str(dt);
|
||||
assert_eq!(actual.unwrap().timestamp(), TEST_TIME);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_space_sep_offset() {
|
||||
env::set_var("TZ", "UTC");
|
||||
let dt = "2021-02-14 22:37:47 -0800";
|
||||
let actual = from_str(dt);
|
||||
assert_eq!(actual.unwrap().timestamp(), TEST_TIME);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_t_sep_offset() {
|
||||
env::set_var("TZ", "UTC");
|
||||
let dt = "2021-02-14T22:37:47 -0800";
|
||||
let actual = from_str(dt);
|
||||
assert_eq!(actual.unwrap().timestamp(), TEST_TIME);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_formats() {
|
||||
let invalid_dts = vec!["NotADate", "202104", "202104-12T22:37:47"];
|
||||
for dt in invalid_dts {
|
||||
assert_eq!(from_str(dt), Err(ParseDurationError::InvalidInput));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_epoch_seconds() {
|
||||
env::set_var("TZ", "UTC");
|
||||
let dt = "@1613371067";
|
||||
let actual = from_str(dt);
|
||||
assert_eq!(actual.unwrap().timestamp(), TEST_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod offsets {
|
||||
use chrono::Local;
|
||||
|
||||
use crate::{parse_datetime::from_str, ParseDurationError};
|
||||
|
||||
#[test]
|
||||
fn test_positive_offsets() {
|
||||
let offsets = vec![
|
||||
"UTC+07:00",
|
||||
"UTC+0700",
|
||||
"UTC+07",
|
||||
"Z+07:00",
|
||||
"Z+0700",
|
||||
"Z+07",
|
||||
];
|
||||
|
||||
let expected = format!("{}{}", Local::now().format("%Y%m%d"), "0000+0700");
|
||||
for offset in offsets {
|
||||
let actual = from_str(offset).unwrap();
|
||||
assert_eq!(expected, format!("{}", actual.format("%Y%m%d%H%M%z")));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_offset() {
|
||||
let offsets = vec!["UTC+00:15", "UTC+0015", "Z+00:15", "Z+0015"];
|
||||
let expected = format!("{}{}", Local::now().format("%Y%m%d"), "0000+0015");
|
||||
for offset in offsets {
|
||||
let actual = from_str(offset).unwrap();
|
||||
assert_eq!(expected, format!("{}", actual.format("%Y%m%d%H%M%z")));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_offset_format() {
|
||||
let invalid_offsets = vec!["+0700", "UTC+2", "Z-1", "UTC+01005"];
|
||||
for offset in invalid_offsets {
|
||||
assert_eq!(from_str(offset), Err(ParseDurationError::InvalidInput));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod timestamp {
|
||||
use crate::parse_datetime::from_str;
|
||||
use chrono::{TimeZone, Utc};
|
||||
|
||||
#[test]
|
||||
fn test_positive_offsets() {
|
||||
let offsets: Vec<i64> = vec![
|
||||
0, 1, 2, 10, 100, 150, 2000, 1234400000, 1334400000, 1692582913, 2092582910,
|
||||
];
|
||||
|
||||
for offset in offsets {
|
||||
let time = Utc.timestamp_opt(offset, 0).unwrap();
|
||||
let dt = from_str(format!("@{}", offset));
|
||||
assert_eq!(dt.unwrap(), time);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to test example code presented in the README.
|
||||
mod readme_test {
|
||||
use crate::parse_datetime::from_str;
|
||||
use chrono::{Local, TimeZone};
|
||||
|
||||
#[test]
|
||||
fn test_readme_code() {
|
||||
let dt = from_str("2021-02-14 06:37:47");
|
||||
assert_eq!(
|
||||
dt.unwrap(),
|
||||
Local.with_ymd_and_hms(2021, 2, 14, 6, 37, 47).unwrap()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
-147
@@ -1,148 +1 @@
|
||||
use chrono::{Duration, Utc};
|
||||
use parse_datetime::{from_str, from_str_at_date, ParseDurationError};
|
||||
|
||||
#[test]
|
||||
fn test_invalid_input() {
|
||||
let result = from_str("foobar");
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, Err(ParseDurationError::InvalidInput));
|
||||
|
||||
let result = from_str("invalid 1");
|
||||
assert_eq!(result, Err(ParseDurationError::InvalidInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_duration_parsing() {
|
||||
assert_eq!(from_str("1 year").unwrap(), Duration::seconds(31_536_000));
|
||||
assert_eq!(
|
||||
from_str("-2 years").unwrap(),
|
||||
Duration::seconds(-63_072_000)
|
||||
);
|
||||
assert_eq!(
|
||||
from_str("2 years ago").unwrap(),
|
||||
Duration::seconds(-63_072_000)
|
||||
);
|
||||
assert_eq!(from_str("year").unwrap(), Duration::seconds(31_536_000));
|
||||
|
||||
assert_eq!(from_str("1 month").unwrap(), Duration::seconds(2_592_000));
|
||||
assert_eq!(
|
||||
from_str("1 month and 2 weeks").unwrap(),
|
||||
Duration::seconds(3_801_600)
|
||||
);
|
||||
assert_eq!(
|
||||
from_str("1 month, 2 weeks").unwrap(),
|
||||
Duration::seconds(3_801_600)
|
||||
);
|
||||
assert_eq!(
|
||||
from_str("1 months 2 weeks").unwrap(),
|
||||
Duration::seconds(3_801_600)
|
||||
);
|
||||
assert_eq!(
|
||||
from_str("1 month and 2 weeks ago").unwrap(),
|
||||
Duration::seconds(-3_801_600)
|
||||
);
|
||||
assert_eq!(from_str("2 months").unwrap(), Duration::seconds(5_184_000));
|
||||
assert_eq!(from_str("month").unwrap(), Duration::seconds(2_592_000));
|
||||
|
||||
assert_eq!(
|
||||
from_str("1 fortnight").unwrap(),
|
||||
Duration::seconds(1_209_600)
|
||||
);
|
||||
assert_eq!(
|
||||
from_str("3 fortnights").unwrap(),
|
||||
Duration::seconds(3_628_800)
|
||||
);
|
||||
assert_eq!(from_str("fortnight").unwrap(), Duration::seconds(1_209_600));
|
||||
|
||||
assert_eq!(from_str("1 week").unwrap(), Duration::seconds(604_800));
|
||||
assert_eq!(
|
||||
from_str("1 week 3 days").unwrap(),
|
||||
Duration::seconds(864_000)
|
||||
);
|
||||
assert_eq!(
|
||||
from_str("1 week 3 days ago").unwrap(),
|
||||
Duration::seconds(-864_000)
|
||||
);
|
||||
assert_eq!(from_str("-2 weeks").unwrap(), Duration::seconds(-1_209_600));
|
||||
assert_eq!(
|
||||
from_str("2 weeks ago").unwrap(),
|
||||
Duration::seconds(-1_209_600)
|
||||
);
|
||||
assert_eq!(from_str("week").unwrap(), Duration::seconds(604_800));
|
||||
|
||||
assert_eq!(from_str("1 day").unwrap(), Duration::seconds(86_400));
|
||||
assert_eq!(from_str("2 days ago").unwrap(), Duration::seconds(-172_800));
|
||||
assert_eq!(from_str("-2 days").unwrap(), Duration::seconds(-172_800));
|
||||
assert_eq!(from_str("day").unwrap(), Duration::seconds(86_400));
|
||||
|
||||
assert_eq!(from_str("1 hour").unwrap(), Duration::seconds(3_600));
|
||||
assert_eq!(from_str("1 h").unwrap(), Duration::seconds(3_600));
|
||||
assert_eq!(from_str("1 hour ago").unwrap(), Duration::seconds(-3_600));
|
||||
assert_eq!(from_str("-2 hours").unwrap(), Duration::seconds(-7_200));
|
||||
assert_eq!(from_str("hour").unwrap(), Duration::seconds(3_600));
|
||||
|
||||
assert_eq!(from_str("1 minute").unwrap(), Duration::seconds(60));
|
||||
assert_eq!(from_str("1 min").unwrap(), Duration::seconds(60));
|
||||
assert_eq!(from_str("2 minutes").unwrap(), Duration::seconds(120));
|
||||
assert_eq!(from_str("2 mins").unwrap(), Duration::seconds(120));
|
||||
assert_eq!(from_str("2m").unwrap(), Duration::seconds(120));
|
||||
assert_eq!(from_str("min").unwrap(), Duration::seconds(60));
|
||||
|
||||
assert_eq!(from_str("1 second").unwrap(), Duration::seconds(1));
|
||||
assert_eq!(from_str("1 s").unwrap(), Duration::seconds(1));
|
||||
assert_eq!(from_str("2 seconds").unwrap(), Duration::seconds(2));
|
||||
assert_eq!(from_str("2 secs").unwrap(), Duration::seconds(2));
|
||||
assert_eq!(from_str("2 sec").unwrap(), Duration::seconds(2));
|
||||
assert_eq!(from_str("sec").unwrap(), Duration::seconds(1));
|
||||
|
||||
assert_eq!(from_str("now").unwrap(), Duration::seconds(0));
|
||||
assert_eq!(from_str("today").unwrap(), Duration::seconds(0));
|
||||
|
||||
assert_eq!(
|
||||
from_str("1 year 2 months 4 weeks 3 days and 2 seconds").unwrap(),
|
||||
Duration::seconds(39_398_402)
|
||||
);
|
||||
assert_eq!(
|
||||
from_str("1 year 2 months 4 weeks 3 days and 2 seconds ago").unwrap(),
|
||||
Duration::seconds(-39_398_402)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_display_parse_duration_error_through_from_str() {
|
||||
let invalid_input = "9223372036854775807 seconds and 1 second";
|
||||
let _ = from_str(invalid_input).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_display_should_fail() {
|
||||
let invalid_input = "Thu Jan 01 12:34:00 2015";
|
||||
let error = from_str(invalid_input).unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
format!("{error}"),
|
||||
"Invalid input string: cannot be parsed as a relative time"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_from_str_at_date_day() {
|
||||
let today = Utc::now().date_naive();
|
||||
let yesterday = today - Duration::days(1);
|
||||
assert_eq!(
|
||||
from_str_at_date(yesterday, "2 days").unwrap(),
|
||||
Duration::days(1)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_input_at_date() {
|
||||
let today = Utc::now().date_naive();
|
||||
let result = from_str_at_date(today, "foobar");
|
||||
println!("{result:?}");
|
||||
assert_eq!(result, Err(ParseDurationError::InvalidInput));
|
||||
|
||||
let result = from_str_at_date(today, "invalid 1r");
|
||||
assert_eq!(result, Err(ParseDurationError::InvalidInput));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user