sys_datetime/
lib.rs

1use std::{
2    fmt::Display,
3    time::{Duration, SystemTime},
4};
5
6use regex::Regex;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8
9/// Datetime
10/// # Example
11/// ```no_run
12/// let mut dt = Datetime::default();
13/// dt.add_years(1970).add_months(1).add_days(1);
14/// dt.add_seconds(Datetime::timestamp().as_secs() as i64);
15///
16/// let now = Datetime::now();
17///
18/// assert!(dt == now);
19/// ```
20#[derive(Clone, Copy, Default, Debug, PartialEq, PartialOrd)]
21pub struct Datetime {
22    year: i64,
23    month: u8,
24    day: u8,
25    hour: u8,
26    minute: u8,
27    second: u8,
28}
29
30impl Datetime {
31    /// plus years
32    pub fn add_years(&mut self, years: i64) -> &mut Self {
33        if self.year < 0 {
34            self.year += years;
35            if self.year >= 0 {
36                self.year += 1;
37            }
38        } else if self.year > 0 {
39            self.year += years;
40            if self.year <= 0 {
41                self.year -= 1;
42            }
43        } else {
44            self.year = years;
45        }
46
47        if self.month == 2 && self.day > 28 {
48            let yz = if self.year < 0 {
49                self.year + 1
50            } else {
51                self.year
52            };
53
54            if self.day >= 29 && yz % 4 == 0 && (yz % 100 != 0 || yz % 400 == 0) {
55                self.day = 29;
56            } else {
57                self.day = 28;
58            }
59        }
60
61        self
62    }
63
64    /// plus months
65    pub fn add_months(&mut self, months: i64) -> &mut Self {
66        let ys = (self.month as i64 + months) / 12;
67        let mut ms = (self.month as i64 + months) % 12;
68
69        if ms < 1 {
70            ms += 12;
71            self.month = ms as u8;
72
73            self.add_years(ys - 1);
74        } else {
75            self.month = ms as u8;
76
77            self.add_years(ys);
78        }
79
80        match self.month {
81            4 | 6 | 9 | 11 if self.day > 30 => {
82                self.day = 30;
83            }
84            _ => {}
85        }
86
87        self
88    }
89
90    /// plus days
91    pub fn add_days(&mut self, days: i64) -> &mut Self {
92        let mut ds = self.day as i64 + days;
93
94        self.day = 1;
95
96        if ds >= 1 {
97            loop {
98                let mut yz = if self.year < 0 {
99                    self.year + 1
100                } else {
101                    self.year
102                };
103
104                if self.month > 2 {
105                    yz += 1;
106                }
107
108                if yz % 4 == 0 && (yz % 100 != 0 || yz % 400 == 0) {
109                    if ds <= 366 {
110                        break;
111                    }
112                    ds -= 366;
113                } else {
114                    if ds <= 365 {
115                        break;
116                    }
117                    ds -= 365;
118                }
119
120                self.add_years(1);
121            }
122
123            loop {
124                let yz = if self.year < 0 {
125                    self.year + 1
126                } else {
127                    self.year
128                };
129
130                match self.month {
131                    1 | 3 | 5 | 7 | 8 | 10 | 12 => {
132                        if ds <= 31 {
133                            break;
134                        }
135                        ds -= 31;
136                    }
137                    2 => {
138                        if yz % 4 == 0 && (yz % 100 != 0 || yz % 400 == 0) {
139                            if ds <= 29 {
140                                break;
141                            }
142                            ds -= 29;
143                        } else {
144                            if ds <= 28 {
145                                break;
146                            }
147                            ds -= 28;
148                        }
149                    }
150                    4 | 6 | 9 | 11 => {
151                        if ds <= 30 {
152                            break;
153                        }
154                        ds -= 30;
155                    }
156                    _ => {}
157                }
158
159                self.add_months(1);
160            }
161        } else {
162            loop {
163                let mut yz = if self.year < 0 {
164                    self.year + 1
165                } else {
166                    self.year
167                };
168
169                if self.month <= 2 {
170                    yz -= 1;
171                }
172
173                if yz % 4 == 0 && (yz % 100 != 0 || yz % 400 == 0) {
174                    if ds >= -366 {
175                        break;
176                    }
177                    ds += 366;
178                } else {
179                    if ds >= -365 {
180                        break;
181                    }
182                    ds += 365;
183                }
184
185                self.add_years(-1);
186            }
187
188            loop {
189                if ds >= 1 {
190                    break;
191                }
192
193                self.add_months(-1);
194
195                let yz = if self.year < 0 {
196                    self.year + 1
197                } else {
198                    self.year
199                };
200
201                match self.month {
202                    1 | 3 | 5 | 7 | 8 | 10 | 12 => {
203                        ds += 31;
204                    }
205                    2 => {
206                        if yz % 4 == 0 && (yz % 100 != 0 || yz % 400 == 0) {
207                            ds += 29;
208                        } else {
209                            ds += 28;
210                        }
211                    }
212                    4 | 6 | 9 | 11 => {
213                        ds += 30;
214                    }
215                    _ => {}
216                }
217            }
218        }
219
220        self.day = ds as u8;
221
222        self
223    }
224
225    /// plus hours
226    /// ```no_run
227    /// let mut dt = Datetime::now();
228    /// dt.add_hours(8);
229    /// println!("{}", dt);
230    /// ```
231    pub fn add_hours(&mut self, hours: i64) -> &mut Self {
232        let mut hs = (self.hour as i64 + hours) % 24;
233
234        if hs < 0 {
235            hs += 24;
236
237            self.add_days((self.hour as i64 + hours) / 24 - 1);
238        } else {
239            self.add_days((self.hour as i64 + hours) / 24);
240        }
241
242        self.hour = hs as u8;
243
244        self
245    }
246
247    /// plus minutes
248    pub fn add_minutes(&mut self, minutes: i64) -> &mut Self {
249        let mut ms = (self.minute as i64 + minutes) % 60;
250
251        if ms < 0 {
252            ms += 60;
253
254            self.add_hours((self.minute as i64 + minutes) / 60 - 1);
255        } else {
256            self.add_hours((self.minute as i64 + minutes) / 60);
257        }
258
259        self.minute = ms as u8;
260
261        self
262    }
263
264    /// plus seconds
265    pub fn add_seconds(&mut self, seconds: i64) -> &mut Self {
266        let mut ss = (self.second as i64 + seconds) % 60;
267
268        if ss < 0 {
269            ss += 60;
270
271            self.add_minutes((self.second as i64 + seconds) / 60 - 1);
272        } else {
273            self.add_minutes((self.second as i64 + seconds) / 60);
274        }
275
276        self.second = ss as u8;
277
278        self
279    }
280
281    #[inline(always)]
282    pub fn year(&self) -> i64 {
283        self.year
284    }
285
286    #[inline(always)]
287    pub fn month(&self) -> i64 {
288        self.month as i64
289    }
290
291    #[inline(always)]
292    pub fn day(&self) -> i64 {
293        self.day as i64
294    }
295
296    #[inline(always)]
297    pub fn hour(&self) -> i64 {
298        self.hour as i64
299    }
300
301    #[inline(always)]
302    pub fn minute(&self) -> i64 {
303        self.minute as i64
304    }
305
306    #[inline(always)]
307    pub fn second(&self) -> i64 {
308        self.second as i64
309    }
310
311    /// may be used to obtain the day of the week for dates on or after 0000-03-01
312    /// ```no_run
313    /// assert_eq!(
314    ///     Datetime::from_rfc3339("1970-01-01").unwrap().day_of_week(),
315    ///     "Thursday"
316    /// );
317    /// ```
318    pub fn day_of_week(&self) -> &'static str {
319        let mut year = self.year();
320        let mut month = self.month();
321        let day = self.day();
322
323        let dayofweek = [
324            "Sunday",
325            "Monday",
326            "Tuesday",
327            "Wednesday",
328            "Thursday",
329            "Friday",
330            "Saturday",
331        ];
332
333        // adjust months so February is the last one
334        month -= 2;
335        if month < 1 {
336            month += 12;
337            year -= 1;
338        }
339
340        // split by century
341        let cent = year / 100;
342        year %= 100;
343
344        dayofweek
345            [((26 * month - 2) / 10 + day + year + year / 4 + cent / 4 + 5 * cent) as usize % 7]
346    }
347
348    /// the number of seconds between two Datetime
349    /// ```no_run
350    /// assert_eq!(
351    ///     Datetime::now().seconds_since(Datetime::from_rfc3339("1970-01-01").unwrap()),
352    ///     Datetime::timestamp().as_secs() as i64
353    /// );
354    /// ```
355    pub fn seconds_since(&self, earlier: Datetime) -> i64 {
356        let stop = Self {
357            year: self.year,
358            month: self.month,
359            day: self.day,
360            hour: 0,
361            minute: 0,
362            second: 0,
363        };
364        let mut start = Self {
365            year: earlier.year,
366            month: earlier.month,
367            day: earlier.day,
368            hour: 0,
369            minute: 0,
370            second: 0,
371        };
372
373        let mut ss =
374            (stop.year() - start.year()) * 365 + (stop.month() - start.month()) * 30 + stop.day()
375                - start.day();
376        start.add_days(ss);
377
378        while stop > start {
379            start.add_days(1);
380            ss += 1;
381        }
382        while stop < start {
383            start.add_days(-1);
384            ss -= 1;
385        }
386
387        ss = ss * 24 + self.hour() - earlier.hour();
388        ss = ss * 60 + self.minute() - earlier.minute();
389        ss = ss * 60 + self.second() - earlier.second();
390
391        ss
392    }
393
394    /// is the value valid
395    /// ```no_run
396    /// let mut dt = Datetime::default();
397    /// assert!(!dt.is_valid());
398    /// dt.add_seconds(0);
399    /// assert!(dt.is_valid());
400    /// ```
401    pub fn is_valid(&self) -> bool {
402        if self.year == 0 || self.month < 1 || self.month > 12 || self.day < 1 {
403            return false;
404        }
405        match self.month {
406            1 | 3 | 5 | 7 | 8 | 10 | 12 => {
407                if self.day > 31 {
408                    return false;
409                }
410            }
411            2 => {
412                let yz = if self.year < 0 {
413                    self.year + 1
414                } else {
415                    self.year
416                };
417                if yz % 4 == 0 && (yz % 100 != 0 || yz % 400 == 0) {
418                    if self.day > 29 {
419                        return false;
420                    }
421                } else {
422                    if self.day > 28 {
423                        return false;
424                    }
425                }
426            }
427            4 | 6 | 9 | 11 => {
428                if self.day > 30 {
429                    return false;
430                }
431            }
432            _ => {}
433        }
434        if self.hour >= 24 || self.minute >= 60 || self.second >= 60 {
435            return false;
436        }
437        true
438    }
439
440    /// create from string
441    pub fn from_str(dt: &str) -> Option<Self> {
442        if let Ok(re) = Regex::new("(\\d+)\\D+(\\d+)\\D+(\\d+)\\D*(\\d*)\\D*(\\d*)\\D*(\\d*)(\\D*)")
443        {
444            if let Some(caps) = re.captures(dt) {
445                let year = if matches!(caps.get(7),Some(b) if b.as_str().contains("BC")) {
446                    -caps
447                        .get(1)
448                        .map_or(0, |m| m.as_str().parse().unwrap_or_default())
449                } else {
450                    caps.get(1)
451                        .map_or(0, |m| m.as_str().parse().unwrap_or_default())
452                };
453                let month = caps
454                    .get(2)
455                    .map_or(0, |m| m.as_str().parse().unwrap_or_default());
456                let day = caps
457                    .get(3)
458                    .map_or(0, |m| m.as_str().parse().unwrap_or_default());
459                let hour = caps
460                    .get(4)
461                    .map_or(0, |m| m.as_str().parse().unwrap_or_default());
462                let minute = caps
463                    .get(5)
464                    .map_or(0, |m| m.as_str().parse().unwrap_or_default());
465                let second = caps
466                    .get(6)
467                    .map_or(0, |m| m.as_str().parse().unwrap_or_default());
468
469                return Some(Self {
470                    year,
471                    month,
472                    day,
473                    hour,
474                    minute,
475                    second,
476                });
477            }
478        }
479
480        None
481    }
482
483    /// create from rfc3339 string
484    /// ```no_run
485    /// assert_eq!(
486    ///     Datetime::from_rfc3339("2020-01-01 08:00:00+08:00")
487    ///         .unwrap()
488    ///         .to_string(),
489    ///     "2020-01-01 00:00:00"
490    /// );
491    /// ```
492    pub fn from_rfc3339(rfc: &str) -> Option<Self> {
493        if rfc.len() >= 10 {
494            let mut dt = Self {
495                year: rfc[0..4].parse().ok()?,
496                month: rfc[5..7].parse().ok()?,
497                day: rfc[8..10].parse().ok()?,
498                hour: 0,
499                minute: 0,
500                second: 0,
501            };
502
503            if rfc.len() >= 19 {
504                dt.hour = rfc[11..13].parse().ok()?;
505                dt.minute = rfc[14..16].parse().ok()?;
506                dt.second = rfc[17..19].parse().ok()?;
507
508                if rfc.len() > 19 {
509                    let tail = &rfc[19..];
510                    if let Some(p) = tail.find(&['+', '-']) {
511                        let z: Vec<&str> = tail[p + 1..].split(':').collect();
512                        if z.len() > 0 {
513                            if &tail[p..p + 1] == "+" {
514                                dt.add_hours(-z[0].parse().ok()?);
515                                if z.len() > 1 {
516                                    dt.add_minutes(-z[1].parse().ok()?);
517                                }
518                            } else if &tail[p..p + 1] == "-" {
519                                dt.add_hours(z[0].parse::<i64>().ok()?);
520                                if z.len() > 1 {
521                                    dt.add_minutes(z[1].parse::<i64>().ok()?);
522                                }
523                            }
524                        }
525                    }
526                }
527            }
528
529            return Some(dt);
530        }
531        None
532    }
533
534    /// current system timestamp
535    pub fn timestamp() -> Duration {
536        SystemTime::now()
537            .duration_since(SystemTime::UNIX_EPOCH)
538            .unwrap_or_default()
539    }
540
541    /// current time
542    pub fn now() -> Self {
543        let mut epoch = Self {
544            year: 1970,
545            month: 1,
546            day: 1,
547            hour: 0,
548            minute: 0,
549            second: 0,
550        };
551        epoch.add_seconds(Datetime::timestamp().as_secs() as i64);
552        epoch
553    }
554}
555
556impl Display for Datetime {
557    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
558        if self.year >= 0 {
559            write!(
560                f,
561                "{:0>4}-{:0>2}-{:0>2} {:0>2}:{:0>2}:{:0>2}",
562                self.year, self.month, self.day, self.hour, self.minute, self.second
563            )
564        } else {
565            write!(
566                f,
567                "{:0>4}-{:0>2}-{:0>2} {:0>2}:{:0>2}:{:0>2} BC",
568                -self.year, self.month, self.day, self.hour, self.minute, self.second
569            )
570        }
571    }
572}
573
574impl Serialize for Datetime {
575    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
576    where
577        S: Serializer,
578    {
579        let s = self.to_string();
580        String::serialize(&s, serializer)
581    }
582}
583
584impl<'de> Deserialize<'de> for Datetime {
585    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
586    where
587        D: Deserializer<'de>,
588    {
589        let s = String::deserialize(deserializer)?;
590        if s.len() == 0 {
591            Ok(Datetime::default())
592        } else {
593            if let Some(r) = Datetime::from_rfc3339(&s) {
594                Ok(r)
595            } else if let Some(r) = Datetime::from_str(&s) {
596                Ok(r)
597            } else {
598                Err(serde::de::Error::custom("The data format is not correct"))
599            }
600        }
601    }
602}
603
604#[cfg(feature = "postgres")]
605impl sqlx::Type<sqlx::Postgres> for Datetime {
606    fn type_info() -> sqlx::postgres::PgTypeInfo {
607        sqlx::postgres::PgTypeInfo::with_name("TIMESTAMP")
608    }
609
610    fn compatible(ty: &sqlx::postgres::PgTypeInfo) -> bool {
611        matches!(
612            ty.to_string().as_str(),
613            "TIMESTAMP" | "TIMESTAMPTZ" | "DATE" | "VARCHAR" | "TEXT"
614        )
615    }
616}
617
618#[cfg(feature = "postgres")]
619impl sqlx::Encode<'_, sqlx::Postgres> for Datetime {
620    fn encode_by_ref(
621        &self,
622        buf: &mut sqlx::postgres::PgArgumentBuffer,
623    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
624        let s = self.seconds_since(Self {
625            year: 2000,
626            month: 1,
627            day: 1,
628            hour: 0,
629            minute: 0,
630            second: 0,
631        }) * 1000000;
632        sqlx::Encode::<sqlx::Postgres>::encode_by_ref(&s, buf)
633    }
634
635    fn size_hint(&self) -> usize {
636        8
637    }
638}
639
640#[cfg(feature = "postgres")]
641impl<'r> sqlx::Decode<'r, sqlx::Postgres> for Datetime
642where
643    i64: sqlx::Decode<'r, sqlx::Postgres>,
644    i32: sqlx::Decode<'r, sqlx::Postgres>,
645    &'r str: sqlx::Decode<'r, sqlx::Postgres>,
646{
647    /// when using TIMESTAMPTZ please pay attention to time zone conversion such as your_timestamp AT TIME ZONE 'your_timezone'
648    fn decode(value: sqlx::postgres::PgValueRef<'r>) -> Result<Self, sqlx::error::BoxDynError> {
649        match sqlx::ValueRef::type_info(&value)
650            .as_ref()
651            .to_string()
652            .as_str()
653        {
654            "TIMESTAMP" | "TIMESTAMPTZ" => {
655                let mut epoch = Self {
656                    year: 2000,
657                    month: 1,
658                    day: 1,
659                    hour: 0,
660                    minute: 0,
661                    second: 0,
662                };
663                epoch.add_seconds(i64::decode(value)? / 1000000);
664                Ok(epoch)
665            }
666            "DATE" => {
667                let mut epoch = Self {
668                    year: 2000,
669                    month: 1,
670                    day: 1,
671                    hour: 0,
672                    minute: 0,
673                    second: 0,
674                };
675                epoch.add_days(i32::decode(value)? as i64);
676                Ok(epoch)
677            }
678            _ => {
679                let s = <&str>::decode(value)?;
680                let res = if let Some(r) = Datetime::from_rfc3339(s) {
681                    r
682                } else if let Some(r) = Datetime::from_str(s) {
683                    r
684                } else {
685                    Datetime::default()
686                };
687                Ok(res)
688            }
689        }
690    }
691}
692
693#[cfg(all(feature = "sqlx", not(feature = "postgres")))]
694impl<'r, DB: sqlx::Database> sqlx::Type<DB> for Datetime
695where
696    DB: sqlx::Database,
697    &'r str: sqlx::Type<DB>,
698{
699    fn type_info() -> <DB as sqlx::Database>::TypeInfo {
700        <&str>::type_info()
701    }
702
703    fn compatible(ty: &DB::TypeInfo) -> bool {
704        matches!(
705            ty.to_string().as_str(),
706            "TIMESTAMP" | "DATETIME" | "DATE" | "VARCHAR" | "TEXT"
707        )
708    }
709}
710
711#[cfg(all(feature = "sqlx", not(feature = "postgres")))]
712impl<'r, DB> sqlx::Encode<'r, DB> for Datetime
713where
714    DB: sqlx::Database,
715    String: sqlx::Encode<'r, DB>,
716{
717    fn encode_by_ref(
718        &self,
719        buf: &mut <DB as sqlx::Database>::ArgumentBuffer<'r>,
720    ) -> Result<sqlx::encode::IsNull, sqlx::error::BoxDynError> {
721        <String>::encode(self.to_string(), buf)
722    }
723}
724
725#[cfg(all(feature = "sqlx", not(feature = "postgres")))]
726impl<'r, DB> sqlx::Decode<'r, DB> for Datetime
727where
728    DB: sqlx::Database,
729    &'r [u8]: sqlx::Decode<'r, DB>,
730    &'r str: sqlx::Decode<'r, DB>,
731{
732    fn decode(
733        value: <DB as sqlx::Database>::ValueRef<'r>,
734    ) -> Result<Self, sqlx::error::BoxDynError> {
735        match sqlx::ValueRef::type_info(&value)
736            .as_ref()
737            .to_string()
738            .as_str()
739        {
740            #[cfg(feature = "mysql")]
741            "TIMESTAMP" | "DATETIME" | "DATE" => {
742                let buf = <&[u8]>::decode(value)?;
743                let len = buf[0];
744                let mut dt = Self {
745                    year: ((buf[2] as i64) << 8) + buf[1] as i64,
746                    month: buf[3],
747                    day: buf[4],
748                    hour: 0,
749                    minute: 0,
750                    second: 0,
751                };
752                if len > 4 {
753                    dt.hour = buf[5];
754                    dt.minute = buf[6];
755                    dt.second = buf[7];
756                }
757                Ok(dt)
758            }
759            _ => {
760                let s = <&str>::decode(value)?;
761                let res = if let Some(r) = Datetime::from_rfc3339(s) {
762                    r
763                } else if let Some(r) = Datetime::from_str(s) {
764                    r
765                } else {
766                    Datetime::default()
767                };
768                Ok(res)
769            }
770        }
771    }
772}