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::error::{InvalidDateStrSnafu, ParseDateStrSnafu, Result};
23use crate::interval::{IntervalDayTime, IntervalMonthDayNano, IntervalYearMonth};
24use crate::timezone::get_timezone;
25use crate::util::datetime_to_utc;
26use crate::Timezone;
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) = NaiveDate::from_num_days_from_ce_opt(UNIX_EPOCH_FROM_CE + self.0) {
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 NaiveDate::from_num_days_from_ce_opt(UNIX_EPOCH_FROM_CE + self.0)
97 }
98
99 pub fn as_formatted_string(
102 self,
103 pattern: &str,
104 timezone: Option<&Timezone>,
105 ) -> Result<Option<String>> {
106 if let Some(v) = self.to_chrono_date() {
107 let time = NaiveTime::from_hms_nano_opt(0, 0, 0, 0).unwrap();
109 let v = v.and_time(time);
110 let mut formatted = String::new();
111
112 match get_timezone(timezone) {
113 Timezone::Offset(offset) => {
114 write!(
115 formatted,
116 "{}",
117 offset.from_utc_datetime(&v).format(pattern)
118 )
119 .context(crate::error::FormatSnafu { pattern })?;
120 }
121 Timezone::Named(tz) => {
122 write!(formatted, "{}", tz.from_utc_datetime(&v).format(pattern))
123 .context(crate::error::FormatSnafu { pattern })?;
124 }
125 }
126
127 return Ok(Some(formatted));
128 }
129
130 Ok(None)
131 }
132
133 pub fn to_secs(&self) -> i64 {
134 (self.0 as i64) * 24 * 3600
135 }
136
137 pub fn add_year_month(&self, interval: IntervalYearMonth) -> Option<Date> {
140 let naive_date = self.to_chrono_date()?;
141
142 naive_date
143 .checked_add_months(Months::new(interval.months as u32))
144 .map(Into::into)
145 }
146
147 pub fn add_day_time(&self, interval: IntervalDayTime) -> Option<Date> {
149 let naive_date = self.to_chrono_date()?;
150
151 naive_date
152 .checked_add_days(Days::new(interval.days as u64))?
153 .checked_add_signed(TimeDelta::milliseconds(interval.milliseconds as i64))
154 .map(Into::into)
155 }
156
157 pub fn add_month_day_nano(&self, interval: IntervalMonthDayNano) -> Option<Date> {
159 let naive_date = self.to_chrono_date()?;
160
161 naive_date
162 .checked_add_months(Months::new(interval.months as u32))?
163 .checked_add_days(Days::new(interval.days as u64))?
164 .checked_add_signed(TimeDelta::nanoseconds(interval.nanoseconds))
165 .map(Into::into)
166 }
167
168 pub fn sub_year_month(&self, interval: IntervalYearMonth) -> Option<Date> {
170 let naive_date = self.to_chrono_date()?;
171
172 naive_date
173 .checked_sub_months(Months::new(interval.months as u32))
174 .map(Into::into)
175 }
176
177 pub fn sub_day_time(&self, interval: IntervalDayTime) -> Option<Date> {
179 let naive_date = self.to_chrono_date()?;
180
181 naive_date
182 .checked_sub_days(Days::new(interval.days as u64))?
183 .checked_sub_signed(TimeDelta::milliseconds(interval.milliseconds as i64))
184 .map(Into::into)
185 }
186
187 pub fn sub_month_day_nano(&self, interval: IntervalMonthDayNano) -> Option<Date> {
189 let naive_date = self.to_chrono_date()?;
190
191 naive_date
192 .checked_sub_months(Months::new(interval.months as u32))?
193 .checked_sub_days(Days::new(interval.days as u64))?
194 .checked_sub_signed(TimeDelta::nanoseconds(interval.nanoseconds))
195 .map(Into::into)
196 }
197
198 pub fn negative(&self) -> Self {
199 Self(-self.0)
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use chrono::Utc;
206
207 use super::*;
208
209 #[test]
210 pub fn test_print_date2() {
211 assert_eq!("1969-12-31", Date::new(-1).to_string());
212 assert_eq!("1970-01-01", Date::new(0).to_string());
213 assert_eq!("1970-02-12", Date::new(42).to_string());
214 }
215
216 #[test]
217 pub fn test_date_parse() {
218 assert_eq!(
219 "1970-01-01",
220 Date::from_str("1970-01-01", None).unwrap().to_string()
221 );
222
223 assert_eq!(
224 "1969-01-01",
225 Date::from_str("1969-01-01", None).unwrap().to_string()
226 );
227
228 assert_eq!(
229 "1969-01-01",
230 Date::from_str(" 1969-01-01 ", None)
231 .unwrap()
232 .to_string()
233 );
234
235 let now = Utc::now().date_naive().format("%F").to_string();
236 assert_eq!(now, Date::from_str(&now, None).unwrap().to_string());
237
238 assert_eq!(
240 "1969-12-31",
241 Date::from_str(
242 "1970-01-01",
243 Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap())
244 )
245 .unwrap()
246 .to_string()
247 );
248
249 assert_eq!(
250 "1969-12-31",
251 Date::from_str(
252 "1970-01-01",
253 Some(&Timezone::from_tz_string("+16:00").unwrap())
254 )
255 .unwrap()
256 .to_string()
257 );
258
259 assert_eq!(
260 "1970-01-01",
261 Date::from_str(
262 "1970-01-01",
263 Some(&Timezone::from_tz_string("-8:00").unwrap())
264 )
265 .unwrap()
266 .to_string()
267 );
268
269 assert_eq!(
270 "1970-01-01",
271 Date::from_str(
272 "1970-01-01",
273 Some(&Timezone::from_tz_string("-16:00").unwrap())
274 )
275 .unwrap()
276 .to_string()
277 );
278 }
279
280 #[test]
281 fn test_add_sub_interval() {
282 let date = Date::new(1000);
283
284 let interval = IntervalYearMonth::new(3);
285
286 let new_date = date.add_year_month(interval).unwrap();
287 assert_eq!(new_date.val(), 1091);
288
289 assert_eq!(date, new_date.sub_year_month(interval).unwrap());
290 }
291
292 #[test]
293 pub fn test_min_max() {
294 let mut date = Date::from_str("9999-12-31", None).unwrap();
295 date.0 += 1000;
296 assert_eq!(date, Date::from_str(&date.to_string(), None).unwrap());
297 }
298
299 #[test]
300 fn test_as_formatted_string() {
301 let d: Date = 42.into();
302
303 assert_eq!(
304 "1970-02-12",
305 d.as_formatted_string("%Y-%m-%d", None).unwrap().unwrap()
306 );
307 assert_eq!(
308 "1970-02-12 00:00:00",
309 d.as_formatted_string("%Y-%m-%d %H:%M:%S", None)
310 .unwrap()
311 .unwrap()
312 );
313 assert_eq!(
314 "1970-02-12T00:00:00:000",
315 d.as_formatted_string("%Y-%m-%dT%H:%M:%S:%3f", None)
316 .unwrap()
317 .unwrap()
318 );
319 assert_eq!(
320 "1970-02-12T08:00:00:000",
321 d.as_formatted_string(
322 "%Y-%m-%dT%H:%M:%S:%3f",
323 Some(&Timezone::from_tz_string("Asia/Shanghai").unwrap())
324 )
325 .unwrap()
326 .unwrap()
327 );
328 }
329
330 #[test]
331 pub fn test_from() {
332 let d: Date = 42.into();
333 assert_eq!(42, d.val());
334 }
335
336 #[test]
337 fn test_to_secs() {
338 let d = Date::from_str("1970-01-01", None).unwrap();
339 assert_eq!(d.to_secs(), 0);
340 let d = Date::from_str("1970-01-02", None).unwrap();
341 assert_eq!(d.to_secs(), 24 * 3600);
342 let d = Date::from_str("1970-01-03", None).unwrap();
343 assert_eq!(d.to_secs(), 2 * 24 * 3600);
344 }
345}