multisql/data/value/methods/
timestamp.rs

1use {
2	crate::{Convert, Result, Value, ValueError},
3	chrono::{Datelike, NaiveDate, NaiveDateTime, Timelike},
4	fstrings::*,
5	std::{
6		cmp::min,
7		convert::TryInto,
8		panic,
9		time::{SystemTime, UNIX_EPOCH},
10	},
11};
12
13macro_rules! protect_null {
14	($protect: expr) => {
15		match $protect {
16			Value::Null => return Ok(Value::Null),
17			other => other,
18		}
19	};
20}
21
22macro_rules! expect_arguments {
23	($arguments: expr, $expect: expr) => {
24		match $arguments.len() {
25			$expect => (),
26			found => {
27				return Err(ValueError::NumberOfFunctionParamsNotMatching {
28					expected: $expect,
29					found,
30				}
31				.into())
32			}
33		}
34	};
35}
36
37macro_rules! optional_expect_arguments {
38	($arguments: expr, $min: expr, $max: expr) => {
39		match $arguments.len() {
40			len if ($min..=$max).contains(&len) => (),
41			found => {
42				return Err(ValueError::NumberOfFunctionParamsNotMatching {
43					expected: $min,
44					found,
45				}
46				.into())
47			}
48		}
49	};
50}
51
52impl Value {
53	pub fn function_now(arguments: Vec<Self>) -> Result<Self> {
54		expect_arguments!(arguments, 0);
55		Value::now()
56	}
57	pub fn function_year(mut arguments: Vec<Self>) -> Result<Self> {
58		expect_arguments!(arguments, 1);
59		protect_null!(arguments.remove(0)).year()
60	}
61	pub fn function_month(mut arguments: Vec<Self>) -> Result<Self> {
62		expect_arguments!(arguments, 1);
63		protect_null!(arguments.remove(0)).month()
64	}
65	pub fn function_day(mut arguments: Vec<Self>) -> Result<Self> {
66		expect_arguments!(arguments, 1);
67		protect_null!(arguments.remove(0)).day()
68	}
69	pub fn function_hour(mut arguments: Vec<Self>) -> Result<Self> {
70		expect_arguments!(arguments, 1);
71		protect_null!(arguments.remove(0)).hour()
72	}
73	pub fn function_minute(mut arguments: Vec<Self>) -> Result<Self> {
74		expect_arguments!(arguments, 1);
75		protect_null!(arguments.remove(0)).minute()
76	}
77	pub fn function_second(mut arguments: Vec<Self>) -> Result<Self> {
78		expect_arguments!(arguments, 1);
79		protect_null!(arguments.remove(0)).second()
80	}
81
82	pub fn function_timestamp_add(mut arguments: Vec<Self>) -> Result<Self> {
83		expect_arguments!(arguments, 3);
84		arguments.remove(0).date_add(
85			protect_null!(arguments.remove(0)),
86			protect_null!(arguments.remove(0)),
87		)
88	}
89	pub fn function_timestamp_from_parts(arguments: Vec<Self>) -> Result<Self> {
90		optional_expect_arguments!(arguments, 1, 6);
91		protect_null!(arguments.get(0).cloned().unwrap_or(Value::I64(1))).date_from_parts(
92			protect_null!(arguments.get(1).cloned().unwrap_or(Value::I64(1))),
93			protect_null!(arguments.get(2).cloned().unwrap_or(Value::I64(1))),
94			protect_null!(arguments.get(3).cloned().unwrap_or(Value::I64(0))),
95			protect_null!(arguments.get(4).cloned().unwrap_or(Value::I64(0))),
96			protect_null!(arguments.get(5).cloned().unwrap_or(Value::I64(0))),
97		)
98	}
99}
100
101// System
102impl Value {
103	pub fn now() -> Result<Value> {
104		Ok(Value::I64(
105			NaiveDateTime::from_timestamp(
106				SystemTime::now()
107					.duration_since(UNIX_EPOCH)
108					.unwrap()
109					.as_secs() as i64,
110				0,
111			)
112			.timestamp(),
113		))
114	}
115}
116
117// Parts
118impl Value {
119	pub fn year(self) -> Result<Value> {
120		let datetime: NaiveDateTime = self.convert()?;
121		Ok(Value::I64(datetime.year() as i64))
122	}
123	pub fn month(self) -> Result<Value> {
124		let datetime: NaiveDateTime = self.convert()?;
125		Ok(Value::I64(datetime.month() as i64))
126	}
127	pub fn day(self) -> Result<Value> {
128		let datetime: NaiveDateTime = self.convert()?;
129		Ok(Value::I64(datetime.day() as i64))
130	}
131	pub fn hour(self) -> Result<Value> {
132		let datetime: NaiveDateTime = self.convert()?;
133		Ok(Value::I64(datetime.hour() as i64))
134	}
135	pub fn minute(self) -> Result<Value> {
136		let datetime: NaiveDateTime = self.convert()?;
137		Ok(Value::I64(datetime.minute() as i64))
138	}
139	pub fn second(self) -> Result<Value> {
140		let datetime: NaiveDateTime = self.convert()?;
141		Ok(Value::I64(datetime.second() as i64))
142	}
143}
144
145// Math
146impl Value {
147	pub fn date_add(self, amount: Value, datetime: Value) -> Result<Value> {
148		let datetime: NaiveDateTime = datetime.convert()?;
149		let amount: i64 = amount.convert()?;
150		let amount: i32 = amount.try_into().map_err(|_| ValueError::DateError)?;
151		if amount > 100_000 {
152			panic!("Looks like you put the amount and timestamp the wrong way around. This will be fixed in future by using different datatypes");
153		}
154
155		match self {
156			Value::Str(string) if string == "YEAR" => {
157				let years = datetime.year() + amount as i32;
158				let calculated = datetime.with_year(years).unwrap_or_else(
159					|| {
160						datetime
161							.with_day(28)
162							.ok_or(ValueError::DateError)
163							.unwrap() //?
164							.with_year(years)
165							.ok_or(ValueError::DateError)
166							.unwrap()
167					}, //?,
168				);
169				Ok(Value::I64(calculated.timestamp()))
170			}
171			Value::Str(string) if string == "MONTH" => {
172				let month: i32 = datetime
173					.month()
174					.try_into()
175					.map_err(|_| ValueError::DateError)?;
176
177				let months = month + amount;
178				let month = ((months - 1) % 12) + 1;
179
180				let years = (months - month) / 12;
181				let month: u32 = month.try_into().map_err(|_| ValueError::DateError).unwrap(); //?;
182
183				let (years, month) = if month == 0 { (-1, 12) } else { (years, month) }; // TEMP-- no support for > -1 yet
184
185				let next_month = if datetime.month() == 12 {
186					NaiveDate::from_ymd(datetime.year() + 1, 1, 1)
187				} else {
188					NaiveDate::from_ymd(datetime.year(), datetime.month() + 1, 1)
189				};
190				let this_month = NaiveDate::from_ymd(datetime.year(), datetime.month(), 1);
191
192				let month_days: u32 = NaiveDate::signed_duration_since(next_month, this_month)
193					.num_days()
194					.try_into()
195					.map_err(|_| ValueError::DateError)?;
196
197				let day = min(datetime.day(), month_days);
198				let calculated = datetime
199					.with_day(day)
200					.ok_or(ValueError::DateError)
201					.unwrap() //?
202					.with_month(month)
203					.ok_or(ValueError::DateError)
204					.unwrap(); //?;
205
206				let calculated = Value::I64(calculated.timestamp());
207				Value::Str(String::from("YEAR")).date_add(Value::I64(years as i64), calculated)
208			}
209			Value::Str(string) if string == "DAY" => {
210				let day: i32 = datetime
211					.day()
212					.try_into()
213					.map_err(|_| ValueError::DateError)?;
214				let days = day + amount;
215
216				let next_month = if datetime.month() == 12 {
217					NaiveDate::from_ymd(datetime.year() + 1, 1, 1)
218				} else {
219					NaiveDate::from_ymd(datetime.year(), datetime.month() + 1, 1)
220				};
221				let this_month = NaiveDate::from_ymd(datetime.year(), datetime.month(), 1);
222
223				let month_days: i32 = NaiveDate::signed_duration_since(next_month, this_month)
224					.num_days()
225					.try_into()
226					.map_err(|_| ValueError::DateError)?;
227
228				if days > month_days {
229					let first_day = datetime.with_day(1).ok_or(ValueError::DateError)?;
230					let next_month = Value::Str(String::from("MONTH"))
231						.date_add(Value::I64(1), Value::I64(first_day.timestamp()))?;
232					Value::Str(String::from("DAY")).date_add(
233						Value::I64(
234							(days - month_days - 1)
235								.try_into()
236								.map_err(|_| ValueError::DateError)
237								.unwrap(), //?,
238						),
239						next_month,
240					)
241				} else if days <= 0 {
242					let prev_month = if datetime.month() == 1 {
243						NaiveDate::from_ymd(datetime.year() - 1, 12, 1)
244					} else {
245						NaiveDate::from_ymd(datetime.year(), datetime.month() - 1, 1)
246					};
247
248					let prev_month_days: i32 =
249						NaiveDate::signed_duration_since(this_month, prev_month)
250							.num_days()
251							.try_into()
252							.map_err(|_| ValueError::DateError)?;
253
254					let first_day = datetime.with_day(1).ok_or(ValueError::DateError)?;
255					let prev_month = Value::Str(String::from("MONTH"))
256						.date_add(Value::I64(-1), Value::I64(first_day.timestamp()))?;
257					Value::Str(String::from("DAY")).date_add(
258						Value::I64(
259							(days + prev_month_days - 1)
260								.try_into()
261								.map_err(|_| ValueError::DateError)
262								.unwrap(), //?,
263						),
264						prev_month,
265					)
266				} else {
267					let day: u32 = days.try_into().map_err(|_| ValueError::DateError)?;
268					Ok(Value::I64(
269						datetime
270							.with_day(day)
271							.ok_or(ValueError::DateError)?
272							.timestamp(),
273					))
274				}
275			}
276			_ => Err(ValueError::BadInput(self).into()),
277		}
278	}
279	pub fn date_from_parts(
280		self,
281		month: Value,
282		day: Value,
283		hour: Value,
284		minute: Value,
285		second: Value,
286	) -> Result<Value> {
287		let (year, month, day, hour, minute, second): (i64, i64, i64, i64, i64, i64) = (
288			self.convert()?,
289			month.convert()?,
290			day.convert()?,
291			hour.convert()?,
292			minute.convert()?,
293			second.convert()?,
294		);
295		let (year, month, day, hour, minute, second): (i32, u32, u32, u32, u32, u32) = (
296			year.try_into().map_err(|_| ValueError::DateError)?,
297			month.try_into().map_err(|_| ValueError::DateError)?,
298			day.try_into().map_err(|_| ValueError::DateError)?,
299			hour.try_into().map_err(|_| ValueError::DateError)?,
300			minute.try_into().map_err(|_| ValueError::DateError)?,
301			second.try_into().map_err(|_| ValueError::DateError)?,
302		);
303		let datetime = panic::catch_unwind(|| {
304			NaiveDate::from_ymd(year, month, day).and_hms(hour, minute, second)
305		})
306		.map_err(|panic| {
307			ValueError::SpecifiedTimestampError(f!(
308				"{year=}, {month=}, {day=},\n{hour=}, {minute=}, {second=}\n{panic=:?}"
309			))
310		})?;
311
312		Ok(Value::I64(datetime.timestamp()))
313	}
314}