refactor: add jiff and optimize date type sizes

This commit is contained in:
yuankunzhang
2025-08-14 22:07:14 +08:00
parent 25f9e48e55
commit c87b6f19eb
5 changed files with 114 additions and 42 deletions
Generated
+60
View File
@@ -166,6 +166,30 @@ dependencies = [
"hashbrown",
]
[[package]]
name = "jiff"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49"
dependencies = [
"jiff-static",
"log",
"portable-atomic",
"portable-atomic-util",
"serde",
]
[[package]]
name = "jiff-static"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "js-sys"
version = "0.3.77"
@@ -214,6 +238,7 @@ name = "parse_datetime"
version = "0.11.0"
dependencies = [
"chrono",
"jiff",
"num-traits",
"regex",
"rstest",
@@ -232,6 +257,21 @@ version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "portable-atomic"
version = "1.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483"
[[package]]
name = "portable-atomic-util"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
dependencies = [
"portable-atomic",
]
[[package]]
name = "proc-macro-crate"
version = "3.3.0"
@@ -344,6 +384,26 @@ version = "1.0.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56e6fa9c48d24d85fb3de5ad847117517440f6beceb7798af16b4a87d616b8d0"
[[package]]
name = "serde"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.219"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "shlex"
version = "1.3.0"
+1
View File
@@ -13,6 +13,7 @@ regex = "1.10.4"
chrono = { version="0.4.38", default-features=false, features=["std", "alloc", "clock"] }
winnow = "0.7.10"
num-traits = "0.2.19"
jiff = { version = "0.2.15", default-features = false, features = ["std"] }
[dev-dependencies]
rstest = "0.26"
+2 -2
View File
@@ -196,8 +196,8 @@ impl DateTimeBuilder {
if let Some(date::Date { year, month, day }) = self.date {
dt = new_date(
year.map(|x| x as i32).unwrap_or(dt.year()),
month,
day,
month as u32,
day as u32,
dt.hour(),
dt.minute(),
dt.second(),
+40 -29
View File
@@ -41,13 +41,13 @@ use super::{
};
#[derive(PartialEq, Eq, Clone, Debug, Default)]
pub struct Date {
pub day: u32,
pub month: u32,
pub year: Option<u32>,
pub(crate) struct Date {
pub(crate) day: u8,
pub(crate) month: u8,
pub(crate) year: Option<u16>,
}
impl TryFrom<(&str, u32, u32)> for Date {
impl TryFrom<(&str, u8, u8)> for Date {
type Error = &'static str;
/// Create a `Date` from a tuple of `(year, month, day)`.
@@ -55,7 +55,7 @@ impl TryFrom<(&str, u32, u32)> for Date {
/// Note: The `year` is represented as a `&str` to handle a specific GNU
/// compatibility quirk. See the comment in [`year`](super::year) for more
/// details.
fn try_from(value: (&str, u32, u32)) -> Result<Self, Self::Error> {
fn try_from(value: (&str, u8, u8)) -> Result<Self, Self::Error> {
let (year_str, month, day) = value;
let year = year_from_str(year_str)?;
@@ -80,11 +80,11 @@ impl TryFrom<(&str, u32, u32)> for Date {
}
}
impl TryFrom<(u32, u32)> for Date {
impl TryFrom<(u8, u8)> for Date {
type Error = &'static str;
/// Create a `Date` from a tuple of `(month, day)`.
fn try_from((month, day): (u32, u32)) -> Result<Self, Self::Error> {
fn try_from((month, day): (u8, u8)) -> Result<Self, Self::Error> {
if !(1..=12).contains(&month) {
return Err("month must be between 1 and 12");
}
@@ -104,14 +104,27 @@ impl TryFrom<(u32, u32)> for Date {
}
}
pub fn parse(input: &mut &str) -> ModalResult<Date> {
impl TryFrom<Date> for jiff::civil::Date {
type Error = &'static str;
fn try_from(date: Date) -> Result<Self, Self::Error> {
jiff::civil::Date::new(
date.year.unwrap_or(0) as i16,
date.month as i8,
date.day as i8,
)
.map_err(|_| "date is not valid")
}
}
pub(super) fn parse(input: &mut &str) -> ModalResult<Date> {
alt((iso1, iso2, us, literal1, literal2)).parse_next(input)
}
/// Parse `[year]-[month]-[day]`
///
/// This is also used by [`combined`](super::combined).
pub fn iso1(input: &mut &str) -> ModalResult<Date> {
pub(super) fn iso1(input: &mut &str) -> ModalResult<Date> {
let (year, _, month, _, day) =
(year_str, s('-'), s(dec_uint), s('-'), s(dec_uint)).parse_next(input)?;
@@ -123,19 +136,13 @@ pub fn iso1(input: &mut &str) -> ModalResult<Date> {
/// Parse `[year][month][day]`
///
/// This is also used by [`combined`](super::combined).
pub fn iso2(input: &mut &str) -> ModalResult<Date> {
pub(super) 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();
let year = &date_str[..len - 4];
let month = date_str[len - 4..len - 2]
.parse::<u32>()
.map_err(|_| ErrMode::Cut(ctx_err("month must be a valid number")))?;
let day = date_str[len - 2..]
.parse::<u32>()
.map_err(|_| ErrMode::Cut(ctx_err("day must be a valid number")))?;
let month = month_from_str(&date_str[len - 4..len - 2])?;
let day = day_from_str(&date_str[len - 2..])?;
(year, month, day)
.try_into()
@@ -158,27 +165,21 @@ fn us(input: &mut &str) -> ModalResult<Date> {
//
// GNU quirk: interpret as [year]/[month]/[day] if the first part is at
// least 4 characters long.
let day = s2
.parse::<u32>()
.map_err(|_| ErrMode::Cut(ctx_err("day must be a valid number")))?;
let day = day_from_str(s2)?;
(s1, n, day)
.try_into()
.map_err(|e| ErrMode::Cut(ctx_err(e)))
}
Some(s2) => {
// [month]/[day]/[year]
let month = s1
.parse::<u32>()
.map_err(|_| ErrMode::Cut(ctx_err("month must be a valid number")))?;
let month = month_from_str(s1)?;
(s2, month, n)
.try_into()
.map_err(|e| ErrMode::Cut(ctx_err(e)))
}
None => {
// [month]/[day]
let month = s1
.parse::<u32>()
.map_err(|_| ErrMode::Cut(ctx_err("month must be a valid number")))?;
let month = month_from_str(s1)?;
(month, n).try_into().map_err(|e| ErrMode::Cut(ctx_err(e)))
}
}
@@ -239,7 +240,7 @@ fn literal2(input: &mut &str) -> ModalResult<Date> {
}
/// Parse the name of a month (case-insensitive)
fn literal_month(input: &mut &str) -> ModalResult<u32> {
fn literal_month(input: &mut &str) -> ModalResult<u8> {
s(alpha1)
.verify_map(|s: &str| {
Some(match s {
@@ -261,6 +262,16 @@ fn literal_month(input: &mut &str) -> ModalResult<u32> {
.parse_next(input)
}
fn month_from_str(s: &str) -> ModalResult<u8> {
s.parse::<u8>()
.map_err(|_| ErrMode::Cut(ctx_err("month must be a valid u8 number")))
}
fn day_from_str(s: &str) -> ModalResult<u8> {
s.parse::<u8>()
.map_err(|_| ErrMode::Cut(ctx_err("day must be a valid u8 number")))
}
#[cfg(test)]
mod tests {
use super::{parse, Date};
+11 -11
View File
@@ -15,10 +15,10 @@ use winnow::{stream::AsChar, token::take_while, ModalResult, Parser};
use super::primitive::s;
// TODO: Leverage `TryFrom` trait.
pub(super) fn year_from_str(year_str: &str) -> Result<u32, &'static str> {
pub(super) fn year_from_str(year_str: &str) -> Result<u16, &'static str> {
let mut year = year_str
.parse::<u32>()
.map_err(|_| "year must be a valid number")?;
.parse::<u16>()
.map_err(|_| "year must be a valid u16 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.
@@ -57,16 +57,16 @@ mod tests {
#[test]
fn test_year() {
// 2-characters are converted to 19XX/20XX
assert_eq!(year_from_str("10").unwrap(), 2010u32);
assert_eq!(year_from_str("68").unwrap(), 2068u32);
assert_eq!(year_from_str("69").unwrap(), 1969u32);
assert_eq!(year_from_str("99").unwrap(), 1999u32);
assert_eq!(year_from_str("10").unwrap(), 2010u16);
assert_eq!(year_from_str("68").unwrap(), 2068u16);
assert_eq!(year_from_str("69").unwrap(), 1969u16);
assert_eq!(year_from_str("99").unwrap(), 1999u16);
// 3,4-characters are converted verbatim
assert_eq!(year_from_str("468").unwrap(), 468u32);
assert_eq!(year_from_str("469").unwrap(), 469u32);
assert_eq!(year_from_str("1568").unwrap(), 1568u32);
assert_eq!(year_from_str("1569").unwrap(), 1569u32);
assert_eq!(year_from_str("468").unwrap(), 468u16);
assert_eq!(year_from_str("469").unwrap(), 469u16);
assert_eq!(year_from_str("1568").unwrap(), 1568u16);
assert_eq!(year_from_str("1569").unwrap(), 1569u16);
// years greater than 9999 are not accepted
assert!(year_from_str("10000").is_err());