Skip to main content

rfc3339_fast/
lib.rs

1//! # RFC3339 Timestamp Library
2//!
3//! A high-performance library for parsing and formatting RFC3339/ISO8601
4//! timestamps, with support for nanosecond precision.
5//!
6//! ## Overview
7//!
8//! This library provides efficient serialization and deserialization of RFC3339/ISO8601
9//! timestamps in the format `YYYY-MM-DDTHH:mm:ss[.nnn]Z`. It supports:
10//!
11//! - Timestamps from year 1 to year 9999
12//! - Nanosecond precision (up to 9 decimal places)
13//! - Integration with `std::time::SystemTime` (with the default `std` feature)
14//! - `no_std` support by disabling default features
15//! - Optional support for `chrono` types via the `chrono` feature
16//! - Optional `serde` integration via the `serde` feature
17//! - SIMD acceleration on platforms supporting SSSE3 (`x86`/`x86_64`) or NEON (ARM)
18//!
19//! ## Examples
20//!
21//! Parsing a timestamp from a string:
22//!
23//! ```
24//! use std::str::FromStr;
25//! # use rfc3339_fast::Timestamp;
26//! let ts = Timestamp::from_str("2026-02-25T14:30:00Z").unwrap();
27//! ```
28//!
29//! Formatting a timestamp to a string:
30//!
31//! ```
32//! # use rfc3339_fast::{Timestamp, Buffer};
33//! let ts = Timestamp::now();
34//! let mut buf = Buffer::new();
35//! let formatted = buf.format(ts);
36//! ```
37
38#![cfg_attr(not(feature = "std"), no_std)]
39#![deny(unsafe_op_in_unsafe_fn)]
40// Crate-wide clippy::pedantic suppressions. Each one is intentional and
41// scoped narrowly enough that the local code makes the safety/perf reason
42// obvious; we silence them at the crate root to keep the hot paths
43// uncluttered by `#[allow]` attributes on every line.
44//
45// * `cast_possible_wrap`, `cast_sign_loss`, `cast_lossless`,
46//   `cast_possible_truncation`: the date-arithmetic and SIMD code does a
47//   lot of `u32 ↔ i32` and `usize → u16`/`u32` casts on values that are
48//   provably in range (years bounded by 1–9999, lengths bounded by
49//   `BUFFER_SIZE = 30`, JD math pre-biased into the positive range, etc.).
50//   Switching to `From::from` / `TryFrom` would either fail to compile
51//   (different signedness) or add a runtime check on a hot path.
52// * `unreadable_literal`: constants like `2440588` (Julian Day of the Unix
53//   epoch) and `253402300799` (max representable Unix-seconds value) are
54//   well-known reference numbers; underscore-grouping them obscures the
55//   reference more than it helps.
56// * `inline_always`: the per-byte `write_byte` / `write_number` /
57//   `jsonenc_nanos` helpers are tiny leaf functions on the format hot
58//   path; benchmarks regress noticeably if the inliner is allowed to
59//   second-guess them.
60// * `items_after_statements`: a few local `const`s are placed next to
61//   their first use inside `jsonenc_timestamp` to keep the algorithm and
62//   its magic numbers visually adjacent.
63#![allow(
64    clippy::cast_possible_wrap,
65    clippy::cast_sign_loss,
66    clippy::cast_lossless,
67    clippy::cast_possible_truncation,
68    clippy::unreadable_literal,
69    clippy::inline_always,
70    clippy::items_after_statements
71)]
72
73use core::{fmt, mem::MaybeUninit, ptr, str::FromStr};
74
75#[cfg(feature = "std")]
76use std::time::{Duration, SystemTime, UNIX_EPOCH};
77
78#[cfg(target_feature = "ssse3")]
79mod sse;
80
81#[cfg(target_feature = "neon")]
82mod neon;
83
84/// Error type for parsing or formatting JSON timestamps.
85///
86/// This enum represents errors that can occur when parsing timestamp strings
87/// or validating timestamp values.
88///
89/// Marked `#[non_exhaustive]` so additional variants can be introduced in a
90/// future release without a semver break; downstream `match` expressions
91/// must include a wildcard arm.
92#[derive(Debug, Clone, Copy, PartialEq, Eq)]
93#[non_exhaustive]
94pub enum TimestampError {
95    /// The input string had an invalid format.
96    InvalidFormat,
97    /// The timestamp value is out of the supported range
98    /// (year 1 through year 9999, with `nanos < 1_000_000_000`).
99    OutOfRange,
100}
101
102impl fmt::Display for TimestampError {
103    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
104        match self {
105            TimestampError::InvalidFormat => write!(f, "invalid timestamp format"),
106            TimestampError::OutOfRange => write!(f, "timestamp value out of range"),
107        }
108    }
109}
110
111impl core::error::Error for TimestampError {}
112
113#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
114/// A timestamp value that can be serialized to and deserialized from JSON.
115///
116/// `Timestamp` is stored internally as `(seconds: i64, nanos: u32)`, where
117/// `seconds` counts (signed) seconds since the Unix epoch and `nanos` is
118/// always in `[0, 1_000_000_000)`. This matches the
119/// [`google.protobuf.Timestamp`] convention and lets the type be
120/// `no_std`-compatible. Negative whole-second values combined with a
121/// positive `nanos` mean the same instant as the float
122/// `seconds + nanos / 1e9`, e.g. `(-5, 200_000_000)` is `-4.8s`.
123///
124/// ## Invariants
125///
126/// Every `Timestamp` value satisfies:
127///
128/// * `seconds` is in `SECONDS_MIN..=SECONDS_MAX`, i.e. it represents an
129///   instant between `0001-01-01T00:00:00Z` and `9999-12-31T23:59:59Z`
130///   inclusive.
131/// * `nanos` is in `[0, 1_000_000_000)`.
132///
133/// All public constructors enforce these invariants — [`Timestamp::now`],
134/// [`Timestamp::from_unix`], [`Timestamp::from_str`], `From<SystemTime>`,
135/// and the optional `From<chrono::DateTime<Tz>>` either validate, saturate,
136/// or are infallible by construction. As a result, [`Buffer::format`] and
137/// [`fmt::Display`] never fail.
138///
139/// With the default `std` feature, this type round-trips to and from
140/// [`std::time::SystemTime`] efficiently — the conversion is just a
141/// `duration_since(UNIX_EPOCH)` followed by storing the two fields
142/// (saturating at the supported range bounds).
143///
144/// [`google.protobuf.Timestamp`]: https://protobuf.dev/reference/protobuf/google.protobuf/#timestamp
145///
146/// ## Creating timestamps
147///
148/// ```
149/// # use rfc3339_fast::Timestamp;
150/// // From Unix seconds + nanoseconds.
151/// let ts = Timestamp::from_unix(1_641_006_000, 0).unwrap();
152///
153/// // Equivalent via the `TryFrom<(i64, u32)>` impl.
154/// let ts = Timestamp::try_from((1_641_006_000i64, 0u32)).unwrap();
155///
156/// // With the `std` feature: from current system time.
157/// # #[cfg(feature = "std")] {
158/// let now = Timestamp::now();
159/// let ts = Timestamp::from(std::time::SystemTime::now());
160/// # }
161/// ```
162///
163/// ## Inspecting timestamps
164///
165/// ```
166/// # use rfc3339_fast::Timestamp;
167/// let ts = Timestamp::from_unix(1_641_006_000, 250_000_000).unwrap();
168/// assert_eq!(ts.seconds(), 1_641_006_000);
169/// assert_eq!(ts.subsec_nanos(), 250_000_000);
170/// ```
171pub struct Timestamp {
172    /// Signed seconds since the Unix epoch.
173    seconds: i64,
174    /// Fractional seconds, always in `[0, 1_000_000_000)`.
175    nanos: u32,
176}
177
178impl Timestamp {
179    /// Constructs a `Timestamp` directly from canonical `(seconds, nanos)`
180    /// fields without range checks. Internal helper: callers must
181    /// guarantee `seconds` is in `SECONDS_MIN..=SECONDS_MAX` and `nanos`
182    /// is in `[0, 1_000_000_000)`.
183    #[inline]
184    fn new_unchecked(seconds: i64, nanos: u32) -> Self {
185        debug_assert!(nanos < 1_000_000_000);
186        Self { seconds, nanos }
187    }
188
189    /// Constructs a `Timestamp` from Unix seconds and nanoseconds.
190    ///
191    /// Returns [`TimestampError::OutOfRange`] if `seconds` is outside the
192    /// representable year 1..=9999 range, or if `nanos >= 1_000_000_000`.
193    ///
194    /// # Examples
195    ///
196    /// ```
197    /// # use rfc3339_fast::Timestamp;
198    /// let ts = Timestamp::from_unix(0, 0).unwrap();
199    /// assert_eq!(ts.seconds(), 0);
200    /// assert_eq!(ts.subsec_nanos(), 0);
201    /// ```
202    pub fn from_unix(seconds: i64, nanos: u32) -> Result<Self, TimestampError> {
203        if !(SECONDS_MIN..=SECONDS_MAX).contains(&seconds) || nanos >= 1_000_000_000 {
204            return Err(TimestampError::OutOfRange);
205        }
206        Ok(Self::new_unchecked(seconds, nanos))
207    }
208
209    /// Returns the (signed) seconds component, counted from the Unix epoch.
210    #[inline]
211    #[must_use]
212    pub fn seconds(&self) -> i64 {
213        self.seconds
214    }
215
216    /// Returns the fractional-second component, in nanoseconds.
217    ///
218    /// The returned value is always in `[0, 1_000_000_000)`. The naming
219    /// matches [`std::time::Duration::subsec_nanos`].
220    #[inline]
221    #[must_use]
222    pub fn subsec_nanos(&self) -> u32 {
223        self.nanos
224    }
225
226    /// Returns a `Timestamp` representing the current system time.
227    ///
228    /// This is a convenience method for `Timestamp::from(SystemTime::now())`.
229    #[cfg(feature = "std")]
230    #[must_use]
231    pub fn now() -> Self {
232        Self::from(SystemTime::now())
233    }
234}
235
236/// Converts a `SystemTime` into a `Timestamp` by computing its offset from
237/// the Unix epoch.
238///
239/// `SystemTime` can in principle represent instants outside the
240/// `Timestamp` range (year 1 through year 9999); such values are
241/// **saturated** to the nearest in-range second. In practice this only
242/// affects deliberately constructed `SystemTime`s; wall-clock times from
243/// the system clock are well within range.
244#[cfg(feature = "std")]
245impl From<SystemTime> for Timestamp {
246    #[inline]
247    fn from(value: SystemTime) -> Self {
248        // Both branches collapse to a single `duration_since` plus a
249        // small fixup; LLVM inlines this away when `format` is called
250        // directly on a `SystemTime`. The final `clamp` enforces the
251        // `Timestamp` range invariant.
252        let (seconds, nanos) = match value.duration_since(UNIX_EPOCH) {
253            Ok(dur) => (dur.as_secs() as i64, dur.subsec_nanos()),
254            Err(e) => {
255                let dur_before = e.duration();
256                let secs_before = -(dur_before.as_secs() as i64);
257                let nanos_before = dur_before.subsec_nanos();
258                if nanos_before > 0 {
259                    (secs_before - 1, 1_000_000_000 - nanos_before)
260                } else {
261                    (secs_before, 0)
262                }
263            }
264        };
265        // Saturate out-of-range values to the supported bounds. When
266        // saturating to `SECONDS_MAX`, drop sub-second precision so the
267        // result still represents `9999-12-31T23:59:59Z`.
268        if seconds < SECONDS_MIN {
269            Self::new_unchecked(SECONDS_MIN, 0)
270        } else if seconds > SECONDS_MAX {
271            Self::new_unchecked(SECONDS_MAX, 0)
272        } else {
273            Self::new_unchecked(seconds, nanos)
274        }
275    }
276}
277
278/// Wraps a `SystemTime` reference as a `Timestamp`. See [`From<SystemTime>`]
279/// for the saturation contract.
280#[cfg(feature = "std")]
281impl From<&SystemTime> for Timestamp {
282    #[inline]
283    fn from(value: &SystemTime) -> Self {
284        Self::from(*value)
285    }
286}
287
288/// Converts a `Timestamp` back into a `SystemTime` by adding (or
289/// subtracting) its offset from the Unix epoch.
290#[cfg(feature = "std")]
291impl From<Timestamp> for SystemTime {
292    #[inline]
293    fn from(value: Timestamp) -> Self {
294        if value.seconds >= 0 {
295            UNIX_EPOCH + Duration::new(value.seconds as u64, value.nanos)
296        } else {
297            // Canonical form has nanos in [0, 1e9). For negative seconds,
298            // a non-zero `nanos` adds time *forward*, so the magnitude of
299            // the offset is `-seconds - 1` whole seconds plus `1e9 - nanos`
300            // sub-second component (when nanos > 0).
301            let (mag_secs, mag_nanos) = if value.nanos == 0 {
302                ((-value.seconds) as u64, 0)
303            } else {
304                ((-value.seconds - 1) as u64, 1_000_000_000 - value.nanos)
305            };
306            UNIX_EPOCH - Duration::new(mag_secs, mag_nanos)
307        }
308    }
309}
310
311impl From<&Timestamp> for Timestamp {
312    #[inline]
313    fn from(value: &Timestamp) -> Self {
314        *value
315    }
316}
317
318#[cfg(feature = "chrono")]
319impl<Tz: chrono::TimeZone> From<chrono::DateTime<Tz>> for Timestamp {
320    fn from(value: chrono::DateTime<Tz>) -> Self {
321        // chrono's `timestamp()` returns signed Unix seconds and
322        // `timestamp_subsec_nanos()` returns nanos in [0, 1e9), so this
323        // already matches our canonical form — no SystemTime detour.
324        // Saturate to the `Timestamp` range to uphold its invariant.
325        let seconds = value.timestamp();
326        let nanos = value.timestamp_subsec_nanos();
327        if seconds < SECONDS_MIN {
328            Self::new_unchecked(SECONDS_MIN, 0)
329        } else if seconds > SECONDS_MAX {
330            Self::new_unchecked(SECONDS_MAX, 0)
331        } else {
332            Self::new_unchecked(seconds, nanos)
333        }
334    }
335}
336
337#[cfg(feature = "chrono")]
338impl From<Timestamp> for chrono::DateTime<chrono::Utc> {
339    fn from(value: Timestamp) -> Self {
340        chrono::DateTime::<chrono::Utc>::from_timestamp(value.seconds, value.nanos)
341            .expect("Timestamp out of range for chrono::DateTime")
342    }
343}
344
345#[cfg(feature = "serde")]
346impl serde_core::Serialize for Timestamp {
347    #[inline]
348    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
349    where
350        S: serde_core::Serializer,
351    {
352        let mut buf = Buffer::new();
353        serializer.serialize_str(buf.format(self))
354    }
355}
356
357#[cfg(feature = "serde")]
358impl<'de> serde_core::Deserialize<'de> for Timestamp {
359    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
360    where
361        D: serde_core::Deserializer<'de>,
362    {
363        struct TsVisitor;
364
365        impl serde_core::de::Visitor<'_> for TsVisitor {
366            type Value = Timestamp;
367
368            #[inline]
369            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
370                formatter.write_str("an ISO8601 Timestamp")
371            }
372
373            #[inline]
374            fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
375            where
376                E: serde_core::de::Error,
377            {
378                Timestamp::from_str(v).map_err(|_e| E::custom("Invalid Format"))
379            }
380
381            #[inline]
382            fn visit_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
383            where
384                E: serde_core::de::Error,
385            {
386                let s = core::str::from_utf8(v).map_err(|_| E::custom("Invalid Format"))?;
387                self.visit_str(s)
388            }
389        }
390        deserializer.deserialize_str(TsVisitor)
391    }
392}
393
394impl TryFrom<(i64, u32)> for Timestamp {
395    type Error = TimestampError;
396
397    #[inline]
398    fn try_from(value: (i64, u32)) -> Result<Self, Self::Error> {
399        Self::from_unix(value.0, value.1)
400    }
401}
402
403impl fmt::Display for Timestamp {
404    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
405        let mut buf = Buffer::new();
406        write!(f, "{}", buf.format(self))
407    }
408}
409
410impl FromStr for Timestamp {
411    type Err = TimestampError;
412
413    fn from_str(s: &str) -> Result<Self, Self::Err> {
414        let mut ascii = s.as_bytes();
415
416        #[cfg(target_feature = "ssse3")]
417        let (seconds, nanos) = unsafe {
418            (
419                sse::decode_seconds(&mut ascii)?,
420                sse::decode_nanos(&mut ascii)?,
421            )
422        };
423
424        #[cfg(target_feature = "neon")]
425        let (seconds, nanos) = unsafe {
426            (
427                neon::decode_seconds(&mut ascii)?,
428                neon::decode_nanos(&mut ascii)?,
429            )
430        };
431
432        #[cfg(not(any(target_feature = "ssse3", target_feature = "neon")))]
433        let (seconds, nanos) = (decode_seconds(&mut ascii)?, decode_nanos(&mut ascii)?);
434
435        let offset = match ascii.first() {
436            Some(b'Z') => 0,
437            Some(&c @ (b'+' | b'-')) => decode_offset(ascii, c)?,
438            _ => return Err(TimestampError::InvalidFormat),
439        };
440
441        // `decode_nanos` returns `i32` in `[0, 1_000_000_000)`, and
442        // `decode_seconds` keeps the year in 1..=9999, so the cast and
443        // unchecked constructor are sound here.
444        Ok(Self::new_unchecked(seconds + offset, nanos as u32))
445    }
446}
447
448/// Decodes an RFC3339 numeric timezone offset of the form `+HH:MM` or `-HH:MM`.
449///
450/// Returns the value (in seconds) that must be added to the local-time seconds
451/// to yield UTC seconds. For example, `+05:00` yields `-18000`.
452#[inline]
453fn decode_offset(ascii: &[u8], sign: u8) -> Result<i64, TimestampError> {
454    // Expect exactly 6 bytes: [+-]HH:MM
455    if ascii.len() != 6 || ascii[3] != b':' {
456        return Err(TimestampError::InvalidFormat);
457    }
458
459    let h10 = ascii[1].wrapping_sub(b'0');
460    let h1 = ascii[2].wrapping_sub(b'0');
461    let m10 = ascii[4].wrapping_sub(b'0');
462    let m1 = ascii[5].wrapping_sub(b'0');
463    if (h10 | h1 | m10 | m1) > 9 {
464        return Err(TimestampError::InvalidFormat);
465    }
466
467    let hours = h10 as i64 * 10 + h1 as i64;
468    let mins = m10 as i64 * 10 + m1 as i64;
469    if hours > 23 || mins > 59 {
470        return Err(TimestampError::InvalidFormat);
471    }
472
473    let magnitude = hours * 3600 + mins * 60;
474    // local = UTC + offset (when sign is '+'), so UTC = local - offset.
475    Ok(if sign == b'+' { -magnitude } else { magnitude })
476}
477
478// 30 bytes is exactly enough for the longest valid timestamp:
479//   `YYYY-MM-DDTHH:mm:ss.sssssssssZ` (4+1+2+1+2 + 1 + 2+1+2+1+2 + 1+9 + 1 = 30).
480// Combined with a `u16` length below, the whole `Buffer` is 32 bytes — one
481// cacheline / four qwords — which keeps it cheap to copy and well-aligned on
482// the stack.
483const BUFFER_SIZE: usize = 30; // YYYY-MM-DDTHH:mm:ss.sssssssssZ
484const SECONDS_MIN: i64 = -62135596800;
485const SECONDS_MAX: i64 = 253402300799;
486
487/// Pre-computed pair table: byte `2*N` and `2*N+1` are the ASCII digits of
488/// `N` for `N` in `0..=99`. Used by `write_2_at` / `write_4_at` /
489/// `write_9_at` to convert a value to its two-digit ASCII form with one
490/// indexed `u16` load instead of two scalar divides.
491const PAIR_TABLE: [u8; 200] = {
492    let mut t = [0u8; 200];
493    let mut i = 0;
494    while i < 100 {
495        t[i * 2] = b'0' + (i / 10) as u8;
496        t[i * 2 + 1] = b'0' + (i % 10) as u8;
497        i += 1;
498    }
499    t
500};
501
502/// A reusable buffer for formatting timestamps to strings.
503///
504/// `Buffer` provides an efficient way to format multiple timestamps without
505/// allocating memory. It uses a fixed-size stack-allocated buffer that is
506/// large enough to hold any valid ISO8601 timestamp with nanosecond precision.
507///
508/// The buffer size is 30 bytes, which is sufficient to hold the longest
509/// possible timestamp: `9999-12-31T23:59:59.999999999Z`.
510///
511/// ## Examples
512///
513/// ```
514/// # use rfc3339_fast::{Timestamp, Buffer};
515/// let mut buf = Buffer::new();
516/// let ts = Timestamp::now();
517/// let formatted = buf.format(ts);
518/// println!("{}", formatted);  // Prints the timestamp
519/// ```
520pub struct Buffer {
521    bytes: [MaybeUninit<u8>; BUFFER_SIZE],
522    // `len` is bounded by `BUFFER_SIZE` (30), so a `u16` is plenty and lets
523    // the whole struct round to a tidy 32 bytes. We `as`-cast freely between
524    // `len` and `usize` because the value is guaranteed to fit.
525    len: u16,
526}
527
528impl Default for Buffer {
529    #[inline]
530    fn default() -> Buffer {
531        Buffer::new()
532    }
533}
534
535impl Copy for Buffer {}
536
537// `Clone` is implemented manually rather than derived because the buffer
538// holds `MaybeUninit<u8>` whose contents are only valid for the
539// `..self.len` prefix; cloning by copying that uninitialized tail would
540// be wasteful and semantically meaningless. We instead return a fresh
541// empty buffer, which matches the typical usage pattern (a `Buffer` is
542// always reset before each `format` call). The two clippy lints below
543// flag the unusual semantics on purpose; we accept them.
544#[allow(clippy::non_canonical_clone_impl, clippy::expl_impl_clone_on_copy)]
545impl Clone for Buffer {
546    #[inline]
547    fn clone(&self) -> Self {
548        Buffer::new()
549    }
550}
551
552impl Buffer {
553    #[inline]
554    /// Creates a new empty `Buffer`.
555    ///
556    /// The buffer can then be used to format multiple timestamps.
557    #[must_use]
558    pub fn new() -> Buffer {
559        let bytes = [MaybeUninit::<u8>::uninit(); BUFFER_SIZE];
560        Buffer { bytes, len: 0 }
561    }
562
563    /// Formats a timestamp into an ISO8601 string.
564    ///
565    /// This method converts a timestamp (or anything convertible to `Timestamp`)
566    /// into a string in the format `YYYY-MM-DDTHH:mm:ss[.nnn]Z`.
567    ///
568    /// The returned string is a borrowed reference to data stored in this buffer.
569    /// To format another timestamp, call `format` again and it will overwrite
570    /// the previous contents.
571    ///
572    /// This method is infallible: every [`Timestamp`] value is guaranteed by
573    /// construction to lie in the representable range
574    /// (`0001-01-01T00:00:00Z` through `9999-12-31T23:59:59.999999999Z`),
575    /// and `Buffer` is sized to hold the longest such string.
576    ///
577    /// # Arguments
578    ///
579    /// * `timestamp` - A value that can be converted to `Timestamp` (includes
580    ///   `SystemTime`, `&SystemTime`, `&Timestamp`, and `chrono::DateTime` when
581    ///   the `chrono` feature is enabled).
582    ///
583    /// # Returns
584    ///
585    /// A string slice containing the formatted timestamp.
586    pub fn format<T: Into<Timestamp>>(&mut self, timestamp: T) -> &str {
587        let timestamp = timestamp.into();
588        self.reset();
589
590        let seconds = timestamp.seconds;
591        let nanos = timestamp.nanos as i32;
592
593        // SAFETY/correctness: `Timestamp`'s constructors guarantee
594        // `seconds` lies in `SECONDS_MIN..=SECONDS_MAX` and `nanos` is in
595        // `[0, 1_000_000_000)`, so the date arithmetic and the
596        // fixed-offset writes inside `jsonenc_timestamp` stay within the
597        // 30-byte buffer.
598        debug_assert!((SECONDS_MIN..=SECONDS_MAX).contains(&seconds));
599        debug_assert!((0..1_000_000_000).contains(&nanos));
600
601        self.jsonenc_timestamp(seconds, nanos);
602        self.as_str()
603    }
604
605    #[inline]
606    fn reset(&mut self) {
607        self.len = 0;
608    }
609
610    /// Writes a single byte to the buffer.
611    ///
612    /// This is a low-level method used internally for formatting. The buffer
613    /// is sized to fit the longest possible ISO8601 timestamp, so callers
614    /// inside [`Buffer::jsonenc_timestamp`] never overflow it; we assert
615    /// this only in debug builds to keep the hot path branch-free.
616    #[inline(always)]
617    fn write_byte(&mut self, value: u8) {
618        let len = self.len as usize;
619        debug_assert!(len < BUFFER_SIZE, "Buffer overflow in write_byte");
620        // SAFETY: caller ensures len < BUFFER_SIZE; checked in debug builds.
621        unsafe {
622            let end = self.bytes.as_mut_ptr().cast::<u8>().add(len);
623            ptr::write(end, value);
624        }
625        self.len = (len + 1) as u16;
626    }
627
628    /// Writes a numeric value with a fixed number of digits to the buffer.
629    ///
630    /// Pads with leading zeros as needed. For example, `write_number(42, 4)` writes
631    /// `0042`. Processes digits in pairs for efficiency.
632    ///
633    /// # Arguments
634    ///
635    /// * `value` - The number to write
636    /// * `digits` - The number of digits to write (with zero-padding)
637    #[inline(always)]
638    fn write_number(&mut self, mut value: u32, mut digits: usize) {
639        let len = self.len as usize + digits;
640        debug_assert!(len <= BUFFER_SIZE, "Buffer overflow in write_number");
641        if BUFFER_SIZE >= len {
642            unsafe {
643                self.len = len as u16;
644                let mut ptr = self.bytes.as_mut_ptr().cast::<u8>().add(len - 1);
645                // process 2 digits per iteration, this loop will likely be unrolled
646                while digits >= 2 {
647                    // combine these so the compiler can optimize both operations
648                    let d1;
649                    (value, d1) = (value / 100, value % 100);
650
651                    let (a, b) = (d1 / 10, d1 % 10);
652                    digits -= 1;
653                    ptr.write(b as u8 | b'0');
654                    ptr = ptr.sub(1);
655                    digits -= 1;
656                    ptr.write(a as u8 | b'0');
657                    ptr = ptr.sub(1);
658                }
659
660                // handle remainder
661                if digits == 1 {
662                    ptr.write(value as u8 | b'0');
663                }
664            }
665        }
666    }
667
668    /// Encodes a Unix timestamp into ISO8601 format in the buffer.
669    ///
670    /// Converts seconds and nanoseconds since the epoch into a formatted string
671    /// in the format `YYYY-MM-DDTHH:mm:ss[.nnn...]Z`.
672    ///
673    /// # Arguments
674    ///
675    /// * `seconds` - Seconds since the Unix epoch
676    /// * `nanos` - Nanoseconds (0-999,999,999)
677    ///
678    /// # Algorithm
679    ///
680    /// The date portion is computed by the Fliegel/Van Flandern algorithm,
681    /// which converts a Julian Day Number into a Gregorian (Y, M, D) triple
682    /// using only integer arithmetic — no lookup tables and no branches.
683    /// The original 1968 publication is:
684    ///
685    /// > Fliegel, H. F., and Van Flandern, T. C., "A Machine Algorithm for
686    /// > Processing Calendar Dates," Communications of the ACM, vol. 11
687    /// > no. 10 (October 1968), p. 657.
688    ///
689    /// The magic constants encode the Gregorian calendar's irregular cycle
690    /// of month lengths and leap years:
691    ///
692    /// * `146097` — days in a 400-year Gregorian cycle (the smallest cycle
693    ///   over which the calendar exactly repeats: `400*365 + 100 - 4 + 1`).
694    /// * `1461`   — days in a 4-year Julian cycle (`4*365 + 1`).
695    /// * `2447 / 80` — a piecewise-linear approximation of cumulative
696    ///   month lengths (March-based), exploiting truncating integer
697    ///   division so that successive months fall on the right day-of-year
698    ///   boundary without a lookup table.
699    /// * `68569`  — shifts the Julian Day Number into the algorithm's
700    ///   internal positive range.
701    /// * `49`     — recovers the original year after the 100-year
702    ///   regrouping done by the `n` term.
703    ///
704    /// We pre-bias `seconds` by the offset from 0001-01-01 to 1970-01-01 so
705    /// the value passed to the integer divisions is always non-negative,
706    /// which matches the algorithm's preconditions and avoids the
707    /// round-toward-zero pitfalls of signed division on negative operands.
708    ///
709    /// Background and a friendly walk-through of the Fortran original (and
710    /// of the related branchless variants) is in Josh Haberman's article
711    /// <https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html>.
712    /// This implementation is a Rust port inspired by upb's C version:
713    /// <https://github.com/protocolbuffers/protobuf/blob/27421b97a0daa29e91460d377b0213f9e7be5d3f/upb/json/encode.c#L122>.
714    #[inline(always)]
715    fn jsonenc_timestamp(&mut self, mut seconds: i64, nanos: i32) {
716        const SECONDS_PER_DAY: i32 = 86400;
717
718        // Days from 0001-01-01 (proleptic Gregorian) to 1970-01-01.
719        const CE_EPOCH_TO_UNIX_EPOCH_DAYS: i32 = 719_162;
720        const CE_EPOCH_TO_UNIX_EPOCH_SECONDS: i64 =
721            CE_EPOCH_TO_UNIX_EPOCH_DAYS as i64 * SECONDS_PER_DAY as i64;
722
723        // Julian Day Number of the Unix epoch (1970-01-01). The Julian
724        // period starts on -4713-11-24 (proleptic Gregorian), so the
725        // Unix epoch is day 2_440_588 of that count.
726        const JD_UNIX_EPOCH: i32 = 2_440_588;
727
728        // Pre-fill the buffer with the fixed parts of the output:
729        //   YYYY-MM-DDTHH:MM:SS.NNNNNNNNNZ
730        //   0123456789012345678901234567890
731        //             1111111111222222222
732        // Underscores are placeholders for digits we'll overwrite below.
733        // Doing this as one 30-byte block lets LLVM lower it to a pair of
734        // 16-byte SSE stores; in exchange, the 9 separator bytes (`-`,
735        // `T`, `:`, `.`, `Z`) never need to be written individually inside
736        // the hot path. Just as importantly, by writing each digit at a
737        // *fixed* offset (rather than threading `self.len` through every
738        // call) we break the serial dependency chain between writes, so
739        // the back end can issue them in parallel.
740        const TEMPLATE: [u8; 30] = *b"____-__-__T__:__:__.000000000Z";
741        // SAFETY: `self.bytes` is `[MaybeUninit<u8>; 30]`, exactly the
742        // same size as `TEMPLATE`, and `MaybeUninit<u8>` has the same
743        // layout as `u8`.
744        unsafe {
745            ptr::copy_nonoverlapping(TEMPLATE.as_ptr(), self.bytes.as_mut_ptr().cast::<u8>(), 30);
746        }
747
748        // Bias into the positive range expected by the F/VF formula, then
749        // convert seconds-since-CE-epoch into a Julian Day Number plus the
750        // algorithm's internal offset of 68569.
751        seconds += CE_EPOCH_TO_UNIX_EPOCH_SECONDS;
752        let days = (seconds / SECONDS_PER_DAY as i64) as i32;
753        let mut l = days - CE_EPOCH_TO_UNIX_EPOCH_DAYS + JD_UNIX_EPOCH + 68569;
754
755        // `n` is the number of completed 400-year Gregorian cycles since
756        // the algorithm's internal epoch; subtracting them out leaves a
757        // residue `l` in [0, 146096] (one full cycle of days).
758        let n = 4 * l / 146097;
759        l -= (146097 * n + 3) / 4;
760
761        // Within the cycle, recover the year (March-based) and the
762        // remaining day-of-year, then split that into month and day using
763        // the (80 * l / 2447) piecewise-linear month formula.
764        let mut year = 4000 * (l + 1) / 1461001;
765        l = l - 1461 * year / 4 + 31;
766        let mut month = 80 * l / 2447;
767        let day = l - 2447 * month / 80;
768
769        // Shift March-based months back to January-based, carrying into
770        // the year when month is 11 or 12 (i.e. Jan/Feb of the next year).
771        l = month / 11;
772        month = month + 2 - 12 * l;
773        year = 100 * (n - 49) + year + l;
774
775        // Time-of-day from the seconds-of-day remainder. Doing one i32
776        // divmod for the day boundary, then deriving h/m/s from the
777        // u32 remainder, keeps these off the i64 critical path.
778        let sod = (seconds - days as i64 * SECONDS_PER_DAY as i64) as u32; // 0..86400
779        let hour = sod / 3600;
780        let rem = sod % 3600;
781        let min = rem / 60;
782        let sec = rem % 60;
783
784        // SAFETY: all offsets are < 30 == BUFFER_SIZE; values are bounded
785        // (year <= 9999, the rest <= 99) so the digit math stays in u32.
786        unsafe {
787            self.write_4_at(year as u32, 0);
788            self.write_2_at(month as u32, 5);
789            self.write_2_at(day as u32, 8);
790            self.write_2_at(hour, 11);
791            self.write_2_at(min, 14);
792            self.write_2_at(sec, 17);
793        }
794
795        // Nanoseconds: figure out how many trailing groups of 3 are zero
796        // and patch the buffer accordingly. The template already contains
797        // ".000000000Z" at offsets 19..30, so when we omit the fraction
798        // entirely we just overwrite the '.' at 19 with 'Z' and stop
799        // there; the trailing template bytes are unread because `len`
800        // bounds `as_str`.
801        let final_len = if nanos == 0 {
802            // SAFETY: 19 < 30.
803            unsafe { self.write_byte_at(b'Z', 19) };
804            20
805        } else {
806            // Always materialize all 9 digits into [20..29]; the 'Z' at
807            // [29] from the template stays put. Then re-place 'Z' at 24
808            // or 27 if the trailing 6 / 3 nano digits are zero.
809            // SAFETY: offset 20 + 9 == 29 < 30.
810            unsafe { self.write_9_at(nanos as u32, 20) };
811            // Trim trailing groups of 3 zeros. We compute the trim from
812            // `nanos` directly (cheap divmods on a constant divisor) so
813            // the back end can hoist these alongside the digit writes.
814            if nanos % 1000 != 0 {
815                30
816            } else if (nanos / 1000) % 1000 != 0 {
817                // SAFETY: 26 < 30.
818                unsafe { self.write_byte_at(b'Z', 26) };
819                27
820            } else {
821                // SAFETY: 23 < 30.
822                unsafe { self.write_byte_at(b'Z', 23) };
823                24
824            }
825        };
826        self.len = final_len;
827    }
828
829    /// Encodes the nanosecond component of a timestamp.
830    ///
831    /// (Retained for callers that may want a stand-alone helper; the hot
832    /// `jsonenc_timestamp` path now writes nanos via fixed-offset stores
833    /// directly into the templated buffer instead of going through this
834    /// `self.len`-threaded routine.)
835    #[allow(dead_code)]
836    #[inline(always)]
837    fn jsonenc_nanos(&mut self, mut nanos: u32) {
838        if nanos == 0 {
839            return;
840        }
841        let mut digits = 9;
842
843        let mut q;
844        let mut r;
845        (q, r) = (nanos / 1000, nanos % 1000);
846        if r != 0 {
847            self.write_byte(b'.');
848            self.write_number(nanos, digits);
849            return;
850        }
851        nanos = q;
852        digits -= 3;
853        (q, r) = (nanos / 1000, nanos % 1000);
854        if r != 0 {
855            self.write_byte(b'.');
856            self.write_number(nanos, digits);
857            return;
858        }
859        nanos = q;
860        digits -= 3;
861        r = nanos % 1000;
862        if r != 0 {
863            self.write_byte(b'.');
864            self.write_number(nanos, digits);
865        }
866    }
867
868    /// Writes a single byte at a fixed offset (no `self.len` update).
869    ///
870    /// # Safety
871    ///
872    /// `offset` must be `< BUFFER_SIZE`.
873    #[inline(always)]
874    unsafe fn write_byte_at(&mut self, value: u8, offset: usize) {
875        debug_assert!(offset < BUFFER_SIZE);
876        unsafe {
877            self.bytes
878                .as_mut_ptr()
879                .cast::<u8>()
880                .add(offset)
881                .write(value);
882        }
883    }
884
885    /// Writes a 2-digit zero-padded number at a fixed offset.
886    ///
887    /// Uses a 200-byte ASCII pair table (`"00", "01", …, "99"`) to do the
888    /// conversion as one 2-byte load + one 2-byte store, saving the two
889    /// `div_by_10`s the obvious code would use. The table is pre-built at
890    /// compile time.
891    ///
892    /// # Safety
893    ///
894    /// `offset + 1` must be `< BUFFER_SIZE` and `value` must be `< 100`.
895    #[inline(always)]
896    unsafe fn write_2_at(&mut self, value: u32, offset: usize) {
897        debug_assert!(offset + 1 < BUFFER_SIZE && value < 100);
898        unsafe {
899            let src = PAIR_TABLE.as_ptr().add(value as usize * 2);
900            let dst = self.bytes.as_mut_ptr().cast::<u8>().add(offset);
901            ptr::copy_nonoverlapping(src, dst, 2);
902        }
903    }
904
905    /// Writes a 4-digit zero-padded number at a fixed offset.
906    ///
907    /// Splits the value into two 2-digit halves and emits each via the
908    /// `PAIR_TABLE`, so the 4 ASCII bytes are produced by two 2-byte
909    /// table loads instead of four scalar divides.
910    ///
911    /// # Safety
912    ///
913    /// `offset + 3` must be `< BUFFER_SIZE` and `value` must be `< 10_000`.
914    #[inline(always)]
915    unsafe fn write_4_at(&mut self, value: u32, offset: usize) {
916        debug_assert!(offset + 3 < BUFFER_SIZE && value < 10_000);
917        let hi = (value / 100) as usize;
918        let lo = (value % 100) as usize;
919        unsafe {
920            let dst = self.bytes.as_mut_ptr().cast::<u8>().add(offset);
921            ptr::copy_nonoverlapping(PAIR_TABLE.as_ptr().add(hi * 2), dst, 2);
922            ptr::copy_nonoverlapping(PAIR_TABLE.as_ptr().add(lo * 2), dst.add(2), 2);
923        }
924    }
925
926    /// Writes a 9-digit zero-padded number at a fixed offset.
927    ///
928    /// # Safety
929    ///
930    /// `offset + 8` must be `< BUFFER_SIZE` and `value` must be
931    /// `< 1_000_000_000`.
932    #[inline(always)]
933    unsafe fn write_9_at(&mut self, value: u32, offset: usize) {
934        debug_assert!(offset + 8 < BUFFER_SIZE && value < 1_000_000_000);
935        // Split into 1 + 2 + 2 + 2 + 2 digits so we can use the pair
936        // table for everything but the leading digit.
937        let q1 = value / 100_000_000; // top 1 digit (0..9)
938        let r1 = value % 100_000_000;
939        let q2 = r1 / 1_000_000; // next 2 digits
940        let r2 = r1 % 1_000_000;
941        let q3 = r2 / 10_000; // next 2 digits
942        let r3 = r2 % 10_000;
943        let q4 = r3 / 100; // next 2 digits
944        let q5 = r3 % 100; // last 2 digits
945        unsafe {
946            let dst = self.bytes.as_mut_ptr().cast::<u8>().add(offset);
947            dst.write(q1 as u8 | b'0');
948            ptr::copy_nonoverlapping(PAIR_TABLE.as_ptr().add(q2 as usize * 2), dst.add(1), 2);
949            ptr::copy_nonoverlapping(PAIR_TABLE.as_ptr().add(q3 as usize * 2), dst.add(3), 2);
950            ptr::copy_nonoverlapping(PAIR_TABLE.as_ptr().add(q4 as usize * 2), dst.add(5), 2);
951            ptr::copy_nonoverlapping(PAIR_TABLE.as_ptr().add(q5 as usize * 2), dst.add(7), 2);
952        }
953    }
954
955    /// Returns the formatted timestamp as a string slice.
956    ///
957    /// This method safely converts the buffer's uninitialized bytes to a UTF-8 string.
958    fn as_str(&self) -> &str {
959        // SAFETY: `self.len` is only advanced by `write_byte`/`write_number`,
960        // which write valid bytes via raw pointers, so the prefix
961        // `..self.len` is fully initialized.
962        let written = unsafe { self.bytes.get_unchecked(..self.len as usize) };
963        // SAFETY: `MaybeUninit<u8>` and `u8` have identical layout, and the
964        // bytes are initialized (above). All writes use ASCII exclusively.
965        unsafe {
966            core::str::from_utf8_unchecked(
967                &*(ptr::from_ref::<[MaybeUninit<u8>]>(written) as *const [u8]),
968            )
969        }
970    }
971}
972
973#[inline]
974#[allow(dead_code)]
975fn atoi_consume(ascii: &mut &[u8]) -> i32 {
976    let mut n: i32 = 0;
977    let (s, neg) = match ascii[0] {
978        b'-' => (&ascii[1..], true),
979        b'+' => (&ascii[1..], false),
980        _ => (*ascii, false),
981    };
982
983    let mut idx: usize = 0;
984    // Compute n as a negative number to avoid overflow
985    for c in s {
986        if !c.is_ascii_digit() {
987            break;
988        }
989        idx += 1;
990        n = n * 10 - i32::from(c & 0x0f);
991    }
992
993    *ascii = &s[idx..];
994    if neg {
995        n
996    } else {
997        -n
998    }
999}
1000
1001/// Decodes the seconds component from an ISO8601 timestamp string.
1002///
1003/// Parses the date and time portion of an ISO8601 timestamp string
1004/// (format: `YYYY-MM-DDTHH:mm:ss`) and returns the Unix timestamp
1005/// (seconds since 1970-01-01T00:00:00Z).
1006///
1007/// # Arguments
1008///
1009/// * `ascii` - A mutable reference to a byte slice. On success, this is
1010///   advanced past the parsed seconds field.
1011///
1012/// # Returns
1013///
1014/// - `Ok(seconds)` - The number of seconds since Unix epoch
1015/// - `Err(TimestampError::InvalidFormat)` - If the format is invalid
1016#[inline]
1017#[allow(dead_code)]
1018fn decode_seconds(ascii: &mut &[u8]) -> Result<i64, TimestampError> {
1019    // 1972-01-01T01:00:00
1020    let year = decode_tsdigits(ascii, 4, Some(b'-'))?;
1021    let mon = decode_tsdigits(ascii, 2, Some(b'-'))?;
1022    let day = decode_tsdigits(ascii, 2, Some(b'T'))?;
1023    let hour = decode_tsdigits(ascii, 2, Some(b':'))?;
1024    let min = decode_tsdigits(ascii, 2, Some(b':'))?;
1025    let sec = decode_tsdigits(ascii, 2, None)?;
1026
1027    Ok(jsondec_unixtime(year, mon, day, hour, min, sec))
1028}
1029
1030/// Decodes a sequence of digits from a timestamp string.
1031///
1032/// Parses a fixed number of ASCII digits from the input and optionally
1033/// validates a delimiter character after the digits.
1034///
1035/// # Arguments
1036///
1037/// * `ascii` - A mutable reference to a byte slice to parse from
1038/// * `digits` - The number of ASCII digits to parse
1039/// * `after` - An optional expected delimiter character. If provided and
1040///   doesn't match the character after the digits, returns an error.
1041///
1042/// # Returns
1043///
1044/// - `Ok(value)` - The parsed integer value
1045/// - `Err(TimestampError::InvalidFormat)` - If parsing fails or delimiter doesn't match
1046#[inline]
1047#[allow(dead_code)]
1048fn decode_tsdigits(
1049    ascii: &mut &[u8],
1050    mut digits: usize,
1051    after: Option<u8>,
1052) -> Result<i32, TimestampError> {
1053    if after.is_some_and(|v| v != ascii[digits]) {
1054        return Err(TimestampError::InvalidFormat);
1055    }
1056    let mut s = &ascii[..digits];
1057    let i = atoi_consume(&mut s);
1058    if !s.is_empty() {
1059        return Err(TimestampError::InvalidFormat);
1060    }
1061
1062    if after.is_some() {
1063        digits += 1;
1064    }
1065    *ascii = &ascii[digits..];
1066    Ok(i)
1067}
1068
1069/// Decodes the nanoseconds component from an ISO8601 timestamp string.
1070///
1071/// Parses the optional fractional seconds portion of a timestamp
1072/// (format: `.nnn` where n is 3, 6, or 9 digits).
1073///
1074/// # Arguments
1075///
1076/// * `ascii` - A mutable reference to a byte slice. On success, this is
1077///   advanced past the parsed nanoseconds field.
1078///
1079/// # Returns
1080///
1081/// - `Ok(nanos)` - The nanosecond value (0-999,999,999)
1082/// - `Err(TimestampError::InvalidFormat)` - If the fractional seconds format is invalid
1083///   (must be 3, 6, or 9 digits)
1084#[inline]
1085#[allow(dead_code)]
1086fn decode_nanos(ascii: &mut &[u8]) -> Result<i32, TimestampError> {
1087    let mut nanos: i32 = 0;
1088    if ascii[0] == b'.' {
1089        let mut remaining = &ascii[1..];
1090        nanos = atoi_consume(&mut remaining);
1091        let digits = ascii.len() - 1 - remaining.len();
1092        match digits {
1093            3 | 6 | 9 => {}
1094            _ => {
1095                return Err(TimestampError::InvalidFormat);
1096            }
1097        }
1098        let mut exp_lg10 = 9 - digits as i32;
1099        while exp_lg10 > 0 {
1100            exp_lg10 -= 1;
1101            nanos *= 10;
1102        }
1103        *ascii = remaining;
1104    }
1105    Ok(nanos)
1106}
1107
1108/// Calculates the number of days from a given date to the Unix epoch (1970-01-01).
1109///
1110/// # Arguments
1111///
1112/// * `y` - Year
1113/// * `m` - Month (1-12)
1114/// * `d` - Day (1-31)
1115///
1116/// # Returns
1117///
1118/// The number of days since the Unix epoch (negative for dates before 1970-01-01).
1119///
1120/// # Note
1121///
1122/// `jsondec_epochdays(1970, 1, 1) == 0`.
1123///
1124/// # Algorithm
1125///
1126/// This is the inverse direction of the Fliegel/Van Flandern conversion
1127/// used by [`Buffer::jsonenc_timestamp`]: given (Y, M, D) it returns the
1128/// signed day count since 1970-01-01 without lookup tables and (after
1129/// optimization) without branches.
1130///
1131/// The shape of the formula is due to Howard Hinnant
1132/// (<http://howardhinnant.github.io/date_algorithms.html#days_from_civil>),
1133/// with the specific power-of-two divisor variant due to Gerben Stavenga
1134/// — both surveyed in Josh Haberman's article
1135/// <https://blog.reverberate.org/2020/05/12/optimizing-date-algorithms.html>.
1136/// This is a Rust port of the upb C implementation:
1137/// <https://github.com/protocolbuffers/protobuf/blob/27421b97a0daa29e91460d377b0213f9e7be5d3f/upb/json/encode.c>.
1138///
1139/// Key tricks:
1140///
1141/// * **March-based year.** Treating March as month 1 puts the leap day at
1142///   the *end* of the year, which makes the leap-year correction depend
1143///   only on the year (not on whether the month is past February). The
1144///   `carry` term subtracts 1 from the year for January and February.
1145/// * **Year base of 4800.** Adding 4800 (a multiple of 400) ensures `y_adj`
1146///   is always non-negative for any supported input, so the unsigned
1147///   divisions below behave like floor-division.
1148/// * **`(62719 * m_adj + 769) / 2048`.** A piecewise-linear approximation
1149///   of cumulative month lengths whose divisor is a power of two, so the
1150///   compiler lowers the division to a shift. Equivalent in output to the
1151///   more familiar `(153 * m_adj + 2) / 5` from Hinnant.
1152/// * **`y/4 - y/100 + y/400`.** Standard Gregorian leap-day count.
1153/// * **`-2472632`.** Re-bases the result onto the Unix epoch
1154///   (`365*4800 + leap_days(4800) + 0` for 1970-01-01).
1155#[inline]
1156fn jsondec_epochdays(y: i32, m: i32, d: i32) -> i32 {
1157    const YEAR_BASE: u32 = 4800; // Before min year, multiple of 400.
1158
1159    let m_adj: u32 = (m - 3) as u32; // March-based month.
1160
1161    // `m_adj` underflows in u32 for January/February (m < 3), wrapping to
1162    // a value much larger than `m`; that's the signal we need to borrow a
1163    // year and shift the month into the March-based [0, 11] range.
1164    let carry: u32 = u32::from(m_adj > m as u32);
1165
1166    let adjust: u32 = if carry == 1 { 12 } else { 0 };
1167
1168    let y_adj: u32 = y as u32 + YEAR_BASE - carry;
1169    let month_days: u32 = ((adjust.wrapping_add(m_adj)) * 62719 + 769) / 2048;
1170    let leap_days: u32 = y_adj / 4 - y_adj / 100 + y_adj / 400;
1171
1172    y_adj as i32 * 365 + leap_days as i32 + month_days as i32 + (d - 1) - 2472632
1173}
1174
1175/// Converts a date/time to Unix timestamp (seconds since epoch).
1176///
1177/// Combines the given date components into a single Unix timestamp value.
1178///
1179/// # Arguments
1180///
1181/// * `y` - Year
1182/// * `m` - Month (1-12)
1183/// * `d` - Day (1-31)
1184/// * `h` - Hour (0-23)
1185/// * `min` - Minute (0-59)
1186/// * `s` - Second (0-59)
1187///
1188/// # Returns
1189///
1190/// The number of seconds since the Unix epoch (1970-01-01T00:00:00Z).
1191#[allow(clippy::many_single_char_names)]
1192fn jsondec_unixtime(y: i32, m: i32, d: i32, h: i32, min: i32, s: i32) -> i64 {
1193    i64::from(jsondec_epochdays(y, m, d)) * 86400
1194        + i64::from(h) * 3600
1195        + i64::from(min) * 60
1196        + i64::from(s)
1197}
1198
1199#[cfg(all(test, feature = "std", feature = "serde"))]
1200mod tests {
1201    use serde_test::{assert_tokens, Token};
1202    use std::time::Duration;
1203
1204    use super::*;
1205
1206    /// Tests decoding of the seconds component from an ISO8601 timestamp.
1207    #[test]
1208    fn test_decode_seconds() {
1209        let s = "2026-02-25T14:30:00Z";
1210        let input = &mut s.as_bytes();
1211        assert_eq!(decode_seconds(input).unwrap(), 1772029800);
1212        assert_eq!(input, b"Z");
1213    }
1214
1215    /// Tests that invalid characters in the seconds field are properly rejected.
1216    #[test]
1217    fn test_decode_seconds_invalid_chars() {
1218        let s = "20/6-02-25T14:30:00Z";
1219        let input = &mut s.as_bytes();
1220        assert!(decode_seconds(input).is_err());
1221
1222        let s = "20:6-02-25T14:30:00Z";
1223        let input = &mut s.as_bytes();
1224        assert!(decode_seconds(input).is_err());
1225    }
1226
1227    /// Tests decoding of the nanoseconds component from an ISO8601 timestamp.
1228    #[test]
1229    fn test_decode_nanos() {
1230        let s = ".987654321Z";
1231        let input = &mut s.as_bytes();
1232        assert_eq!(decode_nanos(input).unwrap(), 987654321);
1233        assert_eq!(input, b"Z");
1234
1235        let s = ".987654+00:00";
1236        let input = &mut s.as_bytes();
1237        assert_eq!(decode_nanos(input).unwrap(), 987654000);
1238        assert_eq!(input, b"+00:00");
1239    }
1240
1241    /// Tests that invalid characters in the nanoseconds field are properly rejected.
1242    #[test]
1243    fn test_decode_nanos_invalid_chars() {
1244        let s = ".98/654321Z";
1245        let input = &mut s.as_bytes();
1246        assert!(decode_nanos(input).is_err());
1247
1248        let s = ".98:654321Z";
1249        let input = &mut s.as_bytes();
1250        assert!(decode_nanos(input).is_err());
1251    }
1252
1253    /// Tests ASCII-to-integer conversion with optional sign.
1254    #[test]
1255    fn test_atoi_consume() {
1256        let mut ascii = "1234ABCD".as_bytes();
1257        assert_eq!(atoi_consume(&mut ascii), 1234);
1258        assert_eq!(ascii, "ABCD".as_bytes());
1259
1260        let mut ascii = "-1234ABCD".as_bytes();
1261        assert_eq!(atoi_consume(&mut ascii), -1234);
1262        assert_eq!(ascii, "ABCD".as_bytes());
1263
1264        let mut ascii = "+1234ABCD".as_bytes();
1265        assert_eq!(atoi_consume(&mut ascii), 1234);
1266        assert_eq!(ascii, "ABCD".as_bytes());
1267    }
1268
1269    /// Tests writing zero-padded numbers to the buffer.
1270    #[test]
1271    fn test_buffer_write_number() {
1272        let mut buf = Buffer::new();
1273        buf.write_byte(b'A');
1274        buf.write_number(12345, 5);
1275        buf.write_byte(b'B');
1276        assert_eq!(buf.as_str(), "A12345B");
1277    }
1278
1279    /// Tests formatting of timestamps across various dates and precisions.
1280    #[test]
1281    fn test_buffer_format() {
1282        let mut buf = Buffer::new();
1283        for ts in timestamps() {
1284            assert_eq!(buf.format(ts.0), ts.1);
1285        }
1286    }
1287
1288    /// Tests parsing of ISO8601 timestamp strings across various dates and precisions.
1289    #[test]
1290    fn test_parse() {
1291        for ts in timestamps() {
1292            assert_eq!(Timestamp::from(ts.0), Timestamp::from_str(ts.1).unwrap());
1293        }
1294    }
1295
1296    /// Tests parsing of RFC3339 numeric timezone offsets.
1297    #[test]
1298    fn test_parse_offset() {
1299        // +05:00 means local is 5h ahead of UTC; the same instant in Z form is
1300        // 5h earlier.
1301        let utc = Timestamp::from_str("2026-02-25T09:30:00Z").unwrap();
1302        let off = Timestamp::from_str("2026-02-25T14:30:00+05:00").unwrap();
1303        assert_eq!(utc, off);
1304
1305        let utc = Timestamp::from_str("2026-02-25T19:30:00Z").unwrap();
1306        let off = Timestamp::from_str("2026-02-25T14:30:00-05:00").unwrap();
1307        assert_eq!(utc, off);
1308
1309        // +00:00 == Z
1310        let utc = Timestamp::from_str("2026-02-25T14:30:00Z").unwrap();
1311        let off = Timestamp::from_str("2026-02-25T14:30:00+00:00").unwrap();
1312        assert_eq!(utc, off);
1313
1314        // Fractional seconds preserved alongside an offset.
1315        let utc = Timestamp::from_str("2026-02-25T09:30:00.123456789Z").unwrap();
1316        let off = Timestamp::from_str("2026-02-25T14:30:00.123456789+05:00").unwrap();
1317        assert_eq!(utc, off);
1318
1319        // Half-hour offset.
1320        let utc = Timestamp::from_str("2026-02-25T09:00:00Z").unwrap();
1321        let off = Timestamp::from_str("2026-02-25T14:30:00+05:30").unwrap();
1322        assert_eq!(utc, off);
1323    }
1324
1325    /// Tests rejection of malformed timezone offsets and other trailing input.
1326    #[test]
1327    fn test_parse_offset_invalid() {
1328        // Missing colon.
1329        assert!(Timestamp::from_str("2026-02-25T14:30:00+0500").is_err());
1330        // Wrong colon position.
1331        assert!(Timestamp::from_str("2026-02-25T14:30:00+05.00").is_err());
1332        // Out-of-range hours/minutes.
1333        assert!(Timestamp::from_str("2026-02-25T14:30:00+24:00").is_err());
1334        assert!(Timestamp::from_str("2026-02-25T14:30:00+05:60").is_err());
1335        // Non-digit.
1336        assert!(Timestamp::from_str("2026-02-25T14:30:00+0a:00").is_err());
1337        // Trailing garbage after a valid offset.
1338        assert!(Timestamp::from_str("2026-02-25T14:30:00+05:00X").is_err());
1339        // Trailing garbage with no terminator at all.
1340        assert!(Timestamp::from_str("2026-02-25T14:30:00X").is_err());
1341        // Empty input after the time field.
1342        assert!(Timestamp::from_str("2026-02-25T14:30:00").is_err());
1343    }
1344
1345    /// Provides a collection of test timestamps with known string representations.
1346    fn timestamps() -> [(SystemTime, &'static str); 8] {
1347        [
1348            (
1349                UNIX_EPOCH + Duration::new(86400 + (60 * 60) + 60 + 1, 0),
1350                "1970-01-02T01:01:01Z",
1351            ),
1352            (
1353                UNIX_EPOCH + Duration::new(253402300799, 0),
1354                "9999-12-31T23:59:59Z",
1355            ),
1356            (
1357                UNIX_EPOCH + Duration::new(1641006000, 0),
1358                "2022-01-01T03:00:00Z",
1359            ),
1360            (
1361                UNIX_EPOCH - Duration::new(2208988800, 0),
1362                "1900-01-01T00:00:00Z",
1363            ),
1364            (
1365                UNIX_EPOCH - Duration::new(86400 + (60 * 60) + 60 + 1, 987654300),
1366                "1969-12-30T22:58:58.012345700Z",
1367            ),
1368            (
1369                UNIX_EPOCH + Duration::new(86400 + (60 * 60) + 60 + 1, 987654300),
1370                "1970-01-02T01:01:01.987654300Z",
1371            ),
1372            (
1373                UNIX_EPOCH + Duration::new(86400 + (60 * 60) + 60 + 1, 987654000),
1374                "1970-01-02T01:01:01.987654Z",
1375            ),
1376            (
1377                UNIX_EPOCH + Duration::new(86400 + (60 * 60) + 60 + 1, 987000000),
1378                "1970-01-02T01:01:01.987Z",
1379            ),
1380        ]
1381    }
1382
1383    /// Tests interoperability with chrono datetime types.
1384    #[test]
1385    #[cfg(feature = "chrono")]
1386    fn test_chrono() {
1387        let now = chrono::Utc::now();
1388        let ts: Timestamp = now.into();
1389        let st: SystemTime = ts.into();
1390        assert_eq!(st, now.into());
1391    }
1392
1393    /// Tests serialization and deserialization with serde.
1394    #[test]
1395    #[cfg(feature = "serde")]
1396    fn test_ser_de() {
1397        let ts: Timestamp = "2026-02-26T00:31:30.042Z".parse().unwrap();
1398        assert_tokens(&ts, &[Token::String("2026-02-26T00:31:30.042Z")]);
1399    }
1400
1401    /// Tests deserialization via `visit_bytes` (when the deserializer hands us
1402    /// the input as raw bytes instead of a `&str`).
1403    #[test]
1404    #[cfg(feature = "serde")]
1405    fn test_de_bytes() {
1406        use serde_test::{assert_de_tokens, assert_de_tokens_error, Token};
1407
1408        let ts: Timestamp = "2026-02-26T00:31:30.042Z".parse().unwrap();
1409        assert_de_tokens(&ts, &[Token::Bytes(b"2026-02-26T00:31:30.042Z")]);
1410
1411        // Non-UTF8 bytes hit the error branch in visit_bytes.
1412        assert_de_tokens_error::<Timestamp>(&[Token::Bytes(b"\xff\xfe")], "Invalid Format");
1413        // Valid UTF-8 but malformed timestamp hits visit_str's error branch.
1414        assert_de_tokens_error::<Timestamp>(&[Token::Str("not a timestamp")], "Invalid Format");
1415        // A wrong-typed token forces the deserializer to call `expecting()`
1416        // on the visitor when constructing the error message.
1417        assert_de_tokens_error::<Timestamp>(
1418            &[Token::I32(42)],
1419            "invalid type: integer `42`, expected an ISO8601 Timestamp",
1420        );
1421    }
1422
1423    /// Tests `TimestampError`'s `Display` impl for both variants.
1424    #[test]
1425    fn test_error_display() {
1426        assert_eq!(
1427            TimestampError::InvalidFormat.to_string(),
1428            "invalid timestamp format"
1429        );
1430        assert_eq!(
1431            TimestampError::OutOfRange.to_string(),
1432            "timestamp value out of range"
1433        );
1434        // Exercise the `core::error::Error` blanket impl.
1435        let e: &dyn core::error::Error = &TimestampError::InvalidFormat;
1436        assert!(e.source().is_none());
1437    }
1438
1439    /// Tests `TryFrom<(i64, u32)>` and `Timestamp::from_unix` for both
1440    /// the success and out-of-range paths.
1441    #[test]
1442    fn test_try_from_seconds_nanos() {
1443        let ts = Timestamp::try_from((0i64, 0u32)).unwrap();
1444        assert_eq!(SystemTime::from(ts), UNIX_EPOCH);
1445        assert_eq!(ts.seconds(), 0);
1446        assert_eq!(ts.subsec_nanos(), 0);
1447
1448        // A valid pre-epoch timestamp also exercises the negative-seconds
1449        // branch of `From<Timestamp> for SystemTime` with `nanos > 0`.
1450        let ts = Timestamp::from_unix(-1, 500_000_000).unwrap();
1451        assert_eq!(
1452            SystemTime::from(ts),
1453            UNIX_EPOCH - Duration::from_millis(500)
1454        );
1455        assert_eq!(ts.seconds(), -1);
1456        assert_eq!(ts.subsec_nanos(), 500_000_000);
1457
1458        assert_eq!(
1459            Timestamp::try_from((SECONDS_MIN - 1, 0)),
1460            Err(TimestampError::OutOfRange),
1461        );
1462        assert_eq!(
1463            Timestamp::try_from((SECONDS_MAX + 1, 0)),
1464            Err(TimestampError::OutOfRange),
1465        );
1466        // Out-of-range nanos are now rejected (previously silently coerced).
1467        assert_eq!(
1468            Timestamp::from_unix(0, 1_000_000_000),
1469            Err(TimestampError::OutOfRange),
1470        );
1471    }
1472
1473    /// Tests `Timestamp::now`, `Display`, and `Debug` impls.
1474    #[test]
1475    fn test_now_display_debug() {
1476        let now = Timestamp::now();
1477        // Display round-trips through the buffer formatter.
1478        let s = now.to_string();
1479        assert!(s.ends_with('Z'));
1480        assert_eq!(now, Timestamp::from_str(&s).unwrap());
1481
1482        // Debug format includes the `Timestamp { ... }` derive output.
1483        let dbg = format!("{now:?}");
1484        assert!(dbg.starts_with("Timestamp "), "got: {dbg}");
1485    }
1486
1487    /// Tests the various `From` conversions into `Timestamp`.
1488    #[test]
1489    fn test_from_conversions() {
1490        let st = UNIX_EPOCH + Duration::from_secs(1641006000);
1491        let ts_owned: Timestamp = st.into();
1492        let ts_ref: Timestamp = (&st).into();
1493        assert_eq!(ts_owned, ts_ref);
1494
1495        // From<&Timestamp> for Timestamp (the reflexive copy).
1496        let ts_copy: Timestamp = (&ts_owned).into();
1497        assert_eq!(ts_owned, ts_copy);
1498    }
1499
1500    /// Tests the `chrono::DateTime` → `Timestamp` → `chrono::DateTime`
1501    /// round-trip (covers the `Timestamp → DateTime<Utc>` impl).
1502    #[test]
1503    #[cfg(feature = "chrono")]
1504    fn test_chrono_roundtrip() {
1505        let ts: Timestamp = "2026-02-26T00:31:30.042Z".parse().unwrap();
1506        let dt: chrono::DateTime<chrono::Utc> = ts.into();
1507        let back: Timestamp = dt.into();
1508        assert_eq!(ts, back);
1509    }
1510
1511    /// Tests `Buffer::default` and `Clone`.
1512    #[test]
1513    fn test_buffer_default_and_clone() {
1514        let mut buf = Buffer::default();
1515        let ts: Timestamp = "2026-02-26T00:31:30.042Z".parse().unwrap();
1516        assert_eq!(buf.format(ts), "2026-02-26T00:31:30.042Z");
1517
1518        // `Clone` for `Buffer` deliberately returns a fresh empty buffer
1519        // rather than a true copy; verify that contract here. The lint
1520        // would have us write `buf` directly, but the whole point of the
1521        // test is to exercise the explicit `Clone` impl.
1522        #[allow(clippy::clone_on_copy)]
1523        let cloned = buf.clone();
1524        assert_eq!(cloned.len, 0);
1525    }
1526
1527    /// Tests the 3-digit nanosecond branch (covers the inner multiplication
1528    /// loop running its full 6 iterations).
1529    #[test]
1530    fn test_decode_nanos_3digit() {
1531        let s = ".042Z";
1532        let input = &mut s.as_bytes();
1533        assert_eq!(decode_nanos(input).unwrap(), 42_000_000);
1534        assert_eq!(input, b"Z");
1535    }
1536
1537    /// Pins the in-memory layout of `Buffer` so that future changes to
1538    /// `BUFFER_SIZE` or the `len` field type don't accidentally regress the
1539    /// "fits in one cacheline / four qwords" property.
1540    #[test]
1541    fn test_buffer_size() {
1542        assert_eq!(core::mem::size_of::<Buffer>(), 32);
1543    }
1544}