zino_core/datetime/
date.rs1use crate::{AvroValue, JsonValue, error::Error};
2use chrono::{Datelike, Days, Local, Months, NaiveDate, Weekday, format::ParseError};
3use serde::{Deserialize, Serialize, Serializer};
4use std::{
5 fmt,
6 ops::{Add, AddAssign, Sub, SubAssign},
7 str::FromStr,
8 time::Duration,
9};
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
13pub struct Date(NaiveDate);
14
15impl Date {
16 #[inline]
18 pub fn try_new(year: i32, month: u32, day: u32) -> Result<Self, Error> {
19 NaiveDate::from_ymd_opt(year, month, day)
20 .map(Self)
21 .ok_or_else(|| {
22 let message = format!(
23 "fail to create a date from year: `{year}`, month: `{month}`, day: `{day}`"
24 );
25 Error::new(message)
26 })
27 }
28
29 #[inline]
31 pub fn today() -> Self {
32 Self(Local::now().date_naive())
33 }
34
35 #[inline]
37 pub fn tomorrow() -> Self {
38 let date = Local::now()
39 .date_naive()
40 .succ_opt()
41 .unwrap_or(NaiveDate::MAX);
42 Self(date)
43 }
44
45 #[inline]
47 pub fn yesterday() -> Self {
48 let date = Local::now()
49 .date_naive()
50 .pred_opt()
51 .unwrap_or(NaiveDate::MIN);
52 Self(date)
53 }
54
55 #[inline]
57 pub fn epoch() -> Self {
58 Self(NaiveDate::default())
59 }
60
61 #[inline]
63 pub fn num_days_from_epoch(&self) -> i32 {
64 let unix_epoch = NaiveDate::default();
65 self.0
66 .signed_duration_since(unix_epoch)
67 .num_days()
68 .try_into()
69 .unwrap_or_default()
70 }
71
72 #[inline]
74 pub fn num_days_from(&self, other: Date) -> i32 {
75 self.0
76 .signed_duration_since(other.0)
77 .num_days()
78 .try_into()
79 .unwrap_or_default()
80 }
81
82 #[inline]
85 pub fn format(&self, fmt: &str) -> String {
86 format!("{}", self.0.format(fmt))
87 }
88
89 #[inline]
92 pub fn duration_since(&self, earlier: Date) -> Duration {
93 (self.0 - earlier.0).to_std().unwrap_or_default()
94 }
95
96 #[inline]
98 pub fn span_between(&self, other: Date) -> Duration {
99 let duration = if self > &other {
100 self.0 - other.0
101 } else {
102 other.0 - self.0
103 };
104 duration.to_std().unwrap_or_default()
105 }
106
107 #[inline]
109 pub fn year(&self) -> i32 {
110 self.0.year()
111 }
112
113 #[inline]
117 pub fn quarter(&self) -> u32 {
118 self.0.month().div_ceil(3)
119 }
120
121 #[inline]
125 pub fn month(&self) -> u32 {
126 self.0.month()
127 }
128
129 #[inline]
133 pub fn day(&self) -> u32 {
134 self.0.day()
135 }
136
137 #[inline]
141 pub fn week(&self) -> u32 {
142 self.0.iso_week().week()
143 }
144
145 #[inline]
149 pub fn day_of_year(&self) -> u32 {
150 self.0.ordinal()
151 }
152
153 #[inline]
155 pub fn day_of_week(&self) -> u8 {
156 self.iso_day_of_week() % 7
157 }
158
159 #[inline]
161 pub fn iso_day_of_week(&self) -> u8 {
162 (self.0.weekday() as u8) + 1
163 }
164
165 #[inline]
167 pub fn is_leap_year(&self) -> bool {
168 self.0.leap_year()
169 }
170
171 #[inline]
173 pub fn is_weekend(&self) -> bool {
174 matches!(self.0.weekday(), Weekday::Sat | Weekday::Sun)
175 }
176
177 #[inline]
179 pub fn days_in_current_year(&self) -> u32 {
180 if self.is_leap_year() { 366 } else { 365 }
181 }
182
183 pub fn days_in_current_month(&self) -> u32 {
185 let month = self.month();
186 match month {
187 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
188 4 | 6 | 9 | 11 => 30,
189 2 => {
190 if self.is_leap_year() {
191 29
192 } else {
193 28
194 }
195 }
196 _ => panic!("invalid month: {month}"),
197 }
198 }
199
200 pub fn start_of_current_year(&self) -> Self {
202 let year = self.year();
203 Self(NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default())
204 }
205
206 pub fn end_of_current_year(&self) -> Self {
208 let year = self.year();
209 Self(NaiveDate::from_ymd_opt(year, 12, 31).unwrap_or_default())
210 }
211
212 pub fn start_of_next_year(&self) -> Self {
214 let year = self.year() + 1;
215 Self(NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default())
216 }
217
218 pub fn start_of_current_quarter(&self) -> Self {
220 let year = self.year();
221 let month = 3 * self.quarter() - 2;
222 Self(NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default())
223 }
224
225 pub fn end_of_current_quarter(&self) -> Self {
227 let year = self.year();
228 let month = 3 * self.quarter();
229 let day = Self::days_in_month(year, month);
230 Self(NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default())
231 }
232
233 pub fn start_of_current_month(&self) -> Self {
235 let year = self.year();
236 let month = self.month();
237 Self(NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default())
238 }
239
240 pub fn end_of_current_month(&self) -> Self {
242 let year = self.year();
243 let month = self.month();
244 let day = self.days_in_current_month();
245 Self(NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default())
246 }
247
248 pub fn start_of_next_month(&self) -> Self {
250 let year = self.year();
251 let month = self.month();
252 let date_opt = if month == 12 {
253 NaiveDate::from_ymd_opt(year + 1, 1, 1)
254 } else {
255 NaiveDate::from_ymd_opt(year, month, 1)
256 };
257 Self(date_opt.unwrap_or_default())
258 }
259
260 #[inline]
263 pub fn checked_add_months(self, months: u32) -> Option<Self> {
264 self.0.checked_add_months(Months::new(months)).map(Self)
265 }
266
267 #[inline]
270 pub fn checked_sub_months(self, months: u32) -> Option<Self> {
271 self.0.checked_sub_months(Months::new(months)).map(Self)
272 }
273
274 #[inline]
277 pub fn checked_add_days(self, days: u32) -> Option<Self> {
278 self.0
279 .checked_add_days(Days::new(u64::from(days)))
280 .map(Self)
281 }
282
283 #[inline]
286 pub fn checked_sub_days(self, days: u32) -> Option<Self> {
287 self.0
288 .checked_sub_days(Days::new(u64::from(days)))
289 .map(Self)
290 }
291
292 #[inline]
294 pub fn days_in_year(year: i32) -> u32 {
295 if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
296 366
297 } else {
298 365
299 }
300 }
301
302 #[inline]
304 pub fn days_in_month(year: i32, month: u32) -> u32 {
305 match month {
306 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
307 4 | 6 | 9 | 11 => 30,
308 2 => {
309 if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
310 29
311 } else {
312 28
313 }
314 }
315 _ => panic!("invalid month: {month}"),
316 }
317 }
318}
319
320impl Default for Date {
321 #[inline]
323 fn default() -> Self {
324 Self::today()
325 }
326}
327
328impl fmt::Display for Date {
329 #[inline]
330 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
331 self.0.format("%Y-%m-%d").fmt(f)
332 }
333}
334
335impl Serialize for Date {
336 #[inline]
337 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
338 serializer.serialize_str(&self.to_string())
339 }
340}
341
342impl From<NaiveDate> for Date {
343 #[inline]
344 fn from(d: NaiveDate) -> Self {
345 Self(d)
346 }
347}
348
349impl From<Date> for NaiveDate {
350 #[inline]
351 fn from(d: Date) -> Self {
352 d.0
353 }
354}
355
356impl From<Date> for AvroValue {
357 #[inline]
358 fn from(d: Date) -> Self {
359 AvroValue::Date(d.num_days_from_epoch())
360 }
361}
362
363impl From<Date> for JsonValue {
364 #[inline]
365 fn from(d: Date) -> Self {
366 JsonValue::String(d.to_string())
367 }
368}
369
370#[cfg(feature = "i18n")]
371impl<'a> From<Date> for fluent::FluentValue<'a> {
372 #[inline]
373 fn from(d: Date) -> Self {
374 fluent::FluentValue::String(d.to_string().into())
375 }
376}
377
378impl FromStr for Date {
379 type Err = ParseError;
380
381 #[inline]
382 fn from_str(s: &str) -> Result<Self, Self::Err> {
383 s.parse::<NaiveDate>().map(Self)
384 }
385}
386
387impl Add<Duration> for Date {
388 type Output = Self;
389
390 #[inline]
391 fn add(self, rhs: Duration) -> Self {
392 let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
393 let date = self
394 .0
395 .checked_add_signed(duration)
396 .expect("`Date + Duration` overflowed");
397 Self(date)
398 }
399}
400
401impl AddAssign<Duration> for Date {
402 #[inline]
403 fn add_assign(&mut self, rhs: Duration) {
404 *self = *self + rhs;
405 }
406}
407
408impl Sub<Duration> for Date {
409 type Output = Self;
410
411 #[inline]
412 fn sub(self, rhs: Duration) -> Self {
413 let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
414 let date = self
415 .0
416 .checked_sub_signed(duration)
417 .expect("`Date - Duration` overflowed");
418 Self(date)
419 }
420}
421
422impl SubAssign<Duration> for Date {
423 #[inline]
424 fn sub_assign(&mut self, rhs: Duration) {
425 *self = *self - rhs;
426 }
427}
428
429#[cfg(feature = "sqlx")]
430impl<DB> sqlx::Type<DB> for Date
431where
432 DB: sqlx::Database,
433 NaiveDate: sqlx::Type<DB>,
434{
435 #[inline]
436 fn type_info() -> <DB as sqlx::Database>::TypeInfo {
437 <NaiveDate as sqlx::Type<DB>>::type_info()
438 }
439}
440
441#[cfg(feature = "sqlx")]
442impl<'r, DB> sqlx::Decode<'r, DB> for Date
443where
444 DB: sqlx::Database,
445 NaiveDate: sqlx::Decode<'r, DB>,
446{
447 #[inline]
448 fn decode(value: <DB as sqlx::Database>::ValueRef<'r>) -> Result<Self, crate::BoxError> {
449 <NaiveDate as sqlx::Decode<'r, DB>>::decode(value).map(|dt| dt.into())
450 }
451}