You've already forked parse_datetime
mirror of
https://github.com/uutils/parse_datetime.git
synced 2026-06-10 16:13:15 -07:00
Merge pull request #187 from yuankunzhang/year-module
feat: add a year module
This commit is contained in:
+12
-95
@@ -28,14 +28,17 @@
|
||||
|
||||
use winnow::{
|
||||
ascii::{alpha1, multispace1},
|
||||
combinator::{alt, eof, opt, preceded, terminated, trace},
|
||||
combinator::{alt, eof, opt, preceded, terminated},
|
||||
error::ErrMode,
|
||||
stream::AsChar,
|
||||
token::take_while,
|
||||
ModalResult, Parser,
|
||||
};
|
||||
|
||||
use super::primitive::{ctx_err, dec_uint, s};
|
||||
use super::{
|
||||
primitive::{ctx_err, dec_uint, s},
|
||||
year::{year_from_str, year_str},
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Default)]
|
||||
pub struct Date {
|
||||
@@ -50,40 +53,11 @@ impl TryFrom<(&str, u32, u32)> for Date {
|
||||
/// Create a `Date` from a tuple of `(year, month, day)`.
|
||||
///
|
||||
/// Note: The `year` is represented as a `&str` to handle a specific GNU
|
||||
/// compatibility quirk. According to the GNU documentation: "if the year is
|
||||
/// 68 or smaller, then 2000 is added to it; otherwise, if year is less than
|
||||
/// 100, then 1900 is added to it." This adjustment only applies to
|
||||
/// two-digit year strings. For example, `"00"` is interpreted as `2000`,
|
||||
/// whereas `"0"`, `"000"`, or `"0000"` are interpreted as `0`.
|
||||
/// compatibility quirk. See the comment in [`year`](super::year) for more
|
||||
/// details.
|
||||
fn try_from(value: (&str, u32, u32)) -> Result<Self, Self::Error> {
|
||||
let (year_str, month, day) = value;
|
||||
|
||||
let mut year = year_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| "year must be a valid number")?;
|
||||
|
||||
// If year is 68 or smaller, then 2000 is added to it; otherwise, if year
|
||||
// is less than 100, then 1900 is added to it.
|
||||
//
|
||||
// GNU quirk: this only applies to two-digit years. For example,
|
||||
// "98-01-01" will be parsed as "1998-01-01", while "098-01-01" will be
|
||||
// parsed as "0098-01-01".
|
||||
if year_str.len() == 2 {
|
||||
if year <= 68 {
|
||||
year += 2000
|
||||
} else {
|
||||
year += 1900
|
||||
}
|
||||
}
|
||||
|
||||
// 2147485547 is the maximum value accepted by GNU, but chrono only
|
||||
// behaves like GNU for years in the range: [0, 9999], so we keep in the
|
||||
// range [0, 9999].
|
||||
//
|
||||
// See discussion in https://github.com/uutils/parse_datetime/issues/160.
|
||||
if year > 9999 {
|
||||
return Err("year must be no greater than 9999");
|
||||
}
|
||||
let year = year_from_str(year_str)?;
|
||||
|
||||
if !(1..=12).contains(&month) {
|
||||
return Err("month must be between 1 and 12");
|
||||
@@ -138,15 +112,8 @@ pub fn parse(input: &mut &str) -> ModalResult<Date> {
|
||||
///
|
||||
/// This is also used by [`combined`](super::combined).
|
||||
pub fn iso1(input: &mut &str) -> ModalResult<Date> {
|
||||
let (year, _, month, _, day) = (
|
||||
// `year` must be a `&str`, see comment in `TryFrom` impl for `Date`.
|
||||
s(take_while(1.., AsChar::is_dec_digit)),
|
||||
s('-'),
|
||||
s(dec_uint),
|
||||
s('-'),
|
||||
s(dec_uint),
|
||||
)
|
||||
.parse_next(input)?;
|
||||
let (year, _, month, _, day) =
|
||||
(year_str, s('-'), s(dec_uint), s('-'), s(dec_uint)).parse_next(input)?;
|
||||
|
||||
(year, month, day)
|
||||
.try_into()
|
||||
@@ -160,7 +127,6 @@ pub fn iso2(input: &mut &str) -> ModalResult<Date> {
|
||||
let date_str = take_while(5.., AsChar::is_dec_digit).parse_next(input)?;
|
||||
let len = date_str.len();
|
||||
|
||||
// `year` must be a `&str`, see comment in `TryFrom` impl for `Date`.
|
||||
let year = &date_str[..len - 4];
|
||||
|
||||
let month = date_str[len - 4..len - 2]
|
||||
@@ -226,7 +192,7 @@ fn literal1(input: &mut &str) -> ModalResult<Date> {
|
||||
opt(s('-')),
|
||||
s(literal_month),
|
||||
opt(terminated(
|
||||
preceded(opt(s('-')), s(take_while(1.., AsChar::is_dec_digit))),
|
||||
preceded(opt(s('-')), year_str),
|
||||
// The year must be followed by a space or end of input.
|
||||
alt((multispace1, eof)),
|
||||
)),
|
||||
@@ -254,7 +220,7 @@ fn literal2(input: &mut &str) -> ModalResult<Date> {
|
||||
// space between the comma and the year. This is probably to
|
||||
// distinguish with floats.
|
||||
opt(s(terminated(',', multispace1))),
|
||||
s(take_while(1.., AsChar::is_dec_digit)),
|
||||
year_str,
|
||||
),
|
||||
// The year must be followed by a space or end of input.
|
||||
alt((multispace1, eof)),
|
||||
@@ -272,31 +238,6 @@ fn literal2(input: &mut &str) -> ModalResult<Date> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn year(input: &mut &str) -> ModalResult<u32> {
|
||||
// 2147485547 is the maximum value accepted
|
||||
// by GNU, but chrono only behaves like GNU
|
||||
// for years in the range: [0, 9999], so we
|
||||
// keep in the range [0, 9999]
|
||||
trace(
|
||||
"year",
|
||||
s(
|
||||
take_while(1..=4, AsChar::is_dec_digit).map(|number_str: &str| {
|
||||
let year = number_str.parse::<u32>().unwrap();
|
||||
if number_str.len() == 2 {
|
||||
if year <= 68 {
|
||||
year + 2000
|
||||
} else {
|
||||
year + 1900
|
||||
}
|
||||
} else {
|
||||
year
|
||||
}
|
||||
}),
|
||||
),
|
||||
)
|
||||
.parse_next(input)
|
||||
}
|
||||
|
||||
/// Parse the name of a month (case-insensitive)
|
||||
fn literal_month(input: &mut &str) -> ModalResult<u32> {
|
||||
s(alpha1)
|
||||
@@ -639,28 +580,4 @@ mod tests {
|
||||
assert_eq!(parse(&mut s).unwrap(), reference);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_year() {
|
||||
use super::year;
|
||||
|
||||
// the minimun input length is 2
|
||||
// assert!(year(&mut "0").is_err());
|
||||
// -> GNU accepts year 0
|
||||
// test $(date -d '1-1-1' '+%Y') -eq '0001'
|
||||
|
||||
// test $(date -d '68-1-1' '+%Y') -eq '2068'
|
||||
// 2-characters are converted to 19XX/20XX
|
||||
assert_eq!(year(&mut "10").unwrap(), 2010u32);
|
||||
assert_eq!(year(&mut "68").unwrap(), 2068u32);
|
||||
assert_eq!(year(&mut "69").unwrap(), 1969u32);
|
||||
assert_eq!(year(&mut "99").unwrap(), 1999u32);
|
||||
// 3,4-characters are converted verbatim
|
||||
assert_eq!(year(&mut "468").unwrap(), 468u32);
|
||||
assert_eq!(year(&mut "469").unwrap(), 469u32);
|
||||
assert_eq!(year(&mut "1568").unwrap(), 1568u32);
|
||||
assert_eq!(year(&mut "1569").unwrap(), 1569u32);
|
||||
// consumes at most 4 characters from the input
|
||||
//assert_eq!(year(&mut "1234567").unwrap(), 1234u32);
|
||||
}
|
||||
}
|
||||
|
||||
+6
-3
@@ -18,12 +18,14 @@
|
||||
//! > - pure numbers.
|
||||
//!
|
||||
//! We put all of those in separate modules:
|
||||
//! - [`combined`]
|
||||
//! - [`date`]
|
||||
//! - [`epoch`]
|
||||
//! - [`relative`]
|
||||
//! - [`time`]
|
||||
//! - [`timezone`]
|
||||
//! - [`combined`]
|
||||
//! - [`weekday`]
|
||||
//! - [`relative`]
|
||||
//! - [`year`]
|
||||
|
||||
#![allow(deprecated)]
|
||||
|
||||
@@ -35,6 +37,7 @@ mod relative;
|
||||
mod time;
|
||||
mod timezone;
|
||||
mod weekday;
|
||||
mod year;
|
||||
|
||||
// utility modules
|
||||
mod builder;
|
||||
@@ -219,7 +222,7 @@ fn parse_item(input: &mut &str) -> ModalResult<Item> {
|
||||
relative::parse.map(Item::Relative),
|
||||
weekday::parse.map(Item::Weekday),
|
||||
timezone::parse.map(Item::TimeZone),
|
||||
date::year.map(Item::Year),
|
||||
year::parse.map(Item::Year),
|
||||
)),
|
||||
)
|
||||
.parse_next(input)
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
// For the full copyright and license information, please view the LICENSE
|
||||
// file that was distributed with this source code.
|
||||
|
||||
//! Parse a year from a string.
|
||||
//!
|
||||
//! The year must be parsed to a string first, this is to handle a specific GNU
|
||||
//! compatibility quirk. According to the GNU documentation: "if the year is 68
|
||||
//! or smaller, then 2000 is added to it; otherwise, if year is less than 100,
|
||||
//! then 1900 is added to it." This adjustment only applies to two-digit year
|
||||
//! strings. For example, `"00"` is interpreted as `2000`, whereas `"0"`,
|
||||
//! `"000"`, or `"0000"` are interpreted as `0`.
|
||||
|
||||
use winnow::{error::ErrMode, stream::AsChar, token::take_while, ModalResult, Parser};
|
||||
|
||||
use super::primitive::{ctx_err, s};
|
||||
|
||||
pub(super) fn parse(input: &mut &str) -> ModalResult<u32> {
|
||||
year_from_str(year_str(input)?).map_err(|e| ErrMode::Cut(ctx_err(e)))
|
||||
}
|
||||
|
||||
// TODO: Leverage `TryFrom` trait.
|
||||
pub(super) fn year_from_str(year_str: &str) -> Result<u32, &'static str> {
|
||||
let mut year = year_str
|
||||
.parse::<u32>()
|
||||
.map_err(|_| "year must be a valid number")?;
|
||||
|
||||
// If year is 68 or smaller, then 2000 is added to it; otherwise, if year
|
||||
// is less than 100, then 1900 is added to it.
|
||||
//
|
||||
// GNU quirk: this only applies to two-digit years. For example,
|
||||
// "98-01-01" will be parsed as "1998-01-01", whereas "098-01-01" will be
|
||||
// parsed as "0098-01-01".
|
||||
if year_str.len() == 2 {
|
||||
if year <= 68 {
|
||||
year += 2000
|
||||
} else {
|
||||
year += 1900
|
||||
}
|
||||
}
|
||||
|
||||
// 2147485547 is the maximum value accepted by GNU, but chrono only
|
||||
// behaves like GNU for years in the range: [0, 9999], so we keep in the
|
||||
// range [0, 9999].
|
||||
//
|
||||
// See discussion in https://github.com/uutils/parse_datetime/issues/160.
|
||||
if year > 9999 {
|
||||
return Err("year must be no greater than 9999");
|
||||
}
|
||||
|
||||
Ok(year)
|
||||
}
|
||||
|
||||
pub(super) fn year_str<'a>(input: &mut &'a str) -> ModalResult<&'a str> {
|
||||
s(take_while(1.., AsChar::is_dec_digit)).parse_next(input)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::parse;
|
||||
|
||||
#[test]
|
||||
fn test_year() {
|
||||
// 2-characters are converted to 19XX/20XX
|
||||
assert_eq!(parse(&mut "10").unwrap(), 2010u32);
|
||||
assert_eq!(parse(&mut "68").unwrap(), 2068u32);
|
||||
assert_eq!(parse(&mut "69").unwrap(), 1969u32);
|
||||
assert_eq!(parse(&mut "99").unwrap(), 1999u32);
|
||||
|
||||
// 3,4-characters are converted verbatim
|
||||
assert_eq!(parse(&mut "468").unwrap(), 468u32);
|
||||
assert_eq!(parse(&mut "469").unwrap(), 469u32);
|
||||
assert_eq!(parse(&mut "1568").unwrap(), 1568u32);
|
||||
assert_eq!(parse(&mut "1569").unwrap(), 1569u32);
|
||||
|
||||
// years greater than 9999 are not accepted
|
||||
assert!(parse(&mut "10000").is_err());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user