1use crate::{AvroValue, JsonValue};
4use chrono::{
5 Datelike, Days, Local, Months, NaiveDate, NaiveDateTime, NaiveTime, SecondsFormat, TimeZone,
6 Timelike, Utc, Weekday, format::ParseError,
7};
8use serde::{Deserialize, Serialize, Serializer};
9use std::{
10 fmt,
11 ops::{Add, AddAssign, Sub, SubAssign},
12 str::FromStr,
13 time::Duration,
14};
15use uuid::{NoContext, Timestamp};
16
17mod date;
18mod duration;
19mod time;
20
21pub use date::Date;
22pub use duration::{ParseDurationError, parse_duration};
23pub use time::Time;
24
25type LocalDateTime = chrono::DateTime<Local>;
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize)]
30pub struct DateTime(LocalDateTime);
31
32impl DateTime {
33 #[inline]
35 pub fn now() -> Self {
36 Self(Local::now())
37 }
38
39 #[inline]
41 pub fn current_timestamp() -> i64 {
42 Utc::now().timestamp()
43 }
44
45 #[inline]
47 pub fn current_timestamp_millis() -> i64 {
48 Utc::now().timestamp_millis()
49 }
50
51 #[inline]
53 pub fn current_timestamp_micros() -> i64 {
54 Utc::now().timestamp_micros()
55 }
56
57 #[inline]
59 pub fn current_timestamp_nanos() -> i64 {
60 Utc::now().timestamp_nanos_opt().unwrap_or_default()
61 }
62
63 #[inline]
66 pub fn from_timestamp(secs: i64) -> Self {
67 let dt = chrono::DateTime::from_timestamp(secs, 0).unwrap_or_default();
68 Self(dt.with_timezone(&Local))
69 }
70
71 #[inline]
74 pub fn from_timestamp_millis(millis: i64) -> Self {
75 let dt = chrono::DateTime::from_timestamp_millis(millis).unwrap_or_default();
76 Self(dt.with_timezone(&Local))
77 }
78
79 #[inline]
82 pub fn from_timestamp_micros(micros: i64) -> Self {
83 let dt = chrono::DateTime::from_timestamp_micros(micros).unwrap_or_default();
84 Self(dt.with_timezone(&Local))
85 }
86
87 #[inline]
90 pub fn from_timestamp_nanos(nanos: i64) -> Self {
91 let dt = chrono::DateTime::from_timestamp_nanos(nanos);
92 Self(dt.with_timezone(&Local))
93 }
94
95 #[inline]
97 pub fn from_uuid_timestamp(ts: Timestamp) -> Self {
98 let (secs, nanos) = ts.to_unix();
99 let nanos = secs * 1_000_000_000 + u64::from(nanos);
100 Self::from_timestamp_nanos(nanos.try_into().unwrap_or_default())
101 }
102
103 #[inline]
105 pub fn timestamp(&self) -> i64 {
106 self.0.timestamp()
107 }
108
109 #[inline]
111 pub fn timestamp_millis(&self) -> i64 {
112 self.0.timestamp_millis()
113 }
114
115 #[inline]
117 pub fn timestamp_micros(&self) -> i64 {
118 self.0.timestamp_micros()
119 }
120
121 #[inline]
123 pub fn timestamp_nanos(&self) -> i64 {
124 self.0.timestamp_nanos_opt().unwrap_or_default()
125 }
126
127 pub fn uuid_timestamp(&self) -> Timestamp {
129 let secs = self.timestamp().try_into().unwrap_or_default();
130 let nanos = self.0.nanosecond();
131 Timestamp::from_unix(NoContext, secs, nanos)
132 }
133
134 #[inline]
137 pub fn timezone_offset(&self) -> i32 {
138 self.0.offset().local_minus_utc()
139 }
140
141 #[inline]
143 pub fn parse_utc_str(s: &str) -> Result<Self, ParseError> {
144 let datetime = chrono::DateTime::parse_from_rfc2822(s)?;
145 Ok(Self(datetime.with_timezone(&Local)))
146 }
147
148 #[inline]
150 pub fn parse_iso_str(s: &str) -> Result<Self, ParseError> {
151 let datetime = chrono::DateTime::parse_from_rfc3339(s)?;
152 Ok(Self(datetime.with_timezone(&Local)))
153 }
154
155 #[inline]
158 pub fn parse_from_str(s: &str, fmt: &str) -> Result<Self, ParseError> {
159 let datetime = chrono::DateTime::parse_from_str(s, fmt)?;
160 Ok(Self(datetime.with_timezone(&Local)))
161 }
162
163 #[inline]
165 pub fn to_utc_timestamp(&self) -> String {
166 let datetime = self.0.with_timezone(&Utc);
167 format!("{}", datetime.format("%Y-%m-%d %H:%M:%S%.6f"))
168 }
169
170 #[inline]
172 pub fn to_utc_string(&self) -> String {
173 let datetime = self.0.with_timezone(&Utc);
174 format!("{} GMT", datetime.to_rfc2822().trim_end_matches(" +0000"))
175 }
176
177 #[inline]
180 pub fn to_iso_string(&self) -> String {
181 let datetime = self.0.with_timezone(&Utc);
182 datetime.to_rfc3339_opts(SecondsFormat::Millis, true)
183 }
184
185 #[inline]
187 pub fn to_local_string(&self) -> String {
188 format!("{}", self.0.format("%Y-%m-%d %H:%M:%S %:z"))
189 }
190
191 #[inline]
194 pub fn format(&self, fmt: &str) -> String {
195 format!("{}", self.0.format(fmt))
196 }
197
198 #[inline]
200 pub fn format_date(&self) -> String {
201 format!("{}", self.0.format("%Y-%m-%d"))
202 }
203
204 #[inline]
206 pub fn format_time(&self) -> String {
207 format!("{}", self.0.format("%H:%M:%S"))
208 }
209
210 pub fn format_local(&self) -> String {
212 format!("{}", self.0.format("%Y-%m-%d %H:%M:%S"))
213 }
214
215 #[inline]
217 pub fn format_utc(&self) -> String {
218 let datetime = self.0.with_timezone(&Utc);
219 format!("{}", datetime.format("%Y-%m-%d %H:%M:%S"))
220 }
221
222 #[inline]
225 pub fn duration_since(&self, earlier: DateTime) -> Duration {
226 (self.0 - earlier.0).to_std().unwrap_or_default()
227 }
228
229 #[inline]
231 pub fn span_between(&self, other: DateTime) -> Duration {
232 let duration = if self > &other {
233 self.0 - other.0
234 } else {
235 other.0 - self.0
236 };
237 duration.to_std().unwrap_or_default()
238 }
239
240 #[inline]
242 pub fn span_between_now(&self) -> Duration {
243 self.span_between(Self::now())
244 }
245
246 #[inline]
248 pub fn span_before_now(&self) -> Option<Duration> {
249 let current = Self::now();
250 if self <= ¤t {
251 (current.0 - self.0).to_std().ok()
252 } else {
253 None
254 }
255 }
256
257 #[inline]
259 pub fn span_after_now(&self) -> Option<Duration> {
260 let current = Self::now();
261 if self >= ¤t {
262 (self.0 - current.0).to_std().ok()
263 } else {
264 None
265 }
266 }
267
268 #[inline]
270 pub fn date(&self) -> Date {
271 self.0.date_naive().into()
272 }
273
274 #[inline]
276 pub fn time(&self) -> Time {
277 self.0.time().into()
278 }
279
280 #[inline]
282 pub fn year(&self) -> i32 {
283 self.0.year()
284 }
285
286 #[inline]
290 pub fn quarter(&self) -> u32 {
291 self.0.month().div_ceil(3)
292 }
293
294 #[inline]
298 pub fn month(&self) -> u32 {
299 self.0.month()
300 }
301
302 #[inline]
306 pub fn day(&self) -> u32 {
307 self.0.day()
308 }
309
310 #[inline]
312 pub fn hour(&self) -> u32 {
313 self.0.hour()
314 }
315
316 #[inline]
318 pub fn minute(&self) -> u32 {
319 self.0.minute()
320 }
321
322 #[inline]
324 pub fn second(&self) -> u32 {
325 self.0.second()
326 }
327
328 #[inline]
330 pub fn millisecond(&self) -> u32 {
331 self.0.timestamp_subsec_millis() % 1000
332 }
333
334 #[inline]
336 pub fn microsecond(&self) -> u32 {
337 self.0.timestamp_subsec_micros() % 1000
338 }
339
340 #[inline]
342 pub fn nanosecond(&self) -> u32 {
343 self.0.timestamp_subsec_nanos() % 1_000_000
344 }
345
346 #[inline]
350 pub fn week(&self) -> u32 {
351 self.0.iso_week().week()
352 }
353
354 #[inline]
358 pub fn day_of_year(&self) -> u32 {
359 self.0.ordinal()
360 }
361
362 #[inline]
364 pub fn day_of_week(&self) -> u8 {
365 self.iso_day_of_week() % 7
366 }
367
368 #[inline]
370 pub fn iso_day_of_week(&self) -> u8 {
371 (self.0.weekday() as u8) + 1
372 }
373
374 #[inline]
376 pub fn is_leap_year(&self) -> bool {
377 self.0.date_naive().leap_year()
378 }
379
380 #[inline]
382 pub fn is_weekend(&self) -> bool {
383 matches!(self.0.weekday(), Weekday::Sat | Weekday::Sun)
384 }
385
386 #[inline]
388 pub fn days_in_current_year(&self) -> u32 {
389 if self.is_leap_year() { 366 } else { 365 }
390 }
391
392 pub fn days_in_current_month(&self) -> u32 {
394 let month = self.month();
395 match month {
396 1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
397 4 | 6 | 9 | 11 => 30,
398 2 => {
399 if self.is_leap_year() {
400 29
401 } else {
402 28
403 }
404 }
405 _ => panic!("invalid month: {month}"),
406 }
407 }
408
409 pub fn start_of_current_year(&self) -> Self {
411 let year = self.year();
412 let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default();
413 let dt = NaiveDateTime::new(date, NaiveTime::default());
414 let offset = Local.offset_from_utc_datetime(&dt);
415 Self(LocalDateTime::from_naive_utc_and_offset(
416 dt - offset,
417 offset,
418 ))
419 }
420
421 pub fn end_of_current_year(&self) -> Self {
423 let year = self.year();
424 let dt = NaiveDate::from_ymd_opt(year, 12, 31)
425 .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
426 .unwrap_or_default();
427 let offset = Local.offset_from_utc_datetime(&dt);
428 Self(LocalDateTime::from_naive_utc_and_offset(
429 dt - offset,
430 offset,
431 ))
432 }
433
434 pub fn start_of_current_quarter(&self) -> Self {
436 let year = self.year();
437 let month = 3 * self.quarter() - 2;
438 let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default();
439 let dt = NaiveDateTime::new(date, NaiveTime::default());
440 let offset = Local.offset_from_utc_datetime(&dt);
441 Self(LocalDateTime::from_naive_utc_and_offset(
442 dt - offset,
443 offset,
444 ))
445 }
446
447 pub fn end_of_current_quarter(&self) -> Self {
449 let year = self.year();
450 let month = 3 * self.quarter();
451 let day = Date::days_in_month(year, month);
452 let dt = NaiveDate::from_ymd_opt(year, month, day)
453 .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
454 .unwrap_or_default();
455 let offset = Local.offset_from_utc_datetime(&dt);
456 Self(LocalDateTime::from_naive_utc_and_offset(
457 dt - offset,
458 offset,
459 ))
460 }
461
462 pub fn start_of_current_month(&self) -> Self {
464 let year = self.year();
465 let month = self.month();
466 let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default();
467 let dt = NaiveDateTime::new(date, NaiveTime::default());
468 let offset = Local.offset_from_utc_datetime(&dt);
469 Self(LocalDateTime::from_naive_utc_and_offset(
470 dt - offset,
471 offset,
472 ))
473 }
474
475 pub fn end_of_current_month(&self) -> Self {
477 let year = self.year();
478 let month = self.month();
479 let day = self.days_in_current_month();
480 let dt = NaiveDate::from_ymd_opt(year, month, day)
481 .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
482 .unwrap_or_default();
483 let offset = Local.offset_from_utc_datetime(&dt);
484 Self(LocalDateTime::from_naive_utc_and_offset(
485 dt - offset,
486 offset,
487 ))
488 }
489
490 pub fn start_of_current_day(&self) -> Self {
492 let date = self.0.date_naive();
493 let dt = NaiveDateTime::new(date, NaiveTime::default());
494 let offset = Local.offset_from_utc_datetime(&dt);
495 Self(LocalDateTime::from_naive_utc_and_offset(
496 dt - offset,
497 offset,
498 ))
499 }
500
501 pub fn end_of_current_day(&self) -> Self {
503 let date = self.0.date_naive();
504 let dt = date
505 .and_hms_milli_opt(23, 59, 59, 1_000)
506 .unwrap_or_default();
507 let offset = Local.offset_from_utc_datetime(&dt);
508 Self(LocalDateTime::from_naive_utc_and_offset(
509 dt - offset,
510 offset,
511 ))
512 }
513
514 pub fn start_of_year(year: i32) -> Self {
516 let date = NaiveDate::from_ymd_opt(year, 1, 1).unwrap_or_default();
517 let dt = NaiveDateTime::new(date, NaiveTime::default());
518 let offset = Local.offset_from_utc_datetime(&dt);
519 Self(LocalDateTime::from_naive_utc_and_offset(
520 dt - offset,
521 offset,
522 ))
523 }
524
525 pub fn end_of_year(year: i32) -> Self {
527 let dt = NaiveDate::from_ymd_opt(year + 1, 1, 1)
528 .and_then(|date| date.pred_opt())
529 .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
530 .unwrap_or_default();
531 let offset = Local.offset_from_utc_datetime(&dt);
532 Self(LocalDateTime::from_naive_utc_and_offset(
533 dt - offset,
534 offset,
535 ))
536 }
537
538 pub fn start_of_month(year: i32, month: u32) -> Self {
540 let date = NaiveDate::from_ymd_opt(year, month, 1).unwrap_or_default();
541 let dt = NaiveDateTime::new(date, NaiveTime::default());
542 let offset = Local.offset_from_utc_datetime(&dt);
543 Self(LocalDateTime::from_naive_utc_and_offset(
544 dt - offset,
545 offset,
546 ))
547 }
548
549 pub fn end_of_month(year: i32, month: u32) -> Self {
551 let dt = NaiveDate::from_ymd_opt(year, month + 1, 1)
552 .and_then(|date| date.pred_opt())
553 .and_then(|date| date.and_hms_milli_opt(23, 59, 59, 1_000))
554 .unwrap_or_default();
555 let offset = Local.offset_from_utc_datetime(&dt);
556 Self(LocalDateTime::from_naive_utc_and_offset(
557 dt - offset,
558 offset,
559 ))
560 }
561
562 pub fn start_of_day(year: i32, month: u32, day: u32) -> Self {
564 let date = NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default();
565 let dt = NaiveDateTime::new(date, NaiveTime::default());
566 let offset = Local.offset_from_utc_datetime(&dt);
567 Self(LocalDateTime::from_naive_utc_and_offset(
568 dt - offset,
569 offset,
570 ))
571 }
572
573 pub fn end_of_day(year: i32, month: u32, day: u32) -> Self {
575 let date = NaiveDate::from_ymd_opt(year, month, day).unwrap_or_default();
576 let dt = date
577 .and_hms_milli_opt(23, 59, 59, 1_000)
578 .unwrap_or_default();
579 let offset = Local.offset_from_utc_datetime(&dt);
580 Self(LocalDateTime::from_naive_utc_and_offset(
581 dt - offset,
582 offset,
583 ))
584 }
585
586 #[inline]
589 pub fn checked_add_months(self, months: u32) -> Option<Self> {
590 self.0.checked_add_months(Months::new(months)).map(Self)
591 }
592
593 #[inline]
596 pub fn checked_sub_months(self, months: u32) -> Option<Self> {
597 self.0.checked_sub_months(Months::new(months)).map(Self)
598 }
599
600 #[inline]
603 pub fn checked_add_days(self, days: u32) -> Option<Self> {
604 self.0
605 .checked_add_days(Days::new(u64::from(days)))
606 .map(Self)
607 }
608
609 #[inline]
612 pub fn checked_sub_days(self, days: u32) -> Option<Self> {
613 self.0
614 .checked_sub_days(Days::new(u64::from(days)))
615 .map(Self)
616 }
617}
618
619impl Default for DateTime {
620 #[inline]
622 fn default() -> Self {
623 Self::now()
624 }
625}
626
627impl fmt::Display for DateTime {
628 #[inline]
629 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
630 self.0.format("%Y-%m-%d %H:%M:%S%.6f %z").fmt(f)
631 }
632}
633
634impl Serialize for DateTime {
635 #[inline]
636 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
637 serializer.serialize_str(&self.to_utc_timestamp())
638 }
639}
640
641impl From<Date> for DateTime {
642 fn from(d: Date) -> Self {
643 let dt = NaiveDateTime::new(d.into(), NaiveTime::default());
644 let offset = Local.offset_from_utc_datetime(&dt);
645 Self(LocalDateTime::from_naive_utc_and_offset(
646 dt - offset,
647 offset,
648 ))
649 }
650}
651
652impl From<LocalDateTime> for DateTime {
653 #[inline]
654 fn from(dt: LocalDateTime) -> Self {
655 Self(dt)
656 }
657}
658
659impl From<DateTime> for LocalDateTime {
660 #[inline]
661 fn from(dt: DateTime) -> Self {
662 dt.0
663 }
664}
665
666impl From<DateTime> for AvroValue {
667 #[inline]
668 fn from(dt: DateTime) -> Self {
669 AvroValue::String(dt.to_string())
670 }
671}
672
673impl From<DateTime> for JsonValue {
674 #[inline]
675 fn from(dt: DateTime) -> Self {
676 JsonValue::String(dt.to_string())
677 }
678}
679
680#[cfg(feature = "i18n")]
681impl<'a> From<DateTime> for fluent::FluentValue<'a> {
682 #[inline]
683 fn from(dt: DateTime) -> Self {
684 fluent::FluentValue::String(dt.to_string().into())
685 }
686}
687
688impl FromStr for DateTime {
689 type Err = ParseError;
690
691 fn from_str(s: &str) -> Result<Self, Self::Err> {
692 let length = s.len();
693 if length == 10 {
694 let date = s.parse::<NaiveDate>()?;
695 let dt = NaiveDateTime::new(date, NaiveTime::default());
696 let offset = Local.offset_from_utc_datetime(&dt);
697 Ok(LocalDateTime::from_naive_utc_and_offset(dt, offset).into())
698 } else if length == 19 {
699 let dt = s.parse::<NaiveDateTime>()?;
700 let offset = Local.offset_from_utc_datetime(&dt);
701 Ok(LocalDateTime::from_naive_utc_and_offset(dt, offset).into())
702 } else if s.contains('+') || s.contains(" -") {
703 LocalDateTime::from_str(s).map(Self)
704 } else if s.ends_with('Z') {
705 let dt = s.parse::<chrono::DateTime<Utc>>()?;
706 Ok(dt.with_timezone(&Local).into())
707 } else {
708 let dt = [s, "Z"].concat().parse::<chrono::DateTime<Utc>>()?;
709 Ok(dt.with_timezone(&Local).into())
710 }
711 }
712}
713
714impl Add<Duration> for DateTime {
715 type Output = Self;
716
717 #[inline]
718 fn add(self, rhs: Duration) -> Self {
719 let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
720 let datetime = self
721 .0
722 .checked_add_signed(duration)
723 .expect("`DateTime + Duration` overflowed");
724 Self(datetime)
725 }
726}
727
728impl AddAssign<Duration> for DateTime {
729 #[inline]
730 fn add_assign(&mut self, rhs: Duration) {
731 *self = *self + rhs;
732 }
733}
734
735impl Sub<Duration> for DateTime {
736 type Output = Self;
737
738 #[inline]
739 fn sub(self, rhs: Duration) -> Self {
740 let duration = chrono::Duration::from_std(rhs).expect("Duration value is out of range");
741 let datetime = self
742 .0
743 .checked_sub_signed(duration)
744 .expect("`DateTime - Duration` overflowed");
745 Self(datetime)
746 }
747}
748
749impl SubAssign<Duration> for DateTime {
750 #[inline]
751 fn sub_assign(&mut self, rhs: Duration) {
752 *self = *self - rhs;
753 }
754}
755
756#[cfg(feature = "sqlx")]
757impl<DB> sqlx::Type<DB> for DateTime
758where
759 DB: sqlx::Database,
760 LocalDateTime: sqlx::Type<DB>,
761{
762 #[inline]
763 fn type_info() -> <DB as sqlx::Database>::TypeInfo {
764 <LocalDateTime as sqlx::Type<DB>>::type_info()
765 }
766}
767
768#[cfg(feature = "sqlx")]
769impl<'r, DB> sqlx::Decode<'r, DB> for DateTime
770where
771 DB: sqlx::Database,
772 LocalDateTime: sqlx::Decode<'r, DB>,
773{
774 #[inline]
775 fn decode(value: <DB as sqlx::Database>::ValueRef<'r>) -> Result<Self, crate::BoxError> {
776 <LocalDateTime as sqlx::Decode<'r, DB>>::decode(value).map(|dt| dt.into())
777 }
778}
779
780#[cfg(test)]
781mod tests {
782 use super::{Date, DateTime};
783
784 #[test]
785 fn it_parses_datetime() {
786 assert!("2023-12-31".parse::<DateTime>().is_ok());
787 assert!("2023-12-31T18:00:00".parse::<DateTime>().is_ok());
788 assert!("2023-07-13T02:16:33.449Z".parse::<DateTime>().is_ok());
789 assert!(
790 "2023-06-10 05:17:23.713071 +0800"
791 .parse::<DateTime>()
792 .is_ok()
793 );
794
795 let datetime = "2023-11-30 16:24:30.654321 +0800"
796 .parse::<DateTime>()
797 .unwrap();
798 let start_day = datetime.start_of_current_day();
799 let end_day = datetime.end_of_current_day();
800 assert_eq!("2023-11-30", start_day.format_date());
801 assert_eq!("00:00:00", start_day.format_time());
802 assert_eq!("2023-11-30", end_day.format_date());
803 assert_eq!("23:59:60", end_day.format_time());
804
805 let date = "2023-11-30".parse::<Date>().unwrap();
806 let datetime = DateTime::from(date);
807 assert!(datetime.day_of_week() == 4);
808 assert_eq!("2023-11-30", datetime.format_date());
809 assert_eq!("00:00:00", datetime.format_time());
810 }
811}