Skip to content

Commit

Permalink
Merge branch 'main' into relative-to-feature
Browse files Browse the repository at this point in the history
  • Loading branch information
Kxnr authored Sep 19, 2024
2 parents 76ed345 + 51b2c79 commit 63fb03f
Show file tree
Hide file tree
Showing 2 changed files with 210 additions and 51 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
version: '0.15.0'
args: '-- --test-threads 1'
- name: Archive code coverage results
uses: actions/upload-artifact@v1
uses: actions/upload-artifact@v4
with:
name: code-coverage-report
path: cobertura.xml
Expand Down
259 changes: 209 additions & 50 deletions src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ pub enum Date {
MonthDayYear(Month, u32, u32),
MonthNumDay(u32, u32),
MonthDay(Month, u32),
UnitRelative(RelativeSpecifier, Unit),
Relative(RelativeSpecifier, Weekday),
Weekday(Weekday),
Today,
Expand Down Expand Up @@ -177,6 +178,11 @@ impl Date {
tokens += t;
return Some((Self::Relative(relspec, weekday), tokens));
}

if let Some((unit, t)) = Unit::parse(&l[tokens..]) {
tokens += t;
return Some((Self::UnitRelative(relspec, unit), tokens));
}
} else if let Some((weekday, t)) = Weekday::parse(&l[tokens..]) {
tokens += t;
return Some((Self::Weekday(weekday), tokens));
Expand Down Expand Up @@ -277,6 +283,19 @@ impl Date {

today
}
Date::UnitRelative(relspec, unit) => {
let today = Local::now().naive_local();
let mut date = today.date();
if relspec == &RelativeSpecifier::Next {
date = Duration::Specific(1, unit.to_owned()).after(today).date();
}

if relspec == &RelativeSpecifier::Last {
date = Duration::Specific(1, unit.to_owned()).before(today).date();
}

date
}
Date::Weekday(weekday) => {
let weekday = weekday.to_chrono();
let mut date = today;
Expand Down Expand Up @@ -562,16 +581,9 @@ impl Duration {
date + self.to_chrono()
} else {
match self.unit() {
Unit::Month => {
if date.month() == 12 {
date.with_month(1)
.unwrap()
.with_year(date.year() + 1)
.unwrap()
} else {
date.with_month(date.month() + self.num()).unwrap()
}
}
Unit::Month => date
.checked_add_months(chrono::Months::new(self.num()))
.expect("Date out of representable date range."),
Unit::Year => date.with_year(date.year() + self.num() as i32).unwrap(),
_ => unreachable!(),
}
Expand All @@ -587,24 +599,17 @@ impl Duration {
date - self.to_chrono()
} else {
match self.unit() {
Unit::Month => {
if date.month() == 1 {
date.with_month(12)
.unwrap()
.with_year(date.year() - 1 as i32)
.unwrap()
} else {
date.with_month(date.month() - self.num()).unwrap()
}
}
Unit::Month => date
.checked_sub_months(chrono::Months::new(self.num()))
.expect("Date out of representable date range."),
Unit::Year => date.with_year(date.year() - self.num() as i32).unwrap(),
_ => unreachable!(),
}
}
}
}

#[derive(Debug, Eq, PartialEq)]
#[derive(Debug, Eq, PartialEq, Clone, Copy)]
pub enum Unit {
Day,
Week,
Expand Down Expand Up @@ -1186,50 +1191,204 @@ mod tests {
assert_eq!(date.day(), 5);
}

#[test_case(None; "default reference time")]
#[test_case(Some(Local.with_ymd_and_hms(2021, 4, 30, 7, 15, 17).single().expect("literal date for test case").naive_local()); "past reference time")]
fn test_month_before(now: Option<ChronoDateTime>) {
#[test_case(None; "default reference time")]
#[test_case(Some(Local.with_ymd_and_hms(2021, 4, 30, 7, 15, 17).single().expect("literal date for test case").naive_local()); "past reference time")]
fn test_month_before(now: Option<ChronoDateTime>) {
let l = vec![
Lexeme::A,
Lexeme::Month,
Lexeme::Before,
Lexeme::October,
Lexeme::Num(5),
];

let today = now.map_or(Local::now().naive_local().date(), |now| now.date());
let (date, t) = DateTime::parse(l.as_slice()).unwrap();
let date = date
.to_chrono(Local::now().naive_local().time(), now)
.unwrap();

assert_eq!(t, 5);
assert_eq!(date.year(), today.year());
assert_eq!(date.month(), 9);
assert_eq!(date.day(), 5);
}

#[test_case(None; "default reference time")]
#[test_case(Some(Local.with_ymd_and_hms(2021, 4, 30, 7, 15, 17).single().expect("literal date for test case").naive_local()); "past reference time")]
fn test_year_before(now: Option<ChronoDateTime>) {
let l = vec![
Lexeme::A,
Lexeme::Year,
Lexeme::Before,
Lexeme::October,
Lexeme::Num(5),
];

let today = now.map_or(Local::now().naive_local().date(), |now| now.date());
let (date, t) = DateTime::parse(l.as_slice()).unwrap();
let date = date
.to_chrono(Local::now().naive_local().time(), now)
.unwrap();

assert_eq!(t, 5);
assert_eq!(date.year(), today.year() - 1);
assert_eq!(date.month(), 10);
assert_eq!(date.day(), 5);
}

#[test]
fn test_month_before_to_leap_day() {
let l = vec![
Lexeme::A,
Lexeme::Num(3),
Lexeme::Month,
Lexeme::Before,
Lexeme::October,
Lexeme::Num(5),
Lexeme::May,
Lexeme::Num(31),
Lexeme::Num(2024),
];

let today = now.map_or(Local::now().naive_local().date(), |now| now.date());
let (date, t) = DateTime::parse(l.as_slice()).unwrap();
let date = date
.to_chrono(Local::now().naive_local().time(), now)
.unwrap();
let date = date.to_chrono(Local::now().naive_local().time()).unwrap();

assert_eq!(t, 5);
assert_eq!(date.year(), today.year());
assert_eq!(date.month(), 9);
assert_eq!(date.day(), 5);
assert_eq!(t, 6);
assert_eq!(date.year(), 2024);
assert_eq!(date.month(), 2);
// 2024 is a leap year
assert_eq!(date.day(), 29);
}

#[test_case(None; "default reference time")]
#[test_case(Some(Local.with_ymd_and_hms(2021, 4, 30, 7, 15, 17).single().expect("literal date for test case").naive_local()); "past reference time")]
fn test_year_before(now: Option<ChronoDateTime>) {
#[test]
fn test_month_before_invalid_date() {
let l = vec![
Lexeme::A,
Lexeme::Year,
Lexeme::Num(3),
Lexeme::Month,
Lexeme::Before,
Lexeme::October,
Lexeme::Num(5),
Lexeme::May,
Lexeme::Num(31),
Lexeme::Num(2023),
];

let today = now.map_or(Local::now().naive_local().date(), |now| now.date());
let (date, t) = DateTime::parse(l.as_slice()).unwrap();
let date = date
.to_chrono(Local::now().naive_local().time(), now)
.unwrap();
let date = date.to_chrono(Local::now().naive_local().time()).unwrap();

assert_eq!(t, 5);
assert_eq!(date.year(), today.year() - 1);
assert_eq!(date.month(), 10);
assert_eq!(date.day(), 5);
assert_eq!(t, 6);
assert_eq!(date.year(), 2023);
assert_eq!(date.month(), 2);
// 2024 is a leap year
assert_eq!(date.day(), 28);
}

#[test]
fn test_next_week() {
let l = vec![Lexeme::Next, Lexeme::Week];

let today = Local::now().naive_local();
let (date, _) = DateTime::parse(l.as_slice()).unwrap();
let date = date.to_chrono(today.time()).unwrap();

assert_eq!(date, today + ChronoDuration::weeks(1));
}

#[test]
fn test_next_month() {
let l = vec![Lexeme::Next, Lexeme::Month];

let today = Local::now().naive_local();
let (date, _) = DateTime::parse(l.as_slice()).unwrap();
let date = date.to_chrono(today.time()).unwrap();

assert_eq!(
date,
today
.checked_add_months(chrono::Months::new(1))
.expect("Adding one month to current date shouldn't be the end of time.")
);
}

#[test]
fn test_next_year() {
let l = vec![Lexeme::Next, Lexeme::Year];

let today = Local::now().naive_local();
let (date, _) = DateTime::parse(l.as_slice()).unwrap();
let date = date.to_chrono(today.time()).unwrap();

assert_eq!(
date,
today
.with_year(today.year() + 1)
.expect("Adding one year to current date shouldn't be the end of time.")
);
}

#[test]
fn test_last_week() {
let l = vec![Lexeme::Last, Lexeme::Week];

let today = Local::now().naive_local();
let (date, _) = DateTime::parse(l.as_slice()).unwrap();
let date = date.to_chrono(today.time()).unwrap();

assert_eq!(date, today - ChronoDuration::weeks(1));
}

#[test]
fn test_last_month() {
let l = vec![Lexeme::Last, Lexeme::Month];

let today = Local::now().naive_local();
let (date, _) = DateTime::parse(l.as_slice()).unwrap();
let date = date.to_chrono(today.time()).unwrap();

assert_eq!(
date,
today
.checked_sub_months(chrono::Months::new(1))
.expect("Subtracting one month to current date shouldn't be the end of time.")
);
}

#[test]
fn test_last_year() {
let l = vec![Lexeme::Last, Lexeme::Year];

let today = Local::now().naive_local();
let (date, _) = DateTime::parse(l.as_slice()).unwrap();
let date = date.to_chrono(today.time()).unwrap();

assert_eq!(
date,
today
.with_year(today.year() - 1)
.expect("Subtracting one year to current date shouldn't be the end of time.")
);
}

#[test]
fn test_month_literals_with_time_and_year() {
use chrono::Timelike;

let lexemes = vec![
Lexeme::February,
Lexeme::Num(16),
Lexeme::Num(2022),
Lexeme::Comma,
Lexeme::Num(5),
Lexeme::Colon,
Lexeme::Num(27),
Lexeme::PM,
];

let (date, t) = DateTime::parse(lexemes.as_slice()).unwrap();
let date = date.to_chrono(Local::now().naive_local().time()).unwrap();

assert_eq!(t, 8);
assert_eq!(date.year(), 2022);
assert_eq!(date.month(), 2);
assert_eq!(date.day(), 16);
assert_eq!(date.hour(), 17);
assert_eq!(date.minute(), 27);
}

#[test]
Expand Down

0 comments on commit 63fb03f

Please sign in to comment.