Skip to content

Commit 7592100

Browse files
committed
Add test_month to test relative dates
1 parent cc7e40b commit 7592100

File tree

3 files changed

+84
-43
lines changed

3 files changed

+84
-43
lines changed

src/items/mod.rs

Lines changed: 41 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ where
138138

139139
// Parse an item
140140
pub fn parse_one(input: &mut &str) -> PResult<Item> {
141-
// eprintln!("--- > parse_one {input}");
141+
eprintln!("--- > parse_one {input}");
142142
let result = alt((
143143
combined::parse.map(Item::DateTime),
144144
date::parse.map(Item::Date),
@@ -151,7 +151,7 @@ pub fn parse_one(input: &mut &str) -> PResult<Item> {
151151
))
152152
.parse_next(input)?;
153153

154-
//eprintln!("--- < parse_one {input} {result:?}");
154+
eprintln!("--- < parse_one {input} {result:?}");
155155
Ok(result)
156156
}
157157

@@ -169,7 +169,6 @@ fn with_timezone_restore(
169169
) -> Option<DateTime<FixedOffset>> {
170170
let offset: FixedOffset = chrono::FixedOffset::from(offset);
171171
let copy = at;
172-
//eprintln!("with_timezone {at} {offset}");
173172
let x = at
174173
.with_timezone(&offset)
175174
.with_day(copy.day())?
@@ -181,14 +180,11 @@ fn with_timezone_restore(
181180
Some(x)
182181
}
183182

184-
pub(crate) fn at_date(
185-
date: Vec<Item>,
186-
mut d: DateTime<FixedOffset>,
187-
) -> Result<DateTime<FixedOffset>, ParseDateTimeError> {
188-
d = d.with_hour(0).unwrap_or(d);
189-
d = d.with_minute(0).unwrap_or(d);
190-
d = d.with_second(0).unwrap_or(d);
191-
d = d.with_nanosecond(0).unwrap_or(d);
183+
fn at_date_inner(date: Vec<Item>, mut d: DateTime<FixedOffset>) -> Option<DateTime<FixedOffset>> {
184+
d = d.with_hour(0).unwrap();
185+
d = d.with_minute(0).unwrap();
186+
d = d.with_second(0).unwrap();
187+
d = d.with_nanosecond(0).unwrap();
192188

193189
for item in date {
194190
match item {
@@ -199,13 +195,12 @@ pub(crate) fn at_date(
199195
.with_timezone(&d.timezone())
200196
}
201197
Item::Date(date::Date { day, month, year }) => {
202-
d = d.with_day(day).unwrap_or(d);
203-
d = d.with_month(month).unwrap_or(d);
204-
d = year
205-
// converts i32 to u32 safely
206-
.and_then(|year: u32| <u32 as TryInto<i32>>::try_into(year).ok())
207-
.and_then(|year| d.with_year(year))
208-
.unwrap_or(d);
198+
d = d.with_day(day)?;
199+
d = d.with_month(month)?;
200+
if let Some(year) = year {
201+
let y = year as i32;
202+
d = d.with_year(y)?
203+
}
209204
}
210205
Item::DateTime(combined::DateTime {
211206
date: date::Date { day, month, year },
@@ -222,16 +217,14 @@ pub(crate) fn at_date(
222217
let offset: FixedOffset = chrono::FixedOffset::from(offset);
223218
d = d.with_timezone(&offset);
224219
}
225-
d = d.with_day(day).unwrap_or(d);
226-
d = d.with_month(month).unwrap_or(d);
227-
d = year
228-
// converts i32 to u32 safely
229-
.and_then(|year: u32| <u32 as TryInto<i32>>::try_into(year).ok())
230-
.and_then(|year| d.with_year(year))
231-
.unwrap_or(d);
232-
d = d.with_hour(hour).unwrap_or(d);
233-
d = d.with_minute(minute).unwrap_or(d);
234-
d = d.with_second(second as u32).unwrap_or(d);
220+
d = d.with_day(day)?;
221+
d = d.with_month(month)?;
222+
d = d.with_hour(hour)?;
223+
d = d.with_minute(minute)?;
224+
d = d.with_second(second as u32)?;
225+
if let Some(year) = year {
226+
d = d.with_year(year as i32)?
227+
}
235228
}
236229
Item::Year(year) => d = d.with_year(year as i32).unwrap_or(d),
237230
Item::Time(time::Time {
@@ -242,11 +235,11 @@ pub(crate) fn at_date(
242235
}) => {
243236
if let Some(offset) = offset {
244237
// timezone overflowed
245-
d = with_timezone_restore(offset, d).ok_or(ParseDateTimeError::InvalidInput)?;
238+
d = with_timezone_restore(offset, d)?;
246239
}
247-
d = d.with_hour(hour).unwrap_or(d);
248-
d = d.with_minute(minute).unwrap_or(d);
249-
d = d.with_second(second as u32).unwrap_or(d);
240+
d = d.with_hour(hour)?;
241+
d = d.with_minute(minute)?;
242+
d = d.with_second(second as u32)?;
250243
}
251244
Item::Weekday(weekday::Weekday {
252245
offset: _, // TODO: use the offset
@@ -269,9 +262,14 @@ pub(crate) fn at_date(
269262

270263
d = beginning_of_day
271264
}
272-
Item::Relative(relative::Relative::Years(x)) => d = d.with_year(x).unwrap_or(d),
265+
Item::Relative(relative::Relative::Years(x)) => {
266+
d = d.with_year(d.year() + x)?;
267+
}
273268
Item::Relative(relative::Relative::Months(x)) => {
274-
d = d.with_month(x as u32).unwrap_or(d)
269+
d += d
270+
.date_naive()
271+
.checked_add_months(chrono::Months::new(x as u32))?
272+
.signed_duration_since(d.date_naive());
275273
}
276274
Item::Relative(relative::Relative::Days(x)) => d += chrono::Duration::days(x.into()),
277275
Item::Relative(relative::Relative::Hours(x)) => d += chrono::Duration::hours(x.into()),
@@ -283,12 +281,19 @@ pub(crate) fn at_date(
283281
d += chrono::Duration::seconds(x as i64);
284282
}
285283
Item::TimeZone(offset) => {
286-
d = with_timezone_restore(offset, d).ok_or(ParseDateTimeError::InvalidInput)?;
284+
d = with_timezone_restore(offset, d)?;
287285
}
288286
}
289287
}
290288

291-
Ok(d)
289+
Some(d)
290+
}
291+
292+
pub(crate) fn at_date(
293+
date: Vec<Item>,
294+
d: DateTime<FixedOffset>,
295+
) -> Result<DateTime<FixedOffset>, ParseDateTimeError> {
296+
at_date_inner(date, d).ok_or(ParseDateTimeError::InvalidInput)
292297
}
293298

294299
pub(crate) fn at_local(date: Vec<Item>) -> Result<DateTime<FixedOffset>, ParseDateTimeError> {

src/items/time.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,6 @@ impl From<Offset> for chrono::FixedOffset {
9797
) -> Self {
9898
let secs = hours * 3600 + minutes * 60;
9999

100-
eprintln!("------------------> {hours} {minutes} {secs}");
101100
if negative {
102101
FixedOffset::west_opt(secs.try_into().expect("secs overflow"))
103102
.expect("timezone overflow")

src/lib.rs

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -244,12 +244,7 @@ mod tests {
244244

245245
#[test]
246246
fn invalid_offset_format() {
247-
let invalid_offsets = vec![
248-
// "+0700", // this is not invalid
249-
// "UTC+2", // this is not invalid
250-
// "Z-1", // this is not invalid, means UTC-1
251-
"UTC+01005", // this is
252-
];
247+
let invalid_offsets = vec!["UTC+01005"];
253248
for offset in invalid_offsets {
254249
assert_eq!(
255250
parse_datetime(offset),
@@ -405,4 +400,46 @@ mod tests {
405400
assert_eq!(result, Err(ParseDateTimeError::InvalidInput));
406401
}
407402
}
403+
404+
mod test_relative {
405+
use crate::parse_datetime;
406+
use std::env;
407+
408+
#[test]
409+
fn test_month() {
410+
env::set_var("TZ", "UTC");
411+
412+
assert_eq!(
413+
parse_datetime("28 feb + 1 month")
414+
.expect("parse_datetime")
415+
.format("%+")
416+
.to_string(),
417+
"2024-03-28T00:00:00+00:00"
418+
);
419+
420+
// 29 feb 2025 is invalid
421+
assert!(parse_datetime("29 feb + 1 year").is_err());
422+
423+
// 29 feb 2025 is an invalid date
424+
assert!(parse_datetime("29 feb 2025").is_err());
425+
426+
// because 29 feb 2025 is invalid, 29 feb 2025 + 1 day is invalid
427+
// arithmetic does not operate on invalid dates
428+
assert_eq!(
429+
parse_datetime("29 feb 2025 + 1 day")
430+
.unwrap()
431+
.format("%+")
432+
.to_string(),
433+
"2023-03-01T00:00:00+00:00"
434+
);
435+
// 28 feb 2023 + 1 day = 1 mar
436+
assert_eq!(
437+
parse_datetime("28 feb 2023 + 1 day")
438+
.unwrap()
439+
.format("%+")
440+
.to_string(),
441+
"2023-03-01T00:00:00+00:00"
442+
);
443+
}
444+
}
408445
}

0 commit comments

Comments
 (0)