speedate/
date.rs

1use std::fmt;
2use std::str::FromStr;
3
4use crate::config::DateConfig;
5use crate::numbers::int_parse_bytes;
6use crate::util::timestamp_to_seconds_micros;
7use crate::{get_digit_unchecked, DateTime, ParseError};
8
9/// A Date
10///
11/// Allowed formats:
12/// * `YYYY-MM-DD`
13///
14/// Leap years are correct calculated according to the Gregorian calendar.
15/// Thus `2000-02-29` is a valid date, but `2001-02-29` is not.
16///
17/// # Comparison
18///
19/// `Date` supports equality (`==`) and inequality (`>`, `<`, `>=`, `<=`) comparisons.
20///
21/// ```
22/// use speedate::Date;
23///
24/// let d1 = Date::parse_str("2022-01-01").unwrap();
25/// let d2 = Date::parse_str("2022-01-02").unwrap();
26/// assert!(d2 > d1);
27/// ```
28#[derive(Debug, PartialEq, Eq, PartialOrd, Clone, Copy)]
29pub struct Date {
30    /// Year: four digits
31    pub year: u16,
32    /// Month: 1 to 12
33    pub month: u8,
34    /// Day: 1 to {28, 29, 30, 31} (based on month & year)
35    pub day: u8,
36}
37
38impl fmt::Display for Date {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        let mut buf: [u8; 10] = *b"0000-00-00";
41        crate::display_num_buf(4, 0, self.year as u32, &mut buf);
42        crate::display_num_buf(2, 5, self.month as u32, &mut buf);
43        crate::display_num_buf(2, 8, self.day as u32, &mut buf);
44        f.write_str(std::str::from_utf8(&buf[..]).unwrap())
45    }
46}
47
48impl FromStr for Date {
49    type Err = ParseError;
50
51    #[inline]
52    fn from_str(s: &str) -> Result<Self, Self::Err> {
53        // Delegate to parse_str, which is more permissive - users can call parse_str_rfc3339 directly instead if they
54        // want to be stricter
55        Self::parse_str(s)
56    }
57}
58
59// 2e10 if greater than this, the number is in ms, if less than or equal, it's in seconds
60// (in seconds this is 11th October 2603, in ms it's 20th August 1970)
61pub(crate) const MS_WATERSHED: i64 = 20_000_000_000;
62// 9999-12-31T23:59:59 as a unix timestamp, used as max allowed value below
63const UNIX_9999: i64 = 253_402_300_799;
64// 0000-01-01T00:00:00+00:00 as a unix timestamp, used as min allowed value below
65const UNIX_0000: i64 = -62_167_219_200;
66
67impl Date {
68    /// Parse a date from a string using RFC 3339 format
69    ///
70    /// # Arguments
71    ///
72    /// * `str` - The string to parse
73    ///
74    /// # Examples
75    ///
76    /// ```
77    /// use speedate::Date;
78    ///
79    /// let d = Date::parse_str_rfc3339("2020-01-01").unwrap();
80    /// assert_eq!(
81    ///     d,
82    ///     Date {
83    ///         year: 2020,
84    ///         month: 1,
85    ///         day: 1
86    ///     }
87    /// );
88    /// assert_eq!(d.to_string(), "2020-01-01");
89    /// ```
90    #[inline]
91    pub fn parse_str_rfc3339(str: &str) -> Result<Self, ParseError> {
92        Self::parse_bytes_rfc3339(str.as_bytes())
93    }
94
95    /// Parse a date from a string using RFC 3339 format, or a unix timestamp.
96    ///
97    /// In the input is purely numeric, then the number is interpreted as a unix timestamp,
98    /// using [`Date::from_timestamp`].
99    ///
100    /// # Arguments
101    ///
102    /// * `str` - The string to parse
103    ///
104    /// # Examples
105    ///
106    /// ```
107    /// use speedate::Date;
108    ///
109    /// let d = Date::parse_str("2020-01-01").unwrap();
110    /// assert_eq!(d.to_string(), "2020-01-01");
111    /// let d = Date::parse_str("1577836800").unwrap();
112    /// assert_eq!(d.to_string(), "2020-01-01");
113    /// ```
114    #[inline]
115    pub fn parse_str(str: &str) -> Result<Self, ParseError> {
116        Self::parse_bytes(str.as_bytes())
117    }
118
119    /// As with [`Date::parse_str`] but with a [`DateConfig`].
120    #[inline]
121    pub fn parse_str_with_config(str: &str, config: &DateConfig) -> Result<Self, ParseError> {
122        Self::parse_bytes_with_config(str.as_bytes(), config)
123    }
124
125    /// Parse a date from bytes using RFC 3339 format
126    ///
127    /// # Arguments
128    ///
129    /// * `bytes` - The bytes to parse
130    ///
131    /// # Examples
132    ///
133    /// ```
134    /// use speedate::Date;
135    ///
136    /// let d = Date::parse_bytes_rfc3339(b"2020-01-01").unwrap();
137    /// assert_eq!(
138    ///     d,
139    ///     Date {
140    ///         year: 2020,
141    ///         month: 1,
142    ///         day: 1
143    ///     }
144    /// );
145    /// assert_eq!(d.to_string(), "2020-01-01");
146    /// ```
147    #[inline]
148    pub fn parse_bytes_rfc3339(bytes: &[u8]) -> Result<Self, ParseError> {
149        let d = Self::parse_bytes_partial(bytes)?;
150
151        if bytes.len() > 10 {
152            return Err(ParseError::ExtraCharacters);
153        }
154
155        Ok(d)
156    }
157
158    /// Parse a date from bytes using RFC 3339 format, or a unix timestamp.
159    ///
160    /// In the input is purely numeric, then the number is interpreted as a unix timestamp,
161    /// using [`Date::from_timestamp`].
162    ///
163    /// # Arguments
164    ///
165    /// * `bytes` - The bytes to parse
166    ///
167    /// # Examples
168    ///
169    /// ```
170    /// use speedate::Date;
171    ///
172    /// let d = Date::parse_bytes(b"2020-01-01").unwrap();
173    /// assert_eq!(d.to_string(), "2020-01-01");
174    ///
175    /// let d = Date::parse_bytes(b"1577836800").unwrap();
176    /// assert_eq!(d.to_string(), "2020-01-01");
177    /// ```
178    #[inline]
179    pub fn parse_bytes(bytes: &[u8]) -> Result<Self, ParseError> {
180        Self::parse_bytes_with_config(bytes, &DateConfig::default())
181    }
182
183    /// Same as [`Date::parse_bytes`] but with a [`DateConfig`].
184    #[inline]
185    pub fn parse_bytes_with_config(bytes: &[u8], config: &DateConfig) -> Result<Self, ParseError> {
186        match Self::parse_bytes_rfc3339(bytes) {
187            Ok(d) => Ok(d),
188            Err(e) => match int_parse_bytes(bytes) {
189                Some(int) => Self::from_timestamp(int, true, config),
190                None => Err(e),
191            },
192        }
193    }
194
195    /// Create a date from a Unix Timestamp in seconds or milliseconds
196    ///
197    /// ("Unix Timestamp" means number of seconds or milliseconds since 1970-01-01)
198    ///
199    /// Input must be between `-62,167,219,200,000` (`0000-01-01`) and `253,402,300,799,000` (`9999-12-31`) inclusive.
200    ///
201    /// If the absolute value is > 2e10 (`20,000,000,000`) it is interpreted as being in milliseconds.
202    ///
203    /// That means:
204    /// * `20,000,000,000` is `2603-10-11`
205    /// * `20,000,000,001` is `1970-08-20`
206    /// * `-62,167,219,200,001` gives an error - `DateTooSmall` as it would be before 0000-01-01
207    /// * `-20,000,000,001` is `1969-05-14`
208    /// * `-20,000,000,000` is `1336-03-23`
209    ///
210    /// # Arguments
211    ///
212    /// * `timestamp` - timestamp in either seconds or milliseconds
213    /// * `require_exact` - if true, then the timestamp must be exactly at midnight, otherwise it will be rounded down
214    ///
215    /// # Examples
216    ///
217    /// ```
218    /// use speedate::{Date, DateConfig};
219    ///
220    /// let d = Date::from_timestamp(1_654_560_000, true, &DateConfig::default()).unwrap();
221    /// assert_eq!(d.to_string(), "2022-06-07");
222    /// ```
223    pub fn from_timestamp(timestamp: i64, require_exact: bool, config: &DateConfig) -> Result<Self, ParseError> {
224        let (seconds, microseconds) = timestamp_to_seconds_micros(timestamp, config.timestamp_unit)?;
225        let (d, remaining_seconds) = Self::from_timestamp_calc(seconds)?;
226        if require_exact && (remaining_seconds != 0 || microseconds != 0) {
227            return Err(ParseError::DateNotExact);
228        }
229        Ok(d)
230    }
231
232    /// Unix timestamp in seconds (number of seconds between self and 1970-01-01)
233    ///
234    /// # Example
235    ///
236    /// ```
237    /// use speedate::Date;
238    ///
239    /// let d = Date::parse_str("2022-06-07").unwrap();
240    /// assert_eq!(d.timestamp(), 1_654_560_000);
241    /// ```
242    pub fn timestamp(&self) -> i64 {
243        let days =
244            (self.year as i64) * 365 + (self.ordinal_day() - 1) as i64 + intervening_leap_years(self.year as i64);
245        days * 86400 + UNIX_0000
246    }
247
248    /// Unix timestamp in milliseconds (number of milliseconds between self and 1970-01-01)
249    ///
250    /// # Example
251    ///
252    /// ```
253    /// use speedate::Date;
254    ///
255    /// let d = Date::parse_str("2022-06-07").unwrap();
256    /// assert_eq!(d.timestamp_ms(), 1_654_560_000_000);
257    /// ```
258    pub fn timestamp_ms(&self) -> i64 {
259        self.timestamp() * 1000
260    }
261
262    /// Current date. Internally, this uses [DateTime::now].
263    ///
264    /// # Arguments
265    ///
266    /// * `tz_offset` - timezone offset in seconds, meaning as per [DateTime::now], must be less than `86_400`
267    ///
268    /// # Example
269    ///
270    /// ```
271    /// use speedate::Date;
272    ///
273    /// let d = Date::today(0).unwrap();
274    /// println!("The date today is: {}", d)
275    /// ```
276    pub fn today(tz_offset: i32) -> Result<Self, ParseError> {
277        Ok(DateTime::now(tz_offset)?.date)
278    }
279
280    /// Day of the year, starting from 1.
281    #[allow(clippy::bool_to_int_with_if)]
282    pub fn ordinal_day(&self) -> u16 {
283        let leap_extra = if is_leap_year(self.year) { 1 } else { 0 };
284        let day = self.day as u16;
285        match self.month {
286            1 => day,
287            2 => day + 31,
288            3 => day + 59 + leap_extra,
289            4 => day + 90 + leap_extra,
290            5 => day + 120 + leap_extra,
291            6 => day + 151 + leap_extra,
292            7 => day + 181 + leap_extra,
293            8 => day + 212 + leap_extra,
294            9 => day + 243 + leap_extra,
295            10 => day + 273 + leap_extra,
296            11 => day + 304 + leap_extra,
297            _ => day + 334 + leap_extra,
298        }
299    }
300
301    pub(crate) fn from_timestamp_calc(timestamp_second: i64) -> Result<(Self, u32), ParseError> {
302        if timestamp_second < UNIX_0000 {
303            return Err(ParseError::DateTooSmall);
304        }
305        if timestamp_second > UNIX_9999 {
306            return Err(ParseError::DateTooLarge);
307        }
308        let seconds_diff = timestamp_second - UNIX_0000;
309        let delta_days = seconds_diff / 86_400;
310        let delta_years = delta_days / 365;
311        let leap_years = intervening_leap_years(delta_years);
312
313        // year day is the day of the year, starting from 1
314        let mut ordinal_day: i16 = (delta_days % 365 - leap_years + 1) as i16;
315        let mut year: u16 = delta_years as u16;
316        let mut leap_year: bool = is_leap_year(year);
317        while ordinal_day < 1 {
318            year -= 1;
319            leap_year = is_leap_year(year);
320            ordinal_day += if leap_year { 366 } else { 365 };
321        }
322        let (month, day) = match leap_year {
323            true => leap_year_month_day(ordinal_day),
324            false => common_year_month_day(ordinal_day),
325        };
326        Ok((Self { year, month, day }, (timestamp_second.rem_euclid(86_400)) as u32))
327    }
328
329    /// Parse a date from bytes, no check is performed for extract characters at the end of the string
330    pub(crate) fn parse_bytes_partial(bytes: &[u8]) -> Result<Self, ParseError> {
331        if bytes.len() < 10 {
332            return Err(ParseError::TooShort);
333        }
334        let year: u16;
335        let month: u8;
336        let day: u8;
337        unsafe {
338            let y1 = get_digit_unchecked!(bytes, 0, InvalidCharYear) as u16;
339            let y2 = get_digit_unchecked!(bytes, 1, InvalidCharYear) as u16;
340            let y3 = get_digit_unchecked!(bytes, 2, InvalidCharYear) as u16;
341            let y4 = get_digit_unchecked!(bytes, 3, InvalidCharYear) as u16;
342            year = y1 * 1000 + y2 * 100 + y3 * 10 + y4;
343
344            match bytes.get_unchecked(4) {
345                b'-' => (),
346                _ => return Err(ParseError::InvalidCharDateSep),
347            }
348
349            let m1 = get_digit_unchecked!(bytes, 5, InvalidCharMonth);
350            let m2 = get_digit_unchecked!(bytes, 6, InvalidCharMonth);
351            month = m1 * 10 + m2;
352
353            match bytes.get_unchecked(7) {
354                b'-' => (),
355                _ => return Err(ParseError::InvalidCharDateSep),
356            }
357
358            let d1 = get_digit_unchecked!(bytes, 8, InvalidCharDay);
359            let d2 = get_digit_unchecked!(bytes, 9, InvalidCharDay);
360            day = d1 * 10 + d2;
361        }
362
363        // calculate the maximum number of days in the month, accounting for leap years in the
364        // gregorian calendar
365        let max_days = match month {
366            1 | 3 | 5 | 7 | 8 | 10 | 12 => 31,
367            4 | 6 | 9 | 11 => 30,
368            2 => {
369                if year % 4 == 0 && (year % 100 != 0 || year % 400 == 0) {
370                    29
371                } else {
372                    28
373                }
374            }
375            _ => return Err(ParseError::OutOfRangeMonth),
376        };
377
378        if day < 1 || day > max_days {
379            return Err(ParseError::OutOfRangeDay);
380        }
381
382        Ok(Self { year, month, day })
383    }
384}
385
386fn is_leap_year(year: u16) -> bool {
387    if year % 100 == 0 {
388        year % 400 == 0
389    } else {
390        year % 4 == 0
391    }
392}
393
394/// internal function to calculate the number of leap years since 0000, `delta_years` is the number of
395/// years since 0000
396fn intervening_leap_years(delta_years: i64) -> i64 {
397    if delta_years == 0 {
398        0
399    } else {
400        (delta_years - 1) / 4 - (delta_years - 1) / 100 + (delta_years - 1) / 400 + 1
401    }
402}
403
404fn leap_year_month_day(day: i16) -> (u8, u8) {
405    match day {
406        1..=31 => (1, day as u8),
407        32..=60 => (2, day as u8 - 31),
408        61..=91 => (3, day as u8 - 60),
409        92..=121 => (4, day as u8 - 91),
410        122..=152 => (5, day as u8 - 121),
411        153..=182 => (6, day as u8 - 152),
412        183..=213 => (7, day as u8 - 182),
413        214..=244 => (8, day as u8 - 213),
414        245..=274 => (9, (day - 244) as u8),
415        275..=305 => (10, (day - 274) as u8),
416        306..=335 => (11, (day - 305) as u8),
417        _ => (12, (day - 335) as u8),
418    }
419}
420
421fn common_year_month_day(day: i16) -> (u8, u8) {
422    match day {
423        1..=31 => (1, day as u8),
424        32..=59 => (2, day as u8 - 31),
425        60..=90 => (3, day as u8 - 59),
426        91..=120 => (4, day as u8 - 90),
427        121..=151 => (5, day as u8 - 120),
428        152..=181 => (6, day as u8 - 151),
429        182..=212 => (7, day as u8 - 181),
430        213..=243 => (8, day as u8 - 212),
431        244..=273 => (9, (day - 243) as u8),
432        274..=304 => (10, (day - 273) as u8),
433        305..=334 => (11, (day - 304) as u8),
434        _ => (12, (day - 334) as u8),
435    }
436}