You've already forked parse_datetime
mirror of
https://github.com/uutils/parse_datetime.git
synced 2026-06-10 16:13:15 -07:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ea52a79163 |
Generated
+14
-13
@@ -52,14 +52,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-link",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -119,12 +119,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -144,7 +151,7 @@ checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d"
|
||||
|
||||
[[package]]
|
||||
name = "parse_datetime"
|
||||
version = "0.9.0"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
@@ -279,12 +286,6 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.5"
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "parse_datetime"
|
||||
description = "parsing human-readable time strings and converting them to a DateTime"
|
||||
version = "0.9.0"
|
||||
version = "0.7.0"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
repository = "https://github.com/uutils/parse_datetime"
|
||||
@@ -10,4 +10,4 @@ readme = "README.md"
|
||||
[dependencies]
|
||||
regex = "1.10.4"
|
||||
chrono = { version="0.4.38", default-features=false, features=["std", "alloc", "clock"] }
|
||||
nom = "8.0.0"
|
||||
nom = "7.1.3"
|
||||
|
||||
@@ -16,10 +16,11 @@ A Rust crate for parsing human-readable relative time strings and human-readable
|
||||
|
||||
## Usage
|
||||
|
||||
Add `parse_datetime` to your `Cargo.toml` with:
|
||||
Add this to your `Cargo.toml`:
|
||||
|
||||
```
|
||||
cargo add parse_datetime
|
||||
```toml
|
||||
[dependencies]
|
||||
parse_datetime = "0.6.0"
|
||||
```
|
||||
|
||||
Then, import the crate and use the `parse_datetime_at_date` function:
|
||||
|
||||
Generated
+74
-27
@@ -46,13 +46,11 @@ checksum = "a3e2c3daef883ecc1b5d58c15adae93470a91d425f3532ba1695849656af3fc1"
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.10"
|
||||
version = "1.0.79"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13208fcbb66eaeffe09b99fffbe1af420f00a7b35aa99ad683dfc1aa76145229"
|
||||
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -63,14 +61,14 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.40"
|
||||
version = "0.4.39"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a7964611d71df112cb1730f2ee67324fcf4d0fc6606acbbe9bfe06df124637c"
|
||||
checksum = "7e36cc9d416881d2e24f9a963be5fb1cd90966419ac844274161d10488b3e825"
|
||||
dependencies = [
|
||||
"android-tzdata",
|
||||
"iana-time-zone",
|
||||
"num-traits",
|
||||
"windows-link",
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -86,9 +84,21 @@ dependencies = [
|
||||
"chrono",
|
||||
"libfuzzer-sys",
|
||||
"parse_datetime",
|
||||
"rand",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.61"
|
||||
@@ -114,9 +124,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.32"
|
||||
version = "0.1.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48d1dbcbbeb6a7fec7e059840aa538bd62aaccf972c7346c4d9d2059312853d0"
|
||||
checksum = "936cfd212a0155903bcbc060e316fb6cc7cbf2e1907329391ebadc1fe0ce77c2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
@@ -138,9 +148,9 @@ checksum = "6a987beff54b60ffa6d51982e1aa1146bc42f19bd26be28b0586f252fccf5317"
|
||||
|
||||
[[package]]
|
||||
name = "libfuzzer-sys"
|
||||
version = "0.4.9"
|
||||
version = "0.4.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf78f52d400cf2d84a3a973a78a592b4adc535739e0a5597a0da6f0c357adc75"
|
||||
checksum = "9b9569d2f74e257076d8c6bfa73fb505b46b851e51ddaecc825944aa3bed17fa"
|
||||
dependencies = [
|
||||
"arbitrary",
|
||||
"cc",
|
||||
@@ -159,12 +169,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -184,13 +201,19 @@ checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3"
|
||||
|
||||
[[package]]
|
||||
name = "parse_datetime"
|
||||
version = "0.8.0"
|
||||
version = "0.6.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"nom",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.59"
|
||||
@@ -209,6 +232,36 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.11.1"
|
||||
@@ -238,12 +291,6 @@ version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.18"
|
||||
@@ -261,6 +308,12 @@ version = "1.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.93"
|
||||
@@ -325,12 +378,6 @@ dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3"
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
|
||||
@@ -7,6 +7,7 @@ edition = "2021"
|
||||
cargo-fuzz = true
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.5"
|
||||
libfuzzer-sys = "0.4.7"
|
||||
regex = "1.10.4"
|
||||
chrono = { version="0.4", default-features=false, features=["std", "alloc", "clock"] }
|
||||
|
||||
+68
-394
File diff suppressed because it is too large
Load Diff
+46
-283
@@ -1,9 +1,8 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
use crate::{parse_weekday::parse_weekday, ParseDateTimeError};
|
||||
use crate::ParseDateTimeError;
|
||||
use chrono::{
|
||||
DateTime, Datelike, Days, Duration, LocalResult, Months, NaiveDate, NaiveDateTime, NaiveTime,
|
||||
TimeZone, Weekday,
|
||||
DateTime, Datelike, Days, Duration, LocalResult, Months, NaiveDate, NaiveDateTime, TimeZone,
|
||||
};
|
||||
use regex::Regex;
|
||||
|
||||
@@ -21,8 +20,6 @@ const DAYS_PER_MONTH: [u32; 12] = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 3
|
||||
/// * `date` - A `Date` instance representing the base date for the calculation
|
||||
/// * `s` - A string slice representing the relative time.
|
||||
///
|
||||
/// If `s` is empty, the `date` is returned as-is.
|
||||
///
|
||||
/// # Supported formats
|
||||
///
|
||||
/// The function supports the following formats for relative time:
|
||||
@@ -54,15 +51,11 @@ pub fn parse_relative_time_at_date<T: TimeZone>(
|
||||
mut datetime: DateTime<T>,
|
||||
s: &str,
|
||||
) -> Result<DateTime<T>, ParseDateTimeError> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
return Ok(datetime);
|
||||
}
|
||||
let time_pattern: Regex = Regex::new(
|
||||
r"(?x)
|
||||
(?:(?P<value>[-+]?\s*\d*)\s*)?
|
||||
(?:(?P<value>[-+]?\d*)\s*)?
|
||||
(\s*(?P<direction>next|this|last)?\s*)?
|
||||
(?P<unit>years?|months?|fortnights?|weeks?|days?|hours?|h|minutes?|mins?|m|seconds?|secs?|s|yesterday|tomorrow|now|today|(?P<weekday>[a-z]{3,9}))\b
|
||||
(?P<unit>years?|months?|fortnights?|weeks?|days?|hours?|h|minutes?|mins?|m|seconds?|secs?|s|yesterday|tomorrow|now|today)
|
||||
(\s*(?P<separator>and|,)?\s*)?
|
||||
(\s*(?P<ago>ago)?)?",
|
||||
)?;
|
||||
@@ -74,26 +67,20 @@ pub fn parse_relative_time_at_date<T: TimeZone>(
|
||||
for capture in time_pattern.captures_iter(s) {
|
||||
captures_processed += 1;
|
||||
|
||||
let value_str: String = capture
|
||||
let value_str = capture
|
||||
.name("value")
|
||||
.ok_or(ParseDateTimeError::InvalidInput)?
|
||||
.as_str()
|
||||
.chars()
|
||||
.filter(|c| !c.is_whitespace()) // Remove potential space between +/- and number
|
||||
.collect();
|
||||
let direction = capture.name("direction").map_or("", |d| d.as_str());
|
||||
.as_str();
|
||||
let value = if value_str.is_empty() {
|
||||
if direction == "this" {
|
||||
0
|
||||
} else {
|
||||
1
|
||||
}
|
||||
1
|
||||
} else {
|
||||
value_str
|
||||
.parse::<i64>()
|
||||
.map_err(|_| ParseDateTimeError::InvalidInput)?
|
||||
};
|
||||
|
||||
let direction = capture.name("direction").map_or("", |d| d.as_str());
|
||||
|
||||
if direction == "last" {
|
||||
is_ago = true;
|
||||
}
|
||||
@@ -107,26 +94,27 @@ pub fn parse_relative_time_at_date<T: TimeZone>(
|
||||
is_ago = true;
|
||||
}
|
||||
|
||||
let new_datetime = match unit {
|
||||
"years" | "year" => add_months(datetime, value * 12, is_ago),
|
||||
"months" | "month" => add_months(datetime, value, is_ago),
|
||||
"fortnights" | "fortnight" => add_days(datetime, value * 14, is_ago),
|
||||
"weeks" | "week" => add_days(datetime, value * 7, is_ago),
|
||||
"days" | "day" => add_days(datetime, value, is_ago),
|
||||
"hours" | "hour" | "h" => add_duration(datetime, Duration::hours(value), is_ago),
|
||||
"minutes" | "minute" | "mins" | "min" | "m" => {
|
||||
add_duration(datetime, Duration::minutes(value), is_ago)
|
||||
let new_datetime = if direction == "this" {
|
||||
add_days(datetime, 0, is_ago)
|
||||
} else {
|
||||
match unit {
|
||||
"years" | "year" => add_months(datetime, value * 12, is_ago),
|
||||
"months" | "month" => add_months(datetime, value, is_ago),
|
||||
"fortnights" | "fortnight" => add_days(datetime, value * 14, is_ago),
|
||||
"weeks" | "week" => add_days(datetime, value * 7, is_ago),
|
||||
"days" | "day" => add_days(datetime, value, is_ago),
|
||||
"hours" | "hour" | "h" => add_duration(datetime, Duration::hours(value), is_ago),
|
||||
"minutes" | "minute" | "mins" | "min" | "m" => {
|
||||
add_duration(datetime, Duration::minutes(value), is_ago)
|
||||
}
|
||||
"seconds" | "second" | "secs" | "sec" | "s" => {
|
||||
add_duration(datetime, Duration::seconds(value), is_ago)
|
||||
}
|
||||
"yesterday" => add_days(datetime, 1, true),
|
||||
"tomorrow" => add_days(datetime, 1, false),
|
||||
"now" | "today" => Some(datetime),
|
||||
_ => None,
|
||||
}
|
||||
"seconds" | "second" | "secs" | "sec" | "s" => {
|
||||
add_duration(datetime, Duration::seconds(value), is_ago)
|
||||
}
|
||||
"yesterday" => add_days(datetime, 1, true),
|
||||
"tomorrow" => add_days(datetime, 1, false),
|
||||
"now" | "today" => Some(datetime),
|
||||
_ => capture
|
||||
.name("weekday")
|
||||
.and_then(|weekday| parse_weekday(weekday.as_str()))
|
||||
.and_then(|weekday| adjust_for_weekday(datetime, weekday, value, is_ago)),
|
||||
};
|
||||
datetime = match new_datetime {
|
||||
Some(dt) => dt,
|
||||
@@ -151,25 +139,6 @@ pub fn parse_relative_time_at_date<T: TimeZone>(
|
||||
}
|
||||
}
|
||||
|
||||
fn adjust_for_weekday<T: TimeZone>(
|
||||
mut datetime: DateTime<T>,
|
||||
weekday: Weekday,
|
||||
mut amount: i64,
|
||||
is_ago: bool,
|
||||
) -> Option<DateTime<T>> {
|
||||
let mut same_day = true;
|
||||
// last/this/next <weekday> truncates the time to midnight
|
||||
datetime = datetime.with_time(NaiveTime::MIN).unwrap();
|
||||
while datetime.weekday() != weekday {
|
||||
datetime = add_days(datetime, 1, is_ago)?;
|
||||
same_day = false;
|
||||
}
|
||||
if !same_day && 0 < amount {
|
||||
amount -= 1;
|
||||
}
|
||||
add_days(datetime, amount * 7, is_ago)
|
||||
}
|
||||
|
||||
fn add_months<T: TimeZone>(
|
||||
datetime: DateTime<T>,
|
||||
months: i64,
|
||||
@@ -309,12 +278,6 @@ mod tests {
|
||||
Ok(parsed - now)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_empty_string() {
|
||||
let now = Utc::now();
|
||||
assert_eq!(parse_relative_time_at_date(now, "").unwrap(), now);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_years() {
|
||||
let now = Utc::now();
|
||||
@@ -322,7 +285,10 @@ mod tests {
|
||||
parse_relative_time_at_date(now, "1 year").unwrap(),
|
||||
now.checked_add_months(Months::new(12)).unwrap()
|
||||
);
|
||||
assert_eq!(parse_relative_time_at_date(now, "this year").unwrap(), now);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this year").unwrap(),
|
||||
now.checked_add_months(Months::new(0)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "-2 years").unwrap(),
|
||||
now.checked_sub_months(Months::new(24)).unwrap()
|
||||
@@ -353,24 +319,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_months() {
|
||||
use crate::parse_relative_time::add_months;
|
||||
|
||||
let now = Utc::now();
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 month").unwrap(),
|
||||
add_months(now, 1, false).unwrap(),
|
||||
now.checked_add_months(Months::new(1)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this month").unwrap(),
|
||||
now.checked_add_months(Months::new(0)).unwrap()
|
||||
);
|
||||
assert_eq!(parse_relative_time_at_date(now, "this month").unwrap(), now);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 month and 2 weeks").unwrap(),
|
||||
add_months(now, 1, false)
|
||||
now.checked_add_months(Months::new(1))
|
||||
.unwrap()
|
||||
.checked_add_days(Days::new(14))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 month and 2 weeks ago").unwrap(),
|
||||
add_months(now, 1, true)
|
||||
now.checked_sub_months(Months::new(1))
|
||||
.unwrap()
|
||||
.checked_sub_days(Days::new(14))
|
||||
.unwrap()
|
||||
@@ -381,7 +348,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "month").unwrap(),
|
||||
add_months(now, 1, false).unwrap(),
|
||||
now.checked_add_months(Months::new(1)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -535,19 +502,6 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_spaces() {
|
||||
let now = Utc::now();
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "+ 1 hour").unwrap(),
|
||||
now.checked_add_signed(Duration::hours(1)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "- 1 hour").unwrap(),
|
||||
now.checked_sub_signed(Duration::hours(1)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_input() {
|
||||
let result = parse_duration("foobar");
|
||||
@@ -608,8 +562,6 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_duration_parsing() {
|
||||
use crate::parse_relative_time::add_months;
|
||||
|
||||
let now = Utc::now();
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 year").unwrap(),
|
||||
@@ -630,25 +582,25 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 month").unwrap(),
|
||||
add_months(now, 1, false).unwrap(),
|
||||
now.checked_add_months(Months::new(1)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 month and 2 weeks").unwrap(),
|
||||
add_months(now, 1, false)
|
||||
now.checked_add_months(Months::new(1))
|
||||
.unwrap()
|
||||
.checked_add_days(Days::new(14))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 month, 2 weeks").unwrap(),
|
||||
add_months(now, 1, false)
|
||||
now.checked_add_months(Months::new(1))
|
||||
.unwrap()
|
||||
.checked_add_days(Days::new(14))
|
||||
.unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 months 2 weeks").unwrap(),
|
||||
add_months(now, 1, false)
|
||||
now.checked_add_months(Months::new(1))
|
||||
.unwrap()
|
||||
.checked_add_days(Days::new(14))
|
||||
.unwrap()
|
||||
@@ -666,7 +618,7 @@ mod tests {
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "month").unwrap(),
|
||||
add_months(now, 1, false).unwrap(),
|
||||
now.checked_add_months(Months::new(1)).unwrap()
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -832,193 +784,4 @@ mod tests {
|
||||
let result = parse_relative_time_at_date(now, "invalid 1r");
|
||||
assert_eq!(result, Err(ParseDateTimeError::InvalidInput));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_relative_time_at_date_this_weekday() {
|
||||
// Jan 1 2025 is a Wed
|
||||
let now = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
));
|
||||
// Check "this <same weekday>"
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this wednesday").unwrap(),
|
||||
now
|
||||
);
|
||||
assert_eq!(parse_relative_time_at_date(now, "this wed").unwrap(), now);
|
||||
// Other days
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this thursday").unwrap(),
|
||||
now.checked_add_days(Days::new(1)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this thur").unwrap(),
|
||||
now.checked_add_days(Days::new(1)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this thu").unwrap(),
|
||||
now.checked_add_days(Days::new(1)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this friday").unwrap(),
|
||||
now.checked_add_days(Days::new(2)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this fri").unwrap(),
|
||||
now.checked_add_days(Days::new(2)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this saturday").unwrap(),
|
||||
now.checked_add_days(Days::new(3)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this sat").unwrap(),
|
||||
now.checked_add_days(Days::new(3)).unwrap()
|
||||
);
|
||||
// "this" with a day of the week that comes before today should return the next instance of
|
||||
// that day
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this sunday").unwrap(),
|
||||
now.checked_add_days(Days::new(4)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this sun").unwrap(),
|
||||
now.checked_add_days(Days::new(4)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this monday").unwrap(),
|
||||
now.checked_add_days(Days::new(5)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this mon").unwrap(),
|
||||
now.checked_add_days(Days::new(5)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this tuesday").unwrap(),
|
||||
now.checked_add_days(Days::new(6)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this tue").unwrap(),
|
||||
now.checked_add_days(Days::new(6)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_relative_time_at_date_last_weekday() {
|
||||
// Jan 1 2025 is a Wed
|
||||
let now = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
));
|
||||
// Check "last <same weekday>"
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "last wed").unwrap(),
|
||||
now.checked_sub_days(Days::new(7)).unwrap()
|
||||
);
|
||||
// Check "last <day after today>"
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "last thu").unwrap(),
|
||||
now.checked_sub_days(Days::new(6)).unwrap()
|
||||
);
|
||||
// Check "last <day before today>"
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "last tue").unwrap(),
|
||||
now.checked_sub_days(Days::new(1)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_relative_time_at_date_next_weekday() {
|
||||
// Jan 1 2025 is a Wed
|
||||
let now = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
));
|
||||
// Check "next <same weekday>"
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "next wed").unwrap(),
|
||||
now.checked_add_days(Days::new(7)).unwrap()
|
||||
);
|
||||
// Check "next <day after today>"
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "next thu").unwrap(),
|
||||
now.checked_add_days(Days::new(1)).unwrap()
|
||||
);
|
||||
// Check "next <day before today>"
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "next tue").unwrap(),
|
||||
now.checked_add_days(Days::new(6)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_relative_time_at_date_number_weekday() {
|
||||
// Jan 1 2025 is a Wed
|
||||
let now = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
));
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 wed").unwrap(),
|
||||
now.checked_add_days(Days::new(7)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 thu").unwrap(),
|
||||
now.checked_add_days(Days::new(1)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "1 tue").unwrap(),
|
||||
now.checked_add_days(Days::new(6)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "2 wed").unwrap(),
|
||||
now.checked_add_days(Days::new(14)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "2 thu").unwrap(),
|
||||
now.checked_add_days(Days::new(8)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "2 tue").unwrap(),
|
||||
now.checked_add_days(Days::new(13)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_relative_time_at_date_weekday_truncates_time() {
|
||||
// Jan 1 2025 is a Wed
|
||||
let now = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
|
||||
));
|
||||
let now_midnight = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
));
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this wed").unwrap(),
|
||||
now_midnight
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "last wed").unwrap(),
|
||||
now_midnight.checked_sub_days(Days::new(7)).unwrap()
|
||||
);
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "next wed").unwrap(),
|
||||
now_midnight.checked_add_days(Days::new(7)).unwrap()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_relative_time_at_date_invalid_weekday() {
|
||||
// Jan 1 2025 is a Wed
|
||||
let now = Utc.from_utc_datetime(&NaiveDateTime::new(
|
||||
NaiveDate::from_ymd_opt(2025, 1, 1).unwrap(),
|
||||
NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
|
||||
));
|
||||
assert_eq!(
|
||||
parse_relative_time_at_date(now, "this fooday"),
|
||||
Err(ParseDateTimeError::InvalidInput)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+13
-11
@@ -4,7 +4,7 @@ use regex::Regex;
|
||||
mod time_only_formats {
|
||||
pub const HH_MM: &str = "%R";
|
||||
pub const HH_MM_SS: &str = "%T";
|
||||
pub const TWELVE_HOUR: &str = "%r";
|
||||
pub const TWELVEHOUR: &str = "%r";
|
||||
}
|
||||
|
||||
/// Convert a military time zone string to a time zone offset.
|
||||
@@ -55,14 +55,16 @@ fn parse_time_with_offset_multi(
|
||||
for fmt in [
|
||||
time_only_formats::HH_MM,
|
||||
time_only_formats::HH_MM_SS,
|
||||
time_only_formats::TWELVE_HOUR,
|
||||
time_only_formats::TWELVEHOUR,
|
||||
] {
|
||||
let Ok(parsed) = NaiveTime::parse_from_str(s, fmt) else {
|
||||
continue;
|
||||
let parsed = match NaiveTime::parse_from_str(s, fmt) {
|
||||
Ok(t) => t,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let parsed_dt = date.date_naive().and_time(parsed);
|
||||
if let Some(dt) = offset.from_local_datetime(&parsed_dt).single() {
|
||||
return Some(dt);
|
||||
match offset.from_local_datetime(&parsed_dt).single() {
|
||||
Some(dt) => return Some(dt),
|
||||
None => continue,
|
||||
}
|
||||
}
|
||||
None
|
||||
@@ -82,7 +84,7 @@ pub(crate) fn parse_time_only(date: DateTime<Local>, s: &str) -> Option<DateTime
|
||||
offset_in_sec += minutes.as_str().parse::<i32>().unwrap() * 60;
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
};
|
||||
offset_in_sec *= if &captures["sign"] == "-" { -1 } else { 1 };
|
||||
FixedOffset::east_opt(offset_in_sec)
|
||||
}
|
||||
@@ -138,7 +140,7 @@ mod tests {
|
||||
let parsed_time = parse_time_only(get_test_date(), "21:04")
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
assert_eq!(parsed_time, 1709499840);
|
||||
assert_eq!(parsed_time, 1709499840)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -185,7 +187,7 @@ mod tests {
|
||||
let parsed_time = parse_time_only(get_test_date(), "21:04:30")
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
assert_eq!(parsed_time, 1709499870);
|
||||
assert_eq!(parsed_time, 1709499870)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -194,7 +196,7 @@ mod tests {
|
||||
let parsed_time = parse_time_only(get_test_date(), "21:04:30 +0530")
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
assert_eq!(parsed_time, 1709480070);
|
||||
assert_eq!(parsed_time, 1709480070)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -203,6 +205,6 @@ mod tests {
|
||||
let parsed_time = parse_time_only(get_test_date(), "9:04:00 PM")
|
||||
.unwrap()
|
||||
.timestamp();
|
||||
assert_eq!(parsed_time, 1709499840);
|
||||
assert_eq!(parsed_time, 1709499840)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ use nom::character::complete::{char, digit1};
|
||||
use nom::combinator::all_consuming;
|
||||
use nom::multi::fold_many0;
|
||||
use nom::sequence::preceded;
|
||||
use nom::{self, IResult, Parser};
|
||||
use nom::sequence::tuple;
|
||||
use nom::{self, IResult};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum ParseTimestampError {
|
||||
@@ -54,7 +55,7 @@ pub(crate) fn parse_timestamp(s: &str) -> Result<i64, ParseTimestampError> {
|
||||
|
||||
let res: IResult<&str, (char, &str)> = all_consuming(preceded(
|
||||
char('@'),
|
||||
(
|
||||
tuple((
|
||||
// Note: to stay compatible with gnu date this code allows
|
||||
// multiple + and - and only considers the last one
|
||||
fold_many0(
|
||||
@@ -66,9 +67,8 @@ pub(crate) fn parse_timestamp(s: &str) -> Result<i64, ParseTimestampError> {
|
||||
|_, c| c,
|
||||
),
|
||||
digit1,
|
||||
),
|
||||
))
|
||||
.parse(s);
|
||||
)),
|
||||
))(s);
|
||||
|
||||
let (_, (sign, number_str)) = res?;
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ use chrono::Weekday;
|
||||
use nom::branch::alt;
|
||||
use nom::bytes::complete::tag;
|
||||
use nom::combinator::value;
|
||||
use nom::{self, IResult, Parser};
|
||||
use nom::{self, IResult};
|
||||
|
||||
// Helper macro to simplify tag matching
|
||||
macro_rules! tag_match {
|
||||
@@ -25,8 +25,7 @@ pub(crate) fn parse_weekday(s: &str) -> Option<Weekday> {
|
||||
tag_match!(Weekday::Fri, "friday", "fri"),
|
||||
tag_match!(Weekday::Sat, "saturday", "sat"),
|
||||
tag_match!(Weekday::Sun, "sunday", "sun"),
|
||||
)))
|
||||
.parse(s);
|
||||
)))(s);
|
||||
|
||||
match parse_result {
|
||||
Ok((_, weekday)) => Some(weekday),
|
||||
@@ -64,9 +63,9 @@ mod tests {
|
||||
|
||||
for (name, weekday) in days {
|
||||
assert_eq!(parse_weekday(name), Some(weekday));
|
||||
assert_eq!(parse_weekday(&format!(" {name}")), Some(weekday));
|
||||
assert_eq!(parse_weekday(&format!(" {name} ")), Some(weekday));
|
||||
assert_eq!(parse_weekday(&format!("{name} ")), Some(weekday));
|
||||
assert_eq!(parse_weekday(&format!(" {}", name)), Some(weekday));
|
||||
assert_eq!(parse_weekday(&format!(" {} ", name)), Some(weekday));
|
||||
assert_eq!(parse_weekday(&format!("{} ", name)), Some(weekday));
|
||||
|
||||
let (left, right) = name.split_at(1);
|
||||
let (test_str1, test_str2) = (
|
||||
|
||||
Reference in New Issue
Block a user