pub mod datetime_parsing {
use super::date_time_patterns::{DATE_PATTERNS, TIME_PATTERNS};
use chrono::prelude::{FixedOffset, Local, NaiveDate, NaiveDateTime, NaiveTime};
use chrono::Duration;
use itertools::iproduct;
pub const INVALID_ARG: &str = "Invalid Pattern";
fn time_to_string(time: NaiveTime) -> String {
Local::today()
.and_time(time)
.unwrap()
.timestamp()
.to_string()
}
fn date_to_string(date: NaiveDate) -> String {
let datetime = date.and_hms(0, 0, 0);
let local_time = Local::now();
let timezone_offset: &FixedOffset = local_time.offset();
(datetime - *timezone_offset).timestamp().to_string()
}
fn datetime_to_string(datetime: NaiveDateTime) -> String {
datetime.timestamp().to_string()
}
pub fn parse_arg(arg: &str) -> String {
for pattern in TIME_PATTERNS {
if let Ok(time) = NaiveTime::parse_from_str(arg, pattern) {
return time_to_string(time);
}
}
for pattern in DATE_PATTERNS {
if let Ok(date) = NaiveDate::parse_from_str(arg, pattern) {
return date_to_string(date);
}
}
let datetime_patterns =
iproduct!(DATE_PATTERNS, TIME_PATTERNS).map(|(x, y)| format!("{} {}", x, y));
for pattern in datetime_patterns {
if let Ok(datetime) = NaiveDateTime::parse_from_str(arg, &pattern) {
return datetime_to_string(datetime);
}
}
let timedate_patterns =
iproduct!(TIME_PATTERNS, DATE_PATTERNS).map(|(x, y)| format!("{} {}", x, y));
for pattern in timedate_patterns {
if let Ok(datetime) = NaiveDateTime::parse_from_str(arg, &pattern) {
return datetime_to_string(datetime);
}
}
return match arg {
"yesterday" => (Local::now() + Duration::days(-1)).timestamp().to_string(),
"now" => Local::now().timestamp().to_string(),
"tomorrow" => (Local::now() + Duration::days(1)).timestamp().to_string(),
_ => INVALID_ARG.to_string(),
};
}
}
mod date_time_patterns {
pub static DATE_PATTERNS: [&str; 6] = [
"%m-%d-%y", "%m-%d-%Y", "%D", "%m/%d/%Y", "%F", "%v", ];
pub static TIME_PATTERNS: [&str; 10] = [
"%I:%M %P", "%I:%M %p", "%l:%M %P", "%l:%M %p", "%H:%M", "%I:%M:%S %P", "%I:%M:%S %p", "%l:%M:%S %P", "%l:%M:%S %p", "%H:%M:%S", ];
}
#[cfg(test)]
mod time_tests {
use super::datetime_parsing::{parse_arg, INVALID_ARG};
#[test]
fn test_24_hour_time() {
assert_ne!(parse_arg("13:55"), INVALID_ARG);
}
#[test]
fn test_padded_hour_uppercase() {
assert_ne!(parse_arg("01:23 PM"), INVALID_ARG);
}
#[test]
fn test_padded_hour_lowercase() {
assert_ne!(parse_arg("01:23 pm"), INVALID_ARG);
}
#[test]
fn test_not_padded_hour_lowercase() {
assert_ne!(parse_arg("1:23 pm"), INVALID_ARG);
}
#[test]
fn test_not_padded_hour_uppercase() {
assert_ne!(parse_arg("1:23 PM"), INVALID_ARG);
}
}
#[cfg(test)]
mod date_tests {
use super::datetime_parsing::parse_arg;
const MAY_ONE_1993: &str = "736232400";
#[test]
fn test_dashes_long_year_no_pad() {
assert_eq!(parse_arg("5-1-1993"), MAY_ONE_1993);
}
#[test]
fn test_dashes_long_year_padded() {
assert_eq!(parse_arg("05-01-1993"), MAY_ONE_1993);
}
#[test]
fn test_dashes_short_year_no_pad() {
assert_eq!(parse_arg("5-1-93"), MAY_ONE_1993);
}
#[test]
fn test_dashes_short_year_padded() {
assert_eq!(parse_arg("05-01-93"), MAY_ONE_1993);
}
#[test]
fn test_slashes_short_year() {
assert_eq!(parse_arg("5/1/93"), MAY_ONE_1993)
}
#[test]
fn test_slashes_long_year() {
assert_eq!(parse_arg("5/1/1993"), MAY_ONE_1993)
}
#[test]
fn test_dashes_year_padded_month_day() {
assert_eq!(parse_arg("1993-05-01"), MAY_ONE_1993)
}
#[test]
fn test_dashes_year_month_day() {
assert_eq!(parse_arg("1993-5-1"), MAY_ONE_1993)
}
#[test]
fn test_dashes_day_word_month_year() {
assert_eq!(parse_arg("1-May-1993"), MAY_ONE_1993)
}
}
#[cfg(test)]
mod datetime_tests {
use super::datetime_parsing::parse_arg;
const MAY_ONE_1993_FOUR_FIFTY: &str = "736231800";
#[test]
fn test_multi_part() {
assert_eq!(parse_arg("1-May-1993 4:50 AM"), MAY_ONE_1993_FOUR_FIFTY);
}
#[test]
fn test_slashes_date_lowercase_am() {
assert_eq!(parse_arg("5/1/93 4:50 am"), MAY_ONE_1993_FOUR_FIFTY);
}
#[test]
fn test_dashes_then_24hour_time() {
assert_eq!(parse_arg("2022-04-22 11:40:09"), "1650627609");
}
}