use {
crate::{Convert, Result, Value, ValueError},
chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike},
fstrings::*,
std::{
cmp::min,
convert::TryInto,
panic,
time::{SystemTime, UNIX_EPOCH},
},
};
macro_rules! protect_null {
($protect: expr) => {
match $protect {
Value::Null => return Ok(Value::Null),
other => other,
}
};
}
macro_rules! expect_arguments {
($arguments: expr, $expect: expr) => {
match $arguments.len() {
$expect => (),
found => {
return Err(ValueError::NumberOfFunctionParamsNotMatching {
expected: $expect,
found,
}
.into())
}
}
};
}
macro_rules! optional_expect_arguments {
($arguments: expr, $min: expr, $max: expr) => {
match $arguments.len() {
len if ($min..=$max).contains(&len) => (),
found => {
return Err(ValueError::NumberOfFunctionParamsNotMatching {
expected: $min,
found,
}
.into())
}
}
};
}
impl Value {
pub fn function_now(arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 0);
Value::now()
}
pub fn function_year(mut arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 1);
protect_null!(arguments.remove(0)).year()
}
pub fn function_month(mut arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 1);
protect_null!(arguments.remove(0)).month()
}
pub fn function_day(mut arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 1);
protect_null!(arguments.remove(0)).day()
}
pub fn function_hour(mut arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 1);
protect_null!(arguments.remove(0)).hour()
}
pub fn function_minute(mut arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 1);
protect_null!(arguments.remove(0)).minute()
}
pub fn function_second(mut arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 1);
protect_null!(arguments.remove(0)).second()
}
pub fn function_timestamp_add(mut arguments: Vec<Self>) -> Result<Self> {
expect_arguments!(arguments, 3);
arguments.remove(0).date_add(
protect_null!(arguments.remove(0)),
protect_null!(arguments.remove(0)),
)
}
pub fn function_timestamp_from_parts(arguments: Vec<Self>) -> Result<Self> {
optional_expect_arguments!(arguments, 1, 6);
protect_null!(arguments.get(0).cloned().unwrap_or(Value::I64(1))).date_from_parts(
protect_null!(arguments.get(1).cloned().unwrap_or(Value::I64(1))),
protect_null!(arguments.get(2).cloned().unwrap_or(Value::I64(1))),
protect_null!(arguments.get(3).cloned().unwrap_or(Value::I64(0))),
protect_null!(arguments.get(4).cloned().unwrap_or(Value::I64(0))),
protect_null!(arguments.get(5).cloned().unwrap_or(Value::I64(0))),
)
}
}
impl Value {
pub fn now() -> Result<Value> {
Ok(Value::I64(
NaiveDateTime::from_timestamp(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs() as i64,
0,
)
.timestamp(),
))
}
}
impl Value {
pub fn year(self) -> Result<Value> {
let datetime: NaiveDateTime = self.convert()?;
Ok(Value::I64(datetime.year() as i64))
}
pub fn month(self) -> Result<Value> {
let datetime: NaiveDateTime = self.convert()?;
Ok(Value::I64(datetime.month() as i64))
}
pub fn day(self) -> Result<Value> {
let datetime: NaiveDateTime = self.convert()?;
Ok(Value::I64(datetime.day() as i64))
}
pub fn hour(self) -> Result<Value> {
let datetime: NaiveDateTime = self.convert()?;
Ok(Value::I64(datetime.hour() as i64))
}
pub fn minute(self) -> Result<Value> {
let datetime: NaiveDateTime = self.convert()?;
Ok(Value::I64(datetime.minute() as i64))
}
pub fn second(self) -> Result<Value> {
let datetime: NaiveDateTime = self.convert()?;
Ok(Value::I64(datetime.second() as i64))
}
}
impl Value {
pub fn date_add(self, amount: Value, datetime: Value) -> Result<Value> {
let datetime: NaiveDateTime = datetime.convert()?;
let amount: i64 = amount.convert()?;
let amount: i32 = amount.try_into().map_err(|_| ValueError::DateError)?;
if amount > 100_000 {
panic!("Looks like you put the amount and timestamp the wrong way around. This will be fixed in future by using different datatypes");
}
match self {
Value::Str(string) if string == "YEAR" => {
let years = datetime.year() + amount as i32;
let calculated = datetime.with_year(years).unwrap_or_else(
|| {
datetime
.with_day(28)
.ok_or(ValueError::DateError)
.unwrap() .with_year(years)
.ok_or(ValueError::DateError)
.unwrap()
}, );
Ok(Value::I64(calculated.timestamp()))
}
Value::Str(string) if string == "MONTH" => {
let month: i32 = datetime
.month()
.try_into()
.map_err(|_| ValueError::DateError)?;
let months = month + amount;
let month = ((months - 1) % 12) + 1;
let years = (months - month) / 12;
let month: u32 = month.try_into().map_err(|_| ValueError::DateError).unwrap();
let (years, month) = if month == 0 { (-1, 12) } else { (years, month) };
let next_month = if datetime.month() == 12 {
NaiveDate::from_ymd(datetime.year() + 1, 1, 1)
} else {
NaiveDate::from_ymd(datetime.year(), datetime.month() + 1, 1)
};
let this_month = NaiveDate::from_ymd(datetime.year(), datetime.month(), 1);
let month_days: u32 = NaiveDate::signed_duration_since(next_month, this_month)
.num_days()
.try_into()
.map_err(|_| ValueError::DateError)?;
let day = min(datetime.day(), month_days);
let calculated = datetime
.with_day(day)
.ok_or(ValueError::DateError)
.unwrap() .with_month(month)
.ok_or(ValueError::DateError)
.unwrap();
let calculated = Value::I64(calculated.timestamp());
Value::Str(String::from("YEAR")).date_add(Value::I64(years as i64), calculated)
}
Value::Str(string) if string == "DAY" => {
let day: i32 = datetime
.day()
.try_into()
.map_err(|_| ValueError::DateError)?;
let days = day + amount;
let next_month = if datetime.month() == 12 {
NaiveDate::from_ymd(datetime.year() + 1, 1, 1)
} else {
NaiveDate::from_ymd(datetime.year(), datetime.month() + 1, 1)
};
let this_month = NaiveDate::from_ymd(datetime.year(), datetime.month(), 1);
let month_days: i32 = NaiveDate::signed_duration_since(next_month, this_month)
.num_days()
.try_into()
.map_err(|_| ValueError::DateError)?;
if days > month_days {
let first_day = datetime.with_day(1).ok_or(ValueError::DateError)?;
let next_month = Value::Str(String::from("MONTH"))
.date_add(Value::I64(1), Value::I64(first_day.timestamp()))?;
Value::Str(String::from("DAY")).date_add(
Value::I64(
(days - month_days - 1)
.try_into()
.map_err(|_| ValueError::DateError)
.unwrap(), ),
next_month,
)
} else if days <= 0 {
let prev_month = if datetime.month() == 1 {
NaiveDate::from_ymd(datetime.year() - 1, 12, 1)
} else {
NaiveDate::from_ymd(datetime.year(), datetime.month() - 1, 1)
};
let prev_month_days: i32 =
NaiveDate::signed_duration_since(this_month, prev_month)
.num_days()
.try_into()
.map_err(|_| ValueError::DateError)?;
let first_day = datetime.with_day(1).ok_or(ValueError::DateError)?;
let prev_month = Value::Str(String::from("MONTH"))
.date_add(Value::I64(-1), Value::I64(first_day.timestamp()))?;
Value::Str(String::from("DAY")).date_add(
Value::I64(
(days + prev_month_days - 1)
.try_into()
.map_err(|_| ValueError::DateError)
.unwrap(), ),
prev_month,
)
} else {
let day: u32 = days.try_into().map_err(|_| ValueError::DateError)?;
Ok(Value::I64(
datetime
.with_day(day)
.ok_or(ValueError::DateError)?
.timestamp(),
))
}
}
_ => Err(ValueError::BadInput(self).into()),
}
}
pub fn date_from_parts(
self,
month: Value,
day: Value,
hour: Value,
minute: Value,
second: Value,
) -> Result<Value> {
let (year, month, day, hour, minute, second): (i64, i64, i64, i64, i64, i64) = (
self.convert()?,
month.convert()?,
day.convert()?,
hour.convert()?,
minute.convert()?,
second.convert()?,
);
let (year, month, day, hour, minute, second): (i32, u32, u32, u32, u32, u32) = (
year.try_into().map_err(|_| ValueError::DateError)?,
month.try_into().map_err(|_| ValueError::DateError)?,
day.try_into().map_err(|_| ValueError::DateError)?,
hour.try_into().map_err(|_| ValueError::DateError)?,
minute.try_into().map_err(|_| ValueError::DateError)?,
second.try_into().map_err(|_| ValueError::DateError)?,
);
let datetime = panic::catch_unwind(|| {
NaiveDate::from_ymd(year, month, day).and_hms(hour, minute, second)
})
.map_err(|panic| {
ValueError::SpecifiedTimestampError(f!(
"{year=}, {month=}, {day=},\n{hour=}, {minute=}, {second=}\n{panic=:?}"
))
})?;
Ok(Value::I64(datetime.timestamp()))
}
}