1use std::fmt::{Display, Formatter, Write};
16
17use chrono::{Datelike, Days, LocalResult, Months, NaiveDate, NaiveTime, TimeDelta, TimeZone};
18use serde::{Deserialize, Serialize};
19use serde_json::Value;
20use snafu::ResultExt;
21
22use crate::Timezone;
23use crate::error::{InvalidDateStrSnafu, ParseDateStrSnafu, Result};
24use crate::interval::{IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth};
25use crate::timezone::get_timezone;
26use crate::util::datetime_to_utc;
27
28const UNIX_EPOCH_FROM_CE: i32 = 719_163;
29
30#[derive(
33 Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Deserialize, Serialize,
34)]
35pub struct Date(i32);
36
37impl From<Date> for Value {
38 fn from(d: Date) -> Self {
39 Value::String(d.to_string())
40 }
41}
42
43impl From<i32> for Date {
44 fn from(v: i32) -> Self {
45 Self(v)
46 }
47}
48
49impl From<NaiveDate> for Date {
50 fn from(date: NaiveDate) -> Self {
51 Self(date.num_days_from_ce() - UNIX_EPOCH_FROM_CE)
52 }
53}
54
55impl Display for Date {
56 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
58 if let Some(abs_date) = self.to_chrono_date() {
59 write!(f, "{}", abs_date.format("%F"))
60 } else {
61 write!(f, "Date({})", self.0)
62 }
63 }
64}
65
66impl Date {
67 pub fn from_str_utc(s: &str) -> Result<Self> {
69 Self::from_str(s, None)
70 }
71
72 pub fn from_str(s: &str, timezone: Option<&Timezone>) -> Result<Self> {
74 let s = s.trim();
75 let date = NaiveDate::parse_from_str(s, "%F").context(ParseDateStrSnafu { raw: s })?;
76 let Some(timezone) = timezone else {
77 return Ok(Self(date.num_days_from_ce() - UNIX_EPOCH_FROM_CE));
78 };
79
80 let datetime = date.and_time(NaiveTime::default());
81 match datetime_to_utc(&datetime, timezone) {
82 LocalResult::None => InvalidDateStrSnafu { raw: s }.fail(),
83 LocalResult::Single(utc) | LocalResult::Ambiguous(utc, _) => Ok(Date::from(utc.date())),
84 }
85 }
86
87 pub fn new(val: i32) -> Self {
88 Self(val)
89 }
90
91 pub fn val(&self) -> i32 {
92 self.0
93 }
94
95 pub fn to_chrono_date(&self) -> Option<NaiveDate> {
96 UNIX_EPOCH_FROM_CE
97 .checked_add(self.0)
98 .and_then(NaiveDate::from_num_days_from_ce_opt)
99 }
100
101 pub fn as_formatted_string(
104 self,
105 pattern: &str,
106 timezone: Option<&Timezone>,
107 ) -> Result<Option<String>> {
108 if let Some(v) = self.to_chrono_date() {
109 let time = NaiveTime::from_hms_nano_opt(0, 0, 0, 0).unwrap();
111 let v = v.and_time(time);
112 let mut formatted = String::new();
113
114 match get_timezone(timezone) {
115 Timezone::Offset(offset) => {
116 write!(
117 formatted,
118 "{}",
119 offset.from_utc_datetime(&v).format(pattern)
120 )
121 .context(crate::error::FormatSnafu { pattern })?;
122 }
123 Timezone::Named(tz) => {
124 write!(formatted, "{}", tz.from_utc_datetime(&v).format(pattern))
125 .context(crate::error::FormatSnafu { pattern })?;
126 }
127 }
128
129 return Ok(Some(formatted));
130 }
131
132 Ok(None)
133 }
134
135 pub fn to_secs(&self) -> i64 {
136 (self.0 as i64) * 24 * 3600
137 }
138
139 pub fn add_year_month(&self, interval: IntervalYearMonth) -> Option<Date> {
142 let naive_date = self.to_chrono_date()?;
143
144 naive_date
145 .checked_add_months(Months::new(interval.months as u32))
146 .map(Into::into)
147 }
148
149 pub fn add_day_time(&self, interval: IntervalDayTime) -> Option<Date> {
151 let naive_date = self.to_chrono_date()?;
152
153 naive_date
154 .checked_add_days(Days::new(interval.days as u64))?
155 .checked_add_signed(TimeDelta::milliseconds(interval.milliseconds as i64))
156 .map(Into::into)
157 }
158
159 pub fn add_month_day_nano(&self, interval: IntervalMonthDayNano) -> Option<Date> {
161 let naive_date = self.to_chrono_date()?;
162
163 naive_date
164 .checked_add_months(Months::new(interval.months as u32))?
165 .checked_add_days(Days::new(interval.days as u64))?
166 .checked_add_signed(TimeDelta::nanoseconds(interval.nanoseconds))
167 .map(Into::into)
168 }
169
170 pub fn sub_year_month(&self, interval: IntervalYearMonth) -> Option<Date> {
172 let naive_date = self.to_chrono_date()?;
173
174 naive_date
175 .checked_sub_months(Months::new(interval.months as u32))
176 .map(Into::into)
177 }
178
179 pub fn sub_day_time(&self, interval: IntervalDayTime) -> Option<Date> {
181 let naive_date = self.to_chrono_date()?;
182
183 naive_date
184 .checked_sub_days(Days::new(interval.days as u64))?
185 .checked_sub_signed(TimeDelta::milliseconds(interval.milliseconds as i64))
186 .map(Into::into)
187 }
188
189 pub fn sub_month_day_nano(&self, interval: IntervalMonthDayNano) -> Option<Date> {
191 let naive_date = self.to_chrono_date()?;
192
193 naive_date
194 .checked_sub_months(Months::new(interval.months as u32))?
195 .checked_sub_days(Days::new(interval.days as u64))?
196 .checked_sub_signed(TimeDelta::nanoseconds(interval.nanoseconds))
197 .map(Into::into)
198 }
199
200 pub fn negative(&self) -> Self {
201 Self(-self.0)
202 }
203
204 pub fn checked_negative(&self) -> Option<Self> {
205 self.0.checked_neg().map(Self)
206 }
207}
208
209#[cfg(test)]
210mod tests {
211 use chrono::Utc;
212
213 use super::*;
214
215 #[test]
216 pub fn test_print_date2() {
217 assert_eq!("1969-12-31", Date::new(-1).to_string());
218 assert_eq!("1970-01-01", Date::new(0).to_string());
219 assert_eq!("1970-02-12", Date::new(42).to_string());
220 }
221
222 #[test]
223 fn test_to_chrono_date_out_of_range() {
224 assert_eq!(None, Date::new(i32::MAX).to_chrono_date());
225 assert_eq!("Date(2147483647)", Date::new(i32::MAX).to_string());
226 }
227
228 #[test]
229 pub fn test_date_parse() {
230 assert_eq!(
231 "1970-01-01",
232 Date::from_str("1970-01-01", None).unwrap().to_string()
233 );
234
235 assert_eq!(
236 "1969-01-01",
237 Date::from_str("1969-01-01", None).unwrap().to_string()
238 );
239
240 assert_eq!(
241 "1969-01-01",
242 Date::from_str(" 1969-01-01 ", None)
243 .unwrap()
244 .to_string()
245 );
246
247 let now = Utc::now().date_naive().format("%F").to_string();
248 assert_eq!(now, Date::from_str(&now, None).unwrap().to_string());
249
250 assert_eq!(
252 "1969-12-31",
253 Date::from_str(
254 "1970-01-01",
255 Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap())
256 )
257 .unwrap()
258 .to_string()
259 );
260
261 assert_eq!(
262 "1969-12-31",
263 Date::from_str(
264 "1970-01-01",
265 Some(&Timezone::from_tz_string("+16:00").unwrap())
266 )
267 .unwrap()
268 .to_string()
269 );
270
271 assert_eq!(
272 "1970-01-01",
273 Date::from_str(
274 "1970-01-01",
275 Some(&Timezone::from_tz_string("-8:00").unwrap())
276 )
277 .unwrap()
278 .to_string()
279 );
280
281 assert_eq!(
282 "1970-01-01",
283 Date::from_str(
284 "1970-01-01",
285 Some(&Timezone::from_tz_string("-16:00").unwrap())
286 )
287 .unwrap()
288 .to_string()
289 );
290 }
291
292 #[test]
293 fn test_add_sub_interval() {
294 let date = Date::new(1000);
295
296 let interval = IntervalYearMonth::new(3);
297
298 let new_date = date.add_year_month(interval).unwrap();
299 assert_eq!(new_date.val(), 1091);
300
301 assert_eq!(date, new_date.sub_year_month(interval).unwrap());
302 }
303
304 #[test]
305 pub fn test_min_max() {
306 let mut date = Date::from_str("9999-12-31", None).unwrap();
307 date.0 += 1000;
308 assert_eq!(date, Date::from_str(&date.to_string(), None).unwrap());
309 }
310
311 #[test]
312 fn test_as_formatted_string() {
313 let d: Date = 42.into();
314
315 assert_eq!(
316 "1970-02-12",
317 d.as_formatted_string("%Y-%m-%d", None).unwrap().unwrap()
318 );
319 assert_eq!(
320 "1970-02-12 00:00:00",
321 d.as_formatted_string("%Y-%m-%d %H:%M:%S", None)
322 .unwrap()
323 .unwrap()
324 );
325 assert_eq!(
326 "1970-02-12T00:00:00:000",
327 d.as_formatted_string("%Y-%m-%dT%H:%M:%S:%3f", None)
328 .unwrap()
329 .unwrap()
330 );
331 assert_eq!(
332 "1970-02-12T08:00:00:000",
333 d.as_formatted_string(
334 "%Y-%m-%dT%H:%M:%S:%3f",
335 Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap())
336 )
337 .unwrap()
338 .unwrap()
339 );
340 }
341
342 #[test]
343 pub fn test_from() {
344 let d: Date = 42.into();
345 assert_eq!(42, d.val());
346 }
347
348 #[test]
349 fn test_to_secs() {
350 let d = Date::from_str("1970-01-01", None).unwrap();
351 assert_eq!(d.to_secs(), 0);
352 let d = Date::from_str("1970-01-02", None).unwrap();
353 assert_eq!(d.to_secs(), 24 * 3600);
354 let d = Date::from_str("1970-01-03", None).unwrap();
355 assert_eq!(d.to_secs(), 2 * 24 * 3600);
356 }
357}