emit_core/
timestamp.rs

1/*!
2The [`Timestamp`] type.
3
4A timestamp is a point in time, represented as the number of nanoseconds since the Unix epoch.
5
6Timestamps can be constructed manually through [`Timestamp::from_unix`], or the current timestamp can be read from an instance of [`crate::clock::Clock`].
7
8A timestamp can be converted into a point [`crate::extent::Extent`]. A pair of timestamps representing a timespan can be converted into a span [`crate::extent::Extent`].
9*/
10
11/*
12Parts of this file are adapted from other libraries:
13
14Prost:
15https://github.com/tokio-rs/prost/blob/master/prost-types/src/datetime.rs
16Licensed under Apache 2.0
17
18humantime:
19https://github.com/tailhook/humantime/blob/master/src/date.rs
20Licensed under MIT
21*/
22
23use core::{
24    cmp, fmt,
25    ops::{Add, AddAssign, Sub, SubAssign},
26    str::{self, FromStr},
27    time::Duration,
28};
29
30use crate::{
31    buf::Buffer,
32    value::{FromValue, ToValue, Value},
33};
34
35/**
36A Unix timestamp with nanosecond precision.
37*/
38#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
39pub struct Timestamp(Duration);
40
41/**
42The individual date and time portions of a timestamp.
43
44Values in parts are represented exactly as they would be when formatted into a timestamp. So months and days are both one-based instead of zero-based values.
45*/
46#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
47pub struct Parts {
48    /**
49    The zero-based year.
50    */
51    pub years: u16,
52    /**
53    The one-based month.
54    */
55    pub months: u8,
56    /**
57    The one-based day.
58    */
59    pub days: u8,
60    /**
61    The zero-based hour of the day.
62    */
63    pub hours: u8,
64    /**
65    The zero-based minute of the hour.
66    */
67    pub minutes: u8,
68    /**
69    The zero-based second of the minute.
70    */
71    pub seconds: u8,
72    /**
73    The zero-based subsecond precision.
74    */
75    pub nanos: u32,
76}
77
78// 2000-03-01 (mod 400 year, immediately after feb29
79const LEAPOCH_SECS: u64 = 946_684_800 + 86400 * (31 + 29);
80const DAYS_PER_400Y: i32 = 365 * 400 + 97;
81const DAYS_PER_100Y: i32 = 365 * 100 + 24;
82const DAYS_PER_4Y: i32 = 365 * 4 + 1;
83const DAYS_IN_MONTH: [u8; 12] = [31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31, 29];
84
85// 1970-01-01T00:00:00.000000000Z
86const MIN: Duration = Duration::new(0, 0);
87
88// 9999-12-31T23:59:59.999999999Z
89const MAX: Duration = Duration::new(253402300799, 999999999);
90
91impl Timestamp {
92    /**
93    The minimum timestamp, `1970-01-01T00:00:00Z`.
94    */
95    pub const MIN: Self = Timestamp(MIN);
96
97    /**
98    The maximum timestamp, `9999-12-31T23:59:59.999999999Z`.
99    */
100    pub const MAX: Self = Timestamp(MAX);
101
102    /**
103    Try create a timestamp from time since the Unix epoch.
104
105    If the `unix_time` is within [`Timestamp::MIN`]..=[`Timestamp::MAX`] then this method will return `Some`. Otherwise it will return `None`.
106    */
107    pub fn from_unix(unix_time: Duration) -> Option<Self> {
108        if unix_time >= MIN && unix_time <= MAX {
109            Some(Timestamp(unix_time))
110        } else {
111            None
112        }
113    }
114
115    /**
116    Get the value of the timestamp as time since the Unix epoch.
117    */
118    pub fn to_unix(&self) -> Duration {
119        self.0
120    }
121
122    /**
123    Try parse a timestamp from an RFC3339 formatted representation.
124    */
125    pub fn try_from_str(ts: &str) -> Result<Self, ParseTimestampError> {
126        ts.parse()
127    }
128
129    /**
130    Try parse a timestamp from an RFC3339 formatted value.
131    */
132    pub fn parse(ts: impl fmt::Display) -> Result<Self, ParseTimestampError> {
133        let mut buf = Buffer::<30>::new();
134
135        Self::try_from_str(
136            str::from_utf8(buf.buffer(ts).ok_or_else(|| ParseTimestampError {})?)
137                .map_err(|_| ParseTimestampError {})?,
138        )
139    }
140
141    /**
142    Calculate the timespan between two timestamps.
143
144    This method will return `None` if `earlier` is actually after `self`.
145    */
146    pub fn duration_since(self, earlier: Self) -> Option<Duration> {
147        self.0.checked_sub(earlier.0)
148    }
149
150    /**
151    Convert the timestamp into a system timestamp.
152
153    This method can be used for interoperability with code expecting a standard library timestamp.
154    */
155    #[cfg(feature = "std")]
156    pub fn to_system_time(&self) -> std::time::SystemTime {
157        std::time::SystemTime::UNIX_EPOCH + self.0
158    }
159
160    /**
161    Try get a timestamp from its individual date and time parts.
162
163    If the resulting timestamp is within [`Timestamp::MIN`]..=[`Timestamp::MAX`] then this method will return `Some`. Otherwise it will return `None`.
164
165    If any field of `parts` would overflow its maximum value, such as `days: 32`, then it will wrap into the next unit.
166    */
167    pub fn from_parts(parts: Parts) -> Option<Self> {
168        let is_leap;
169        let start_of_year;
170        let year = (parts.years as i64) - 1900;
171
172        // Fast path for years 1900 - 2038.
173        // The `as u64` conversion here turns negative values
174        // into very large positive ones, failing the `<=`
175        if year as u64 <= 138 {
176            let mut leaps: i64 = (year - 68) >> 2;
177            if (year - 68).trailing_zeros() >= 2 {
178                leaps -= 1;
179                is_leap = true;
180            } else {
181                is_leap = false;
182            }
183
184            start_of_year = i128::from(31_536_000 * (year - 70) + 86400 * leaps);
185        } else {
186            let centuries: i64;
187            let mut leaps: i64;
188
189            let mut cycles: i64 = (year - 100) / 400;
190            let mut rem: i64 = (year - 100) % 400;
191
192            if rem < 0 {
193                cycles -= 1;
194                rem += 400
195            }
196            if rem == 0 {
197                is_leap = true;
198                centuries = 0;
199                leaps = 0;
200            } else {
201                if rem >= 200 {
202                    if rem >= 300 {
203                        centuries = 3;
204                        rem -= 300;
205                    } else {
206                        centuries = 2;
207                        rem -= 200;
208                    }
209                } else if rem >= 100 {
210                    centuries = 1;
211                    rem -= 100;
212                } else {
213                    centuries = 0;
214                }
215                if rem == 0 {
216                    is_leap = false;
217                    leaps = 0;
218                } else {
219                    leaps = rem / 4;
220                    rem %= 4;
221                    is_leap = rem == 0;
222                }
223            }
224            leaps += 97 * cycles + 24 * centuries - i64::from(is_leap);
225
226            start_of_year = i128::from((year - 100) * 31_536_000)
227                + i128::from(leaps * 86400 + 946_684_800 + 86400);
228        }
229
230        let seconds_within_month = 86400 * u32::from(parts.days - 1)
231            + 3600 * u32::from(parts.hours)
232            + 60 * u32::from(parts.minutes)
233            + u32::from(parts.seconds);
234
235        let mut seconds_within_year = [
236            0,           // Jan
237            31 * 86400,  // Feb
238            59 * 86400,  // Mar
239            90 * 86400,  // Apr
240            120 * 86400, // May
241            151 * 86400, // Jun
242            181 * 86400, // Jul
243            212 * 86400, // Aug
244            243 * 86400, // Sep
245            273 * 86400, // Oct
246            304 * 86400, // Nov
247            334 * 86400, // Dec
248        ][usize::from(parts.months - 1) % 12]
249            + seconds_within_month;
250
251        if is_leap && parts.months > 2 {
252            seconds_within_year += 86400
253        }
254
255        Timestamp::from_unix(Duration::new(
256            (start_of_year + i128::from(seconds_within_year))
257                .try_into()
258                .ok()?,
259            parts.nanos,
260        ))
261    }
262
263    /**
264    Get the individual date and time parts of the timestamp.
265
266    The returned parts are in exactly the form needed to display them. Months and days are both one-based.
267    */
268    pub fn to_parts(&self) -> Parts {
269        let dur = self.0;
270        let secs = dur.as_secs();
271        let nanos = dur.subsec_nanos();
272
273        let mut days = ((secs as i64 / 86_400) - (LEAPOCH_SECS as i64 / 86_400)) as i64;
274        let mut remsecs = (secs % 86_400) as i32;
275        if remsecs < 0i32 {
276            remsecs += 86_400;
277            days -= 1
278        }
279
280        let mut qc_cycles: i32 = (days / (DAYS_PER_400Y as i64)) as i32;
281        let mut remdays: i32 = (days % (DAYS_PER_400Y as i64)) as i32;
282        if remdays < 0 {
283            remdays += DAYS_PER_400Y;
284            qc_cycles -= 1;
285        }
286
287        let mut c_cycles: i32 = remdays / DAYS_PER_100Y;
288        if c_cycles == 4 {
289            c_cycles -= 1;
290        }
291        remdays -= c_cycles * DAYS_PER_100Y;
292
293        let mut q_cycles: i32 = remdays / DAYS_PER_4Y;
294        if q_cycles == 25 {
295            q_cycles -= 1;
296        }
297        remdays -= q_cycles * DAYS_PER_4Y;
298
299        let mut remyears: i32 = remdays / 365;
300        if remyears == 4 {
301            remyears -= 1;
302        }
303        remdays -= remyears * 365;
304
305        let mut years: i64 = i64::from(remyears)
306            + 4 * i64::from(q_cycles)
307            + 100 * i64::from(c_cycles)
308            + 400 * i64::from(qc_cycles);
309
310        let mut months: i32 = 0;
311        while i32::from(DAYS_IN_MONTH[months as usize]) <= remdays {
312            remdays -= i32::from(DAYS_IN_MONTH[months as usize]);
313            months += 1
314        }
315
316        if months >= 10 {
317            months -= 12;
318            years += 1;
319        }
320
321        let years = (years + 2000) as u16;
322        let months = (months + 3) as u8;
323        let days = (remdays + 1) as u8;
324        let hours = (remsecs / 3600) as u8;
325        let minutes = (remsecs / 60 % 60) as u8;
326        let seconds = (remsecs % 60) as u8;
327
328        Parts {
329            years,
330            months,
331            days,
332            hours,
333            minutes,
334            seconds,
335            nanos,
336        }
337    }
338
339    /**
340    Add a duration to this timestamp.
341
342    If the result would be greater than [`Timestamp::MAX`] then `None` is returned.
343    */
344    #[must_use = "the result of addition is returned without modifying the original"]
345    pub fn checked_add(&self, rhs: Duration) -> Option<Self> {
346        Timestamp::from_unix(self.to_unix().checked_add(rhs)?)
347    }
348
349    /**
350    Subtract a duration to this timestamp.
351
352    If the result would be less than [`Timestamp::MIN`] then `None` is returned.
353    */
354    #[must_use = "the result of subtraction is returned without modifying the original"]
355    pub fn checked_sub(&self, rhs: Duration) -> Option<Self> {
356        Timestamp::from_unix(self.to_unix().checked_sub(rhs)?)
357    }
358
359    /**
360    Get the duration between this timestamp and an earlier one.
361    */
362    pub fn checked_duration_since(&self, earlier: Timestamp) -> Option<Duration> {
363        self.to_unix().checked_sub(earlier.to_unix())
364    }
365}
366
367impl Add<Duration> for Timestamp {
368    type Output = Timestamp;
369
370    fn add(self, rhs: Duration) -> Self::Output {
371        self.checked_add(rhs).expect("overflow adding to timestamp")
372    }
373}
374
375impl AddAssign<Duration> for Timestamp {
376    fn add_assign(&mut self, rhs: Duration) {
377        *self = *self + rhs;
378    }
379}
380
381impl Sub<Duration> for Timestamp {
382    type Output = Timestamp;
383
384    fn sub(self, rhs: Duration) -> Self::Output {
385        self.checked_sub(rhs)
386            .expect("overflow subtracting from timestamp")
387    }
388}
389
390impl SubAssign<Duration> for Timestamp {
391    fn sub_assign(&mut self, rhs: Duration) {
392        *self = *self - rhs;
393    }
394}
395
396impl Sub<Timestamp> for Timestamp {
397    type Output = Duration;
398
399    fn sub(self, earlier: Timestamp) -> Self::Output {
400        self.checked_duration_since(earlier)
401            .expect("overflow subtracting from timestamp")
402    }
403}
404
405impl fmt::Debug for Timestamp {
406    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
407        use fmt::Write as _;
408
409        f.write_char('"')?;
410        fmt_rfc3339(*self, f)?;
411        f.write_char('"')
412    }
413}
414
415impl fmt::Display for Timestamp {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        fmt_rfc3339(*self, f)
418    }
419}
420
421impl FromStr for Timestamp {
422    type Err = ParseTimestampError;
423
424    fn from_str(s: &str) -> Result<Self, Self::Err> {
425        parse_rfc3339(s)
426    }
427}
428
429impl ToValue for Timestamp {
430    fn to_value(&self) -> Value<'_> {
431        Value::capture_display(self)
432    }
433}
434
435impl<'v> FromValue<'v> for Timestamp {
436    fn from_value(value: Value<'v>) -> Option<Self> {
437        value
438            .downcast_ref::<Timestamp>()
439            .copied()
440            .or_else(|| value.parse())
441    }
442}
443
444impl<'a> PartialEq<&'a Timestamp> for Timestamp {
445    fn eq(&self, other: &&'a Timestamp) -> bool {
446        self == *other
447    }
448}
449
450impl<'a> PartialEq<Timestamp> for &'a Timestamp {
451    fn eq(&self, other: &Timestamp) -> bool {
452        *self == other
453    }
454}
455
456/**
457An error attempting to parse a [`Timestamp`] from text.
458*/
459#[derive(Debug)]
460pub struct ParseTimestampError {}
461
462impl fmt::Display for ParseTimestampError {
463    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
464        write!(f, "the input was not a valid timestamp")
465    }
466}
467
468#[cfg(feature = "std")]
469impl std::error::Error for ParseTimestampError {}
470
471fn parse_rfc3339(fmt: &str) -> Result<Timestamp, ParseTimestampError> {
472    if fmt.len() > 30 || fmt.len() < 19 {
473        // Invalid length
474        return Err(ParseTimestampError {});
475    }
476
477    if *fmt.as_bytes().last().unwrap() != b'Z' {
478        // Non-UTC
479        return Err(ParseTimestampError {});
480    }
481
482    let years = u16::from_str_radix(&fmt[0..4], 10).map_err(|_| ParseTimestampError {})?;
483    let months = u8::from_str_radix(&fmt[5..7], 10).map_err(|_| ParseTimestampError {})?;
484    let days = u8::from_str_radix(&fmt[8..10], 10).map_err(|_| ParseTimestampError {})?;
485    let hours = u8::from_str_radix(&fmt[11..13], 10).map_err(|_| ParseTimestampError {})?;
486    let minutes = u8::from_str_radix(&fmt[14..16], 10).map_err(|_| ParseTimestampError {})?;
487    let seconds = u8::from_str_radix(&fmt[17..19], 10).map_err(|_| ParseTimestampError {})?;
488    let nanos = if fmt.len() > 19 {
489        let subsecond = &fmt[20..fmt.len() - 1];
490        u32::from_str_radix(subsecond, 10).unwrap() * 10u32.pow(9 - subsecond.len() as u32)
491    } else {
492        0
493    };
494
495    Timestamp::from_parts(Parts {
496        years,
497        months,
498        days,
499        hours,
500        minutes,
501        seconds,
502        nanos,
503    })
504    .ok_or_else(|| ParseTimestampError {})
505}
506
507fn fmt_rfc3339(ts: Timestamp, f: &mut fmt::Formatter) -> fmt::Result {
508    let Parts {
509        years,
510        months,
511        days,
512        hours,
513        minutes,
514        seconds,
515        nanos: subsecond_nanos,
516    } = ts.to_parts();
517
518    const BUF_INIT: [u8; 30] = *b"0000-00-00T00:00:00.000000000Z";
519
520    let mut buf: [u8; 30] = BUF_INIT;
521    buf[0] = b'0' + (years / 1000) as u8;
522    buf[1] = b'0' + (years / 100 % 10) as u8;
523    buf[2] = b'0' + (years / 10 % 10) as u8;
524    buf[3] = b'0' + (years % 10) as u8;
525    buf[5] = b'0' + (months / 10) as u8;
526    buf[6] = b'0' + (months % 10) as u8;
527    buf[8] = b'0' + (days / 10) as u8;
528    buf[9] = b'0' + (days % 10) as u8;
529    buf[11] = b'0' + (hours / 10) as u8;
530    buf[12] = b'0' + (hours % 10) as u8;
531    buf[14] = b'0' + (minutes / 10) as u8;
532    buf[15] = b'0' + (minutes % 10) as u8;
533    buf[17] = b'0' + (seconds / 10) as u8;
534    buf[18] = b'0' + (seconds % 10) as u8;
535
536    let i = match f.precision() {
537        Some(0) => 19,
538        precision => {
539            let mut i = 20;
540            let mut divisor = 100_000_000;
541            let end = i + cmp::min(9, precision.unwrap_or(9));
542
543            while i < end {
544                buf[i] = b'0' + (subsecond_nanos / divisor % 10) as u8;
545
546                i += 1;
547                divisor /= 10;
548            }
549
550            i
551        }
552    };
553
554    buf[i] = b'Z';
555
556    // we know our chars are all ascii
557    f.write_str(str::from_utf8(&buf[..=i]).expect("Conversion to utf8 failed"))
558}
559
560#[cfg(feature = "sval")]
561impl sval::Value for Timestamp {
562    fn stream<'sval, S: sval::Stream<'sval> + ?Sized>(&'sval self, stream: &mut S) -> sval::Result {
563        sval::stream_display(stream, self)
564    }
565}
566
567#[cfg(feature = "serde")]
568impl serde::Serialize for Timestamp {
569    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
570        serializer.collect_str(self)
571    }
572}
573
574#[cfg(test)]
575mod tests {
576    use super::*;
577
578    #[test]
579    fn roundtrip() {
580        let ts = Timestamp::from_unix(Duration::new(1691961703, 17532)).unwrap();
581
582        let fmt = ts.to_string();
583
584        for parsed in [
585            Timestamp::try_from_str(&fmt),
586            Timestamp::parse(&fmt),
587            fmt.parse(),
588        ] {
589            let parsed = parsed.unwrap();
590
591            assert_eq!(ts, parsed, "{}", fmt);
592        }
593    }
594
595    #[test]
596    fn parse_invalid() {
597        for case in [
598            "",
599            "0",
600            "2024-01-01T00:00:00.00000000000000000000000000Z",
601            "2024-01-01T00:00:00.000+10",
602            "Thursday, September 12, 2024",
603        ] {
604            assert!(Timestamp::try_from_str(case).is_err());
605            assert!(Timestamp::parse(case).is_err());
606        }
607    }
608
609    #[test]
610    fn parts_max() {
611        let ts = Timestamp::from_parts(Parts {
612            years: 9999,
613            months: 12,
614            days: 31,
615            hours: 23,
616            minutes: 59,
617            seconds: 59,
618            nanos: 999999999,
619        })
620        .unwrap();
621
622        assert_eq!(ts.to_unix(), MAX);
623    }
624
625    #[test]
626    fn parts_min() {
627        let ts = Timestamp::from_parts(Parts {
628            years: 1970,
629            months: 1,
630            days: 1,
631            hours: 0,
632            minutes: 0,
633            seconds: 0,
634            nanos: 0,
635        })
636        .unwrap();
637
638        assert_eq!(ts.to_unix(), MIN);
639    }
640
641    #[test]
642    fn parts_overflow() {
643        let ts = Timestamp::from_parts(Parts {
644            years: 2000,
645            months: 13,
646            days: 32,
647            hours: 25,
648            minutes: 61,
649            seconds: 61,
650            nanos: 1000000000,
651        })
652        .unwrap();
653
654        let expected = Timestamp::from_parts(Parts {
655            years: 2000,
656            months: 13,
657            days: 32,
658            hours: 25,
659            minutes: 61,
660            seconds: 62,
661            nanos: 0,
662        })
663        .unwrap();
664
665        assert_eq!(expected, ts);
666    }
667
668    #[test]
669    fn add() {
670        for (case, add, expected) in [
671            (
672                Timestamp::MIN,
673                Duration::from_nanos(1),
674                Some(Timestamp::from_unix(Duration::from_nanos(1)).unwrap()),
675            ),
676            (Timestamp::MAX, Duration::from_nanos(1), None),
677            (Timestamp::MAX, Duration::MAX, None),
678        ] {
679            assert_eq!(expected, case.checked_add(add));
680        }
681    }
682
683    #[test]
684    fn sub() {
685        for (case, sub, expected) in [
686            (
687                Timestamp::MAX,
688                Duration::from_nanos(1),
689                Some(Timestamp::from_unix(MAX - Duration::from_nanos(1)).unwrap()),
690            ),
691            (Timestamp::MIN, Duration::from_nanos(1), None),
692            (Timestamp::MIN, Duration::MAX, None),
693        ] {
694            assert_eq!(expected, case.checked_sub(sub));
695        }
696    }
697
698    #[test]
699    fn sub_timestamp() {
700        for (case, earlier, expected) in [
701            (Timestamp::MIN, Timestamp::MIN, Some(Duration::from_secs(0))),
702            (
703                Timestamp::from_unix(Duration::from_secs(10)).unwrap(),
704                Timestamp::from_unix(Duration::from_secs(0)).unwrap(),
705                Some(Duration::from_secs(10)),
706            ),
707            (
708                Timestamp::from_unix(Duration::from_secs(0)).unwrap(),
709                Timestamp::from_unix(Duration::from_secs(10)).unwrap(),
710                None,
711            ),
712            (Timestamp::MAX, Timestamp::MIN, Some(MAX)),
713        ] {
714            assert_eq!(expected, case.checked_duration_since(earlier));
715        }
716    }
717
718    #[test]
719    fn to_from_value() {
720        for case in [
721            Timestamp::MIN,
722            Timestamp::MAX,
723            Timestamp::from_unix(Duration::from_secs(1)).unwrap(),
724        ] {
725            let value = case.to_value();
726
727            assert_eq!(case, value.cast::<Timestamp>().unwrap());
728        }
729
730        for (case, expected) in [
731            (
732                Value::from("2024-01-01T00:13:00.000Z"),
733                Some(
734                    Timestamp::from_parts(Parts {
735                        years: 2024,
736                        months: 01,
737                        days: 01,
738                        hours: 00,
739                        minutes: 13,
740                        seconds: 00,
741                        nanos: 000,
742                    })
743                    .unwrap(),
744                ),
745            ),
746            (Value::from(""), None),
747            (Value::from("12024-01-01T00:13:00.000Z"), None),
748            (Value::from("2024-01-01T00:13:00.000+10"), None),
749        ] {
750            assert_eq!(expected, case.cast::<Timestamp>());
751        }
752    }
753
754    #[cfg(feature = "sval")]
755    #[test]
756    fn stream() {
757        sval_test::assert_tokens(
758            &Timestamp::try_from_str("2024-01-01T00:13:00.000Z").unwrap(),
759            &[
760                sval_test::Token::TextBegin(None),
761                sval_test::Token::TextFragmentComputed("2024-01-01T00:13:00.000000000Z".to_owned()),
762                sval_test::Token::TextEnd,
763            ],
764        );
765    }
766
767    #[cfg(feature = "serde")]
768    #[test]
769    fn serialize() {
770        serde_test::assert_ser_tokens(
771            &Timestamp::try_from_str("2024-01-01T00:13:00.000Z").unwrap(),
772            &[serde_test::Token::Str("2024-01-01T00:13:00.000000000Z")],
773        );
774    }
775}