Skip to main content

ion_rs/types/
timestamp.rs

1use crate::decimal::Sign;
2use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
3use crate::result::{IonError, IonFailure, IonResult};
4use crate::types::{CountDecimalDigits, Decimal};
5use chrono::{
6    DateTime, Datelike, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, TimeZone, Timelike,
7};
8use std::cmp::Ordering;
9use std::fmt::{Debug, Display, Formatter};
10use std::hash::{Hash, Hasher};
11use std::marker::PhantomData;
12
13/// Indicates the most precise time unit that has been specified in the accompanying [Timestamp].
14#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Hash)]
15pub enum TimestampPrecision {
16    /// Year-level precision (e.g. `2020T`)
17    #[default]
18    Year,
19    /// Month-level precision (e.g. `2020-08T`)
20    Month,
21    /// Day-level precision (e.g. `2020-08-01T`)
22    Day,
23    /// Minute-level precision (e.g. `2020-08-01T12:34Z`)
24    HourAndMinute,
25    /// Second-level precision or greater. (e.g. `2020-08-01T12:34:56Z` or `2020-08-01T12:34:56.123456789Z`)
26    Second,
27}
28
29// [Default] cannot be derived for enum types. Providing a manual implementation of this type
30// allows us to derive Default for [Timestamp].
31
32/// Stores the precision of a Timestamp's fractional seconds, if present. This type is not
33/// self-contained; if the Timestamp has a precision that is less than or equal to nanoseconds
34/// (i.e. fewer than 10 digits), the fractional seconds value will be stored in the Timestamp's
35/// NaiveDateTime component and the Mantissa will indicate the number of digits from that value
36/// that should be used. If the precision is 10 or more digits, the Mantissa will store the value
37/// itself as a Decimal with the correct precision.
38#[derive(Debug, Clone, PartialEq, Eq)]
39pub enum Mantissa {
40    /// The number of digits of precision in the Timestamp's fractional seconds. For example, a
41    /// value of `3` would indicate millisecond precision. A value of `6` would indicate
42    /// microsecond precision. All precisions less than or equal to nanoseconds should use
43    /// this representation when possible.
44    Digits(u32),
45    /// Specifies the fractional seconds precisely as a `Decimal` in the range `>= 0` and `< 1`.
46    /// The Decimal will have the correct precision; the complete value can and should be used.
47    /// This representation should only be used for precisions greater than nanoseconds as it can
48    /// require allocations.
49    Arbitrary(Decimal),
50}
51
52impl Mantissa {
53    fn decimals_equal(d1: &Decimal, d2: &Decimal) -> bool {
54        // See the [EmptyMantissa] trait for details about `is_empty()`
55        (d1.is_empty() && d2.is_empty())
56            // Exact equality test
57            || d1.eq(d2)
58            // Coefficient zeros' signs don't have to match for fractional seconds.
59            || (d1.coefficient().is_zero() && d2.coefficient().is_zero() && d1.exponent == d2.exponent)
60    }
61
62    fn decimals_compare(d1: &Decimal, d2: &Decimal) -> Ordering {
63        // See the [EmptyMantissa] trait for details about `is_empty()`
64        if d1.is_empty() && d2.is_empty() {
65            Ordering::Equal
66        } else if d1.coefficient().is_zero() && d2.coefficient().is_zero() {
67            // Coefficient zeros' signs don't have to be compared for fractional seconds.
68            d1.exponent.cmp(&d2.exponent)
69        } else {
70            // Exact comparison test
71            d1.cmp(d2)
72        }
73    }
74}
75
76trait EmptyMantissa {
77    /// Returns true if the Mantissa's value is equivalent to not having specified a
78    /// sub-second precision at all. For example, `Mantissa::Digits(0)` or
79    /// `Mantissa::Arbitrary(Decimal::new(0, 0))`.
80    fn is_empty(&self) -> bool;
81}
82
83impl EmptyMantissa for Decimal {
84    fn is_empty(&self) -> bool {
85        self.coefficient().is_zero() && self.exponent == 0
86    }
87}
88
89impl EmptyMantissa for Mantissa {
90    fn is_empty(&self) -> bool {
91        match self {
92            // Look at zero digits of the DateTime's nanoseconds
93            Mantissa::Digits(0) => true,
94            // Or a Decimal with a coefficient of zero (any sign) and an exponent of zero.
95            Mantissa::Arbitrary(d) => d.is_empty(),
96            _ => false,
97        }
98    }
99}
100
101/// Returns the first `num_digits` digits of the specified `value`.
102// This is used in Timestamp's implementation of [PartialEq].
103fn first_n_digits_of(num_digits: u32, value: u32) -> u32 {
104    let total_digits = value.count_decimal_digits();
105    if total_digits <= num_digits {
106        return value;
107    }
108    // Truncate the trailing digits
109    value / 10u32.pow(total_digits - num_digits)
110}
111
112/// Constructs a [FixedOffset] at the specified offset seconds from UTC. If the specified offset
113/// is out of bounds, this method will panic.
114fn offset_east(seconds_east: i32) -> FixedOffset {
115    FixedOffset::east_opt(seconds_east)
116        // This error case is expected to be handled before this method is called.
117        .expect("seconds_east was outside the supported range")
118}
119
120/// Constructs a [`DateTime<FixedOffset>`] at the specified offset using the fields of
121/// [`NaiveDateTime`] representing the desired UTC datetime.
122fn datetime_at_offset(utc_datetime: &NaiveDateTime, seconds_east: i32) -> DateTime<FixedOffset> {
123    offset_east(seconds_east).from_utc_datetime(utc_datetime)
124}
125
126/// Represents a point in time to a specified degree of precision. Unlike `chrono`'s [NaiveDateTime]
127/// and [DateTime], a `Timestamp` has variable precision ranging from a year to fractional seconds
128/// of an arbitrary unit.
129#[derive(Debug, Clone)]
130pub struct Timestamp {
131    pub(crate) date_time: NaiveDateTime,
132    pub(crate) offset: Option<FixedOffset>,
133    pub(crate) precision: TimestampPrecision,
134    pub(crate) fractional_seconds: Option<Mantissa>,
135}
136
137impl Timestamp {
138    /// Converts a [`NaiveDateTime`] or [`DateTime<FixedOffset>`] to a Timestamp with the specified
139    /// precision. If the precision is [`TimestampPrecision::Second`], nanosecond precision (the maximum
140    /// supported by a [`Timelike`]) is assumed.
141    #[cfg(feature = "experimental-chrono")]
142    pub fn from_datetime<D>(datetime: D, precision: TimestampPrecision) -> Timestamp
143    where
144        D: Datelike + Timelike + Into<Timestamp>,
145    {
146        let mut timestamp: Timestamp = datetime.into();
147        if precision < TimestampPrecision::Second {
148            timestamp.fractional_seconds = None;
149        }
150        timestamp.precision = precision;
151        timestamp
152    }
153
154    pub(crate) fn from_naive_datetime(date_time: NaiveDateTime) -> Self {
155        Timestamp {
156            date_time,
157            offset: None,
158            precision: TimestampPrecision::Second,
159            fractional_seconds: Some(Mantissa::Digits(9)),
160        }
161    }
162
163    pub(crate) fn from_fixed_offset_datetime(
164        fixed_offset_date_time: DateTime<FixedOffset>,
165    ) -> Self {
166        let date_time = fixed_offset_date_time.naive_utc();
167        let offset = Some(*fixed_offset_date_time.offset());
168        Timestamp {
169            date_time,
170            offset,
171            precision: TimestampPrecision::Second,
172            fractional_seconds: Some(Mantissa::Digits(9)),
173        }
174    }
175
176    pub(crate) fn try_to_naive_datetime(&self) -> IonResult<NaiveDateTime> {
177        if self.offset.is_some() {
178            return IonResult::illegal_operation(
179                "cannot convert a Timestamp with a known offset into a NaiveDateTime",
180            );
181        }
182        Ok(downconvert_to_naive_datetime_with_nanoseconds(self))
183    }
184
185    pub(crate) fn try_to_datetime_fixed_offset(&self) -> IonResult<DateTime<FixedOffset>> {
186        if self.offset.is_none() {
187            return IonResult::illegal_operation(
188                "cannot convert a Timestamp with an unknown offset into a DateTime<FixedOffset>",
189            );
190        }
191        let date_time = downconvert_to_naive_datetime_with_nanoseconds(self);
192        Ok(self.offset.unwrap().from_utc_datetime(&date_time))
193    }
194
195    /// If the precision is [TimestampPrecision::Second], returns the Decimal scale of this Timestamp's
196    /// fractional seconds; otherwise, returns None.
197    ///
198    /// For example, a Timestamp with 553 milliseconds would return a Decimal scale of 3.
199    pub fn fractional_seconds_scale(&self) -> Option<i64> {
200        // This function is used when comparing two Timestamps with different Mantissa representations.
201        use Mantissa::*;
202        match self.fractional_seconds.as_ref() {
203            // number_of_digits represent number of digits of precision in the Timestamp's fractional seconds.
204            // this is equivalent to the decimal scale when we convert the fractional seconds into a decimal
205            // and return its scale
206            Some(Digits(number_of_digits)) => Some(*number_of_digits as i64),
207            // This timestamp already stores its fractional seconds as a Decimal; return the scale of this Decimal.
208            Some(Arbitrary(decimal)) => Some(decimal.scale()),
209            // This Timestamp's precision is too low to have a fractional seconds field.
210            None => None,
211        }
212    }
213
214    /// If the precision is [TimestampPrecision::Second], returns a Decimal representation of this Timestamp's
215    /// fractional seconds; otherwise, returns None.
216    ///
217    /// For example, a Timestamp with 553 milliseconds would return a Decimal with
218    /// coefficient 553, exponent -3.
219    pub(crate) fn fractional_seconds_as_decimal(&self) -> Option<Decimal> {
220        // This function is used when comparing two Timestamps with different Mantissa representations.
221        use Mantissa::*;
222        match self.fractional_seconds.as_ref() {
223            // This timestamp stores its fractional seconds in its `date_time` field.
224            // We'll need to convert the date_time's nanoseconds to a Decimal and return it.
225            Some(Digits(number_of_digits)) => {
226                const MAX_NANOSECOND_DIGITS: u32 = 9; // If it were 10, it'd be > a second
227                let nanoseconds = self.date_time.nanosecond();
228                let leading_zeros = MAX_NANOSECOND_DIGITS - nanoseconds.count_decimal_digits();
229                let coefficient = if leading_zeros >= *number_of_digits {
230                    0
231                } else {
232                    first_n_digits_of(*number_of_digits - leading_zeros, nanoseconds)
233                };
234                let exponent = -i64::from(*number_of_digits);
235                Some(Decimal::new(coefficient, exponent))
236            }
237            // This timestamp already stores its fractional seconds as a Decimal; return a clone.
238            Some(Arbitrary(decimal)) => Some(decimal.clone()),
239            // This Timestamp's precision is too low to have a fractional seconds field.
240            None => None,
241        }
242    }
243
244    /// If the precision is [TimestampPrecision::Second], returns a u32 representing
245    /// this Timestamp's fractional seconds in nanoseconds; otherwise, returns None.
246    ///
247    /// NOTE: This is a potentially lossy operation. A Timestamp with picoseconds would return a
248    /// number of nanoseconds, losing precision. Similarly, a Timestamp with milliseconds would
249    /// also return a number of nanoseconds, erroneously gaining precision.
250    fn fractional_seconds_as_nanoseconds(&self) -> Option<u32> {
251        // This function is used when converting a Timestamp to a DateTime<FixedOffset> or
252        // NaiveDateTime.
253        use Mantissa::*;
254        match self.fractional_seconds.as_ref() {
255            // This timestamp already stores its fractional seconds in its `date_time` field.
256            // We can ignore the `number_of_digits` (which tracks its precision) and simply return
257            // `self.date_time`'s nanoseconds.
258            Some(Digits(_number_of_digits)) => Some(self.date_time.nanosecond()),
259            // This timestamp stores its fractional seconds as a Decimal. Down-convert it to a u32
260            // representing the number of nanoseconds.
261            Some(Arbitrary(decimal)) => {
262                // nanoseconds = coefficient * 10^(exponent + 9)
263                let nano_exp = decimal.exponent + 9;
264                let mag = decimal.coefficient().magnitude();
265                let nanos: u128 = if nano_exp >= 0 {
266                    let factor = 10u128.saturating_pow(nano_exp as u32);
267                    mag.as_u128().unwrap_or(0).saturating_mul(factor)
268                } else {
269                    let divisor = 10u128.saturating_pow(nano_exp.unsigned_abs() as u32);
270                    let divided = mag.data / divisor;
271                    // Default value can only be reached if the fractional seconds are greater than 1.
272                    u128::try_from(divided).unwrap_or(0)
273                };
274                // The max number of nanos is 999,999,999 whereas u32::MAX is over 4 billion.
275                // This cast will only truncate if fractional_seconds decimal is >= 1, which is
276                // not allowed.
277                Some(nanos as u32)
278            }
279            // This Timestamp's precision is too low to have a fractional seconds field.
280            None => None,
281        }
282    }
283
284    /// Tests the fractional seconds fields of two timestamps for ordering. This function will
285    /// only be called if both Timestamps have a precision of [TimestampPrecision::Second].
286    fn fractional_seconds_compare(&self, other: &Timestamp) -> Ordering {
287        use Mantissa::*;
288        match (
289            self.fractional_seconds.as_ref(),
290            other.fractional_seconds.as_ref(),
291        ) {
292            (None, None) => Ordering::Equal,
293            (Some(_m), None) => {
294                let d1 = self.fractional_seconds_as_decimal().unwrap();
295                let d2 = Decimal::new(0u64, 0);
296                d1.cmp(&d2)
297            }
298            (None, Some(_m)) => {
299                let d1 = Decimal::new(0u64, 0);
300                let d2 = other.fractional_seconds_as_decimal().unwrap();
301                d1.cmp(&d2)
302            }
303            (Some(Digits(_d1)), Some(Digits(_d2))) => {
304                let d1 = self.date_time.nanosecond();
305                let d2 = other.date_time.nanosecond();
306                d1.cmp(&d2)
307            }
308            (Some(Arbitrary(d1)), Some(Arbitrary(d2))) => Mantissa::decimals_compare(d1, d2),
309            (Some(Digits(_d1)), Some(Arbitrary(d2))) => {
310                let d1 = &self.fractional_seconds_as_decimal().unwrap();
311                Mantissa::decimals_compare(d1, d2)
312            }
313            (Some(Arbitrary(d1)), Some(Digits(_d2))) => {
314                let d2 = &other.fractional_seconds_as_decimal().unwrap();
315                Mantissa::decimals_compare(d1, d2)
316            }
317        }
318    }
319
320    /// Tests the fractional seconds fields of two timestamps for equality. This function will
321    /// only be called if both Timestamps have a precision of [TimestampPrecision::Second].
322    fn fractional_seconds_equal(&self, other: &Timestamp) -> bool {
323        use Mantissa::*;
324
325        // TODO: make Timestamp::fractional_seconds to be Mantissa when creating a Timestamp to get rid of below conversion
326        // convert Option<&Mantissa> to &Mantissa
327        let m1 = match &self.fractional_seconds {
328            None => &Mantissa::Digits(0),
329            Some(m) => m,
330        };
331
332        let m2 = match &other.fractional_seconds {
333            None => &Mantissa::Digits(0),
334            Some(m) => m,
335        };
336
337        // compare fractional seconds
338        match (m1, m2) {
339            (Digits(d1), Digits(d2)) => {
340                if d1 != d2 {
341                    // Different precisions
342                    return false;
343                }
344                let d1 = first_n_digits_of(*d1, self.date_time.nanosecond());
345                let d2 = first_n_digits_of(*d2, other.date_time.nanosecond());
346                d1 == d2
347            }
348            (Arbitrary(d1), Arbitrary(d2)) => Mantissa::decimals_equal(d1, d2),
349            (Digits(_d1), Arbitrary(d2)) => {
350                let d1 = match self.fractional_seconds_as_decimal() {
351                    Some(decimal_value) => decimal_value,
352                    None => Decimal::new(0, 0),
353                };
354                Mantissa::decimals_equal(&d1, d2)
355            }
356            (Arbitrary(d1), Digits(_d2)) => {
357                let d2 = match other.fractional_seconds_as_decimal() {
358                    Some(decimal_value) => decimal_value,
359                    None => Decimal::new(0, 0),
360                };
361                Mantissa::decimals_equal(d1, &d2)
362            }
363        }
364    }
365
366    /// Writes the fractional seconds portion of a text timestamp, including a leading `.`.
367    fn format_fractional_seconds<W: std::fmt::Write>(&self, output: &mut W) -> IonResult<()> {
368        if self.fractional_seconds.is_none() {
369            // Nothing to do.
370            return Ok(());
371        }
372        let mantissa = self.fractional_seconds.as_ref().unwrap();
373        if mantissa.is_empty() {
374            // No need to write anything.
375            return Ok(());
376        }
377        match mantissa {
378            Mantissa::Digits(num_digits) => {
379                // Scale the nanoseconds down to the requested number of digits.
380                // Example: if `num_digits` is 3 (that is: millisecond precision), we need to
381                // divide the nanoseconds by 10^(9-3) to get the correct precision:
382                //      123,000,000 nanoseconds / 10^(9-3) = 123 milliseconds
383                let scaled = self.date_time.nanosecond() / 10u32.pow(9 - *num_digits);
384                // If our scaled number has fewer digits than the precision states, add leading
385                // zeros to the output to make up the difference.
386                // Example: `num_digits` is 6 (microsecond precision) but our number of microseconds
387                // is `9500` (only 4 digits), we need to add two leading zeros to make: `009500`.
388                let actual_num_digits = scaled.count_decimal_digits();
389                let num_leading_zeros = *num_digits - actual_num_digits;
390                write!(output, ".")?;
391                for _ in 0..num_leading_zeros {
392                    write!(output, "0")?;
393                }
394                write!(output, "{scaled}")?;
395                Ok(())
396            }
397            Mantissa::Arbitrary(decimal) => {
398                let exponent = decimal.exponent;
399                let coefficient = &decimal.coefficient();
400                if exponent >= 0 {
401                    // We know that the coefficient is non-zero (the mantissa was not empty),
402                    // so having a positive exponent would result in an illegal fractional
403                    // seconds value.
404                    return IonResult::encoding_error(
405                        "found fractional seconds decimal that was >= 1.",
406                    );
407                }
408
409                let num_digits = decimal.coefficient().number_of_decimal_digits();
410                let abs_exponent = decimal.exponent.unsigned_abs();
411                // At this point, we know that the abs_exponent is greater than num_digits because
412                // the decimal has to be < 1.
413                let num_leading_zeros = abs_exponent - num_digits as u64;
414                write!(output, ".")?;
415                for _ in 0..num_leading_zeros {
416                    write!(output, "0")?;
417                }
418                if coefficient.is_negative_zero() {
419                    write!(output, "0")?;
420                } else if coefficient.sign() == Sign::Negative {
421                    return IonResult::encoding_error(
422                        "fractional seconds cannot have a negative coefficient (other than -0)",
423                    );
424                } else {
425                    write!(output, "{}", decimal.coefficient())?;
426                }
427                Ok(())
428            }
429        }
430    }
431
432    pub(crate) fn format<W: std::fmt::Write>(&self, output: &mut W) -> IonResult<()> {
433        let (offset_minutes, datetime) = if let Some(minutes) = self.offset {
434            // Create a datetime with the appropriate offset that we can use for formatting.
435            let datetime: DateTime<FixedOffset> = self.try_to_datetime_fixed_offset()?;
436            // Convert the offset to minutes --v
437            (Some(minutes.local_minus_utc() / 60), datetime)
438        } else {
439            // Our timestamp has an unknown offset. Per the spec, this means it makes no
440            // assertions about *where* it was recorded, but its fields are still in UTC.
441            // Create a UTC datetime that we can use for formatting.
442            let datetime: NaiveDateTime = self.try_to_naive_datetime()?;
443            let datetime: DateTime<FixedOffset> = datetime_at_offset(&datetime, 0);
444            (None, datetime)
445        };
446
447        write!(output, "{:0>4}", datetime.year())?;
448        //                  ^-- 0-padded, right aligned, 4-digit year
449        if self.precision == TimestampPrecision::Year {
450            write!(output, "T")?;
451            return Ok(());
452        }
453
454        write!(output, "-{:0>2}", datetime.month())?;
455        //                   ^-- delimiting hyphen and 0-padded, right aligned, 2-digit month
456        if self.precision == TimestampPrecision::Month {
457            write!(output, "T")?;
458            return Ok(());
459        }
460
461        write!(output, "-{:0>2}", datetime.day())?;
462        //                   ^-- delimiting hyphen and 0-padded, right aligned, 2-digit day
463        if self.precision == TimestampPrecision::Day {
464            write!(output, "T")?;
465            return Ok(());
466        }
467
468        write!(
469            output,
470            "T{:0>2}:{:0>2}",
471            // ^-- delimiting T, formatted hour, delimiting colon, formatted minute
472            datetime.hour(),
473            datetime.minute()
474        )?;
475        if self.precision == TimestampPrecision::HourAndMinute {
476            self.format_offset(offset_minutes, output)?;
477            return Ok(());
478        }
479
480        write!(output, ":{:0>2}", datetime.second())?;
481        //                   ^-- delimiting colon, formatted second
482        self.format_fractional_seconds(output)?;
483        self.format_offset(offset_minutes, output)?;
484        Ok(())
485    }
486
487    fn format_offset<W: std::fmt::Write>(
488        &self,
489        offset_minutes: Option<i32>,
490        output: &mut W,
491    ) -> IonResult<()> {
492        let (sign, hours, minutes) = match offset_minutes {
493            None => ("-", 0, 0),
494            Some(offset_minutes) => {
495                const MINUTES_PER_HOUR: i32 = 60;
496                // Split the offset into a sign and magnitude for formatting
497                let sign = if offset_minutes >= 0 { "+" } else { "-" };
498                let offset_minutes = offset_minutes.abs();
499                let hours = offset_minutes / MINUTES_PER_HOUR;
500                let minutes = offset_minutes % MINUTES_PER_HOUR;
501
502                (sign, hours, minutes)
503            }
504        };
505        write!(output, "{sign}{hours:0>2}:{minutes:0>2}")?;
506        Ok(())
507    }
508
509    /// Creates a TimestampBuilder with the specified year and [TimestampPrecision::Year].
510    pub fn with_year(year: u32) -> TimestampBuilder<HasYear> {
511        TimestampBuilder::with_year(year)
512    }
513
514    /// Creates a TimestampBuilder with the specified year, month, and day. Its precision is
515    /// set to [TimestampPrecision::Day].
516    pub fn with_ymd(year: u32, month: u32, day: u32) -> TimestampBuilder<HasDay> {
517        TimestampBuilder::with_year(year)
518            .with_month(month)
519            .with_day(day)
520    }
521
522    /// Returns the offset in minutes that has been specified in the [Timestamp].
523    /// A positive value indicates Eastern Hemisphere, while a negative value indicates Western Hemisphere.
524    pub fn offset(&self) -> Option<i32> {
525        self.offset.map(|offset| offset.local_minus_utc() / 60)
526    }
527
528    /// Returns the precision that has been specified in the [Timestamp].
529    pub fn precision(&self) -> TimestampPrecision {
530        self.precision
531    }
532
533    /// Returns the year that has been specified in the [Timestamp].
534    pub fn year(&self) -> u32 {
535        // verify if the timestamp has an offset
536        if let Some(offset) = self.offset {
537            // `NaiveDateTime#hours()` returns hours normalized as per UTC
538            // for local time we need to +/- the difference
539            let local_date_time =
540                DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
541            return local_date_time.year() as u32;
542        }
543        self.date_time.year() as u32
544    }
545
546    /// Returns the month that has been specified in the [Timestamp].
547    /// Returns the month number starting from 1.
548    /// The return value ranges from 1 to 12.
549    pub fn month(&self) -> u32 {
550        // verify if the timestamp has an offset
551        if let Some(offset) = self.offset {
552            // `NaiveDateTime#hours()` returns hours normalized as per UTC
553            // for local time we need to +/- the difference
554            let local_date_time =
555                DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
556            return local_date_time.month();
557        }
558        self.date_time.month()
559    }
560
561    /// Returns the day that has been specified in the [Timestamp].
562    /// Returns the day of month starting from 1.
563    // The return value ranges from 1 to 31. (The last day of month differs by months.)
564    pub fn day(&self) -> u32 {
565        // verify if the timestamp has an offset
566        if let Some(offset) = self.offset {
567            // `NaiveDateTime#hours()` returns hours normalized as per UTC
568            // for local time we need to +/- the difference
569            let local_date_time =
570                DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
571            return local_date_time.day();
572        }
573        self.date_time.day()
574    }
575
576    /// Returns the hour(s) that has been specified in the [Timestamp].
577    /// Returns the hour number from 0 to 23.
578    pub fn hour(&self) -> u32 {
579        // verify if the timestamp has an offset
580        if let Some(offset) = self.offset {
581            // `NaiveDateTime#hours()` returns hours normalized as per UTC
582            // for local time we need to +/- the difference
583            let local_date_time =
584                DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
585            return local_date_time.hour();
586        }
587        self.date_time.hour()
588    }
589
590    /// Returns the minute(s) that has been specified in the [Timestamp].
591    /// Returns the minute number from 0 to 59.
592    pub fn minute(&self) -> u32 {
593        // verify if the timestamp has an offset
594        if let Some(offset) = self.offset {
595            // `NaiveDateTime#hours()` returns minutes normalized as per UTC
596            // for local time we need to +/- the difference
597            let local_date_time =
598                DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
599            return local_date_time.minute();
600        }
601        self.date_time.minute()
602    }
603
604    /// Returns the second(s) that has been specified in the [Timestamp].
605    /// Returns the second number from 0 to 59.
606    pub fn second(&self) -> u32 {
607        self.date_time.second()
608    }
609
610    /// Return a UTC timestamp for this [Timestamp]
611    pub fn to_utc(&self) -> Timestamp {
612        Self::from_naive_datetime(self.date_time)
613    }
614
615    /// Returns this Timestamp's fractional seconds in nanoseconds
616    ///
617    /// NOTE: This is a potentially lossy operation. A Timestamp with picoseconds would return a
618    /// number of nanoseconds, losing precision. If it loses precision then truncation is performed.
619    /// (e.g. a timestamp with fractional seconds of `0.000000000999` would return `0`)
620    pub fn nanoseconds(&self) -> u32 {
621        self.fractional_seconds_as_nanoseconds().unwrap_or_default()
622    }
623
624    /// Returns this Timestamp's fractional seconds in microseconds
625    ///
626    /// NOTE: This is a potentially lossy operation. A Timestamp with picoseconds would return a
627    /// number of microseconds, losing precision. If it loses precision then truncation is performed.
628    /// (e.g. a timestamp with fractional seconds of `0.000000999` would return `0`)
629    pub fn microseconds(&self) -> u32 {
630        self.fractional_seconds_as_nanoseconds()
631            .map(|s| s / 1_000)
632            .unwrap_or_default()
633    }
634
635    /// Returns this Timestamp's fractional seconds in milliseconds
636    ///
637    /// NOTE: This is a potentially lossy operation. A Timestamp with picoseconds would return a
638    /// number of milliseconds, losing precision. If it loses precision then truncation is performed.
639    /// (e.g. a timestamp with fractional seconds of `0.000999` would return `0`)
640    pub fn milliseconds(&self) -> u32 {
641        self.fractional_seconds_as_nanoseconds()
642            .map(|s| s / 1_000_000)
643            .unwrap_or_default()
644    }
645}
646
647/// Formats an ISO-8601 timestamp of appropriate precision and offset.
648impl Display for Timestamp {
649    fn fmt(&self, output: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
650        self.format(output).map_err(|_| std::fmt::Error)?;
651        Ok(())
652    }
653}
654
655impl PartialOrd for Timestamp {
656    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
657        Some(self.cmp(other))
658    }
659}
660
661impl Ord for Timestamp {
662    fn cmp(&self, other: &Self) -> Ordering {
663        let self_datetime = self.date_time.with_nanosecond(0).unwrap();
664        let other_datetime = other.date_time.with_nanosecond(0).unwrap();
665
666        let self_datetime = self
667            .offset
668            .map(|offset| offset.from_utc_datetime(&self_datetime))
669            .unwrap_or_else(|| datetime_at_offset(&self_datetime, 0));
670        let other_datetime = other
671            .offset
672            .map(|offset| offset.from_utc_datetime(&other_datetime))
673            .unwrap_or_else(|| datetime_at_offset(&other_datetime, 0));
674
675        let date_time_comparison = self_datetime.cmp(&other_datetime);
676
677        match date_time_comparison {
678            // if the datetime comparison is Ordering::Equal,
679            // then return fractional seconds comparison result
680            Ordering::Equal => self.fractional_seconds_compare(other),
681            // if datetime comparison is not equal,
682            // then no need to check for fractional seconds comparison
683            _ => date_time_comparison,
684        }
685    }
686}
687
688/// Two Timestamps are considered equal (though not necessarily IonEq) if they represent the same
689/// instant in time. TimestampPrecision is ignored. Offsets do not have to match as long as the instants
690/// being represented match. Examples:
691/// * `2022T` == `2022T-01`
692/// * `2022T` == `2022T-01-01T00:00:00.000+00:00`
693/// * `2022T-05-11T12:00:00.000Z` == `2022T-05-11T07:00:00.000-05:00`
694impl PartialEq for Timestamp {
695    fn eq(&self, other: &Self) -> bool {
696        // First, compare the two Timestamps' fractional seconds. We do this first because
697        // Timestamps with different Mantissa representations are a bit tricky to compare. Once
698        // we've established that the fractional seconds match, we can compare all of the other
699        // fields in the timestamp by comparing their respective `DateTime`s.
700        if !self.fractional_seconds_equal(other) {
701            return false;
702        }
703
704        // When a Timestamp is created, any fields beyond its precision are set to the lowest
705        // legal value for that field. So the Timestamp `2022-05T` (which has `Month` precision)
706        // would have a `day` field of `1` and hour, minute, and seconds fields of `0`. This makes
707        // it easy to compare Timestamps with different precisions.
708
709        // Make copies of their respective DateTime values but with the fractional seconds zeroed
710        // out. We're not modifying `self` or `other`, and we've already compared their fractional
711        // seconds so it's ok to ignore them from here on.
712        let self_datetime = self.date_time.with_nanosecond(0).unwrap();
713        let other_datetime = other.date_time.with_nanosecond(0).unwrap();
714
715        // Apply each Timestamp's offset to the DateTime. If there's no offset, set it to UTC.
716        let self_datetime = self
717            .offset
718            .map(|offset| offset.from_utc_datetime(&self_datetime))
719            .unwrap_or_else(|| datetime_at_offset(&self_datetime, 0));
720        let other_datetime = other
721            .offset
722            .map(|offset| offset.from_utc_datetime(&other_datetime))
723            .unwrap_or_else(|| datetime_at_offset(&other_datetime, 0));
724
725        // Compare the resulting `DateTime<FixedOffset>`s
726        self_datetime == other_datetime
727    }
728}
729
730impl Eq for Timestamp {}
731
732impl IonEq for Timestamp {
733    fn ion_eq(&self, other: &Self) -> bool {
734        if self.precision != other.precision {
735            return false;
736        }
737        // Timestamps are only considered Ion-equal if they have the same offset, including "unknown".
738        if self.offset != other.offset {
739            return false;
740        }
741        let self_dt = self.date_time;
742        let other_dt = other.date_time;
743        if self_dt.year() != other_dt.year() {
744            return false;
745        }
746        if self.precision >= TimestampPrecision::Month && self_dt.month() != other_dt.month() {
747            return false;
748        }
749        if self.precision >= TimestampPrecision::Day && self_dt.day() != other_dt.day() {
750            return false;
751        }
752        if self.precision >= TimestampPrecision::HourAndMinute
753            && (self_dt.hour() != other_dt.hour() || self_dt.minute() != other_dt.minute())
754        {
755            return false;
756        }
757        if self.precision <= TimestampPrecision::HourAndMinute {
758            return true;
759        }
760
761        if self_dt.second() != other_dt.second() || !self.fractional_seconds_equal(other) {
762            return false;
763        }
764
765        true
766    }
767}
768
769impl IonDataOrd for Timestamp {
770    fn ion_cmp(&self, other: &Self) -> Ordering {
771        // Compare by point in time
772        let ord = self.cmp(other);
773        if ord != Ordering::Equal {
774            return ord;
775        };
776
777        // And then by precision
778        let ord = self.precision.cmp(&other.precision);
779        if ord != Ordering::Equal {
780            return ord;
781        };
782        match [
783            self.fractional_seconds_scale(),
784            other.fractional_seconds_scale(),
785        ] {
786            [None, Some(b)] if b > 0 => return Ordering::Less,
787            [Some(a), None] if a > 0 => return Ordering::Greater,
788            [Some(a), Some(b)] => {
789                let ord = a.cmp(&b);
790                if ord != Ordering::Equal {
791                    return ord;
792                }
793            }
794            _ => {}
795        }
796
797        // And finally by offset (unknown, then least to greatest)
798        match [self.offset, other.offset] {
799            [None, Some(_)] => Ordering::Less,
800            [None, None] => Ordering::Equal,
801            [Some(_), None] => Ordering::Greater,
802            [Some(o1), Some(o2)] => o1.local_minus_utc().cmp(&o2.local_minus_utc()),
803        }
804    }
805}
806
807impl IonDataHash for Timestamp {
808    fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
809        self.precision.hash(state);
810        let self_dt = self.date_time;
811        self_dt.year().hash(state);
812        if self.precision >= TimestampPrecision::Month {
813            self_dt.month().hash(state)
814        }
815        if self.precision >= TimestampPrecision::Day {
816            self_dt.day().hash(state)
817        }
818        if self.precision >= TimestampPrecision::HourAndMinute {
819            self_dt.hour().hash(state);
820            self_dt.minute().hash(state);
821        }
822        if self.precision == TimestampPrecision::Second {
823            self_dt.second().hash(state);
824
825            let fractional_seconds_scale = self.fractional_seconds_scale();
826            match fractional_seconds_scale {
827                None | Some(0) => {}
828                Some(1..=9) => {
829                    fractional_seconds_scale.unwrap().hash(state);
830                    self.fractional_seconds_as_nanoseconds()
831                        .unwrap()
832                        .hash(state);
833                }
834                Some(_) => {
835                    self.fractional_seconds_as_decimal()
836                        .unwrap()
837                        .ion_data_hash(state);
838                }
839            }
840        }
841        self.offset.hash(state);
842    }
843}
844
845/// A Builder object for incrementally configuring and finally instantiating a [Timestamp].
846/// This builder uses the type-state pattern to expose only those methods which can result in a
847/// valid Timestamp. For example, it is not possible to set the `day` field without first setting
848/// the `year` and `month` fields.
849// See the unit tests for usage examples.
850#[derive(Debug, Clone)]
851pub struct TimestampBuilder<T> {
852    _state: PhantomData<T>,
853    fields_are_utc: bool,
854    precision: TimestampPrecision,
855    offset: Option<i32>,
856    // year..second are always set. Default is the implied value for the field if precision is less than that field.
857    year: u32,
858    month: u32,
859    day: u32,
860    hour: u32,
861    minute: u32,
862    second: u32,
863    fractional_seconds: Option<Mantissa>,
864    nanoseconds: Option<u32>,
865}
866
867impl<T> TimestampBuilder<T> {
868    fn change_state<U>(self) -> TimestampBuilder<U> {
869        // If we ever discover a performance difference, this entire function could be replaced with one line.
870        // unsafe { std::mem::transmute(self) }
871        TimestampBuilder {
872            _state: PhantomData,
873            fields_are_utc: self.fields_are_utc,
874            precision: self.precision,
875            offset: self.offset,
876            year: self.year,
877            month: self.month,
878            day: self.day,
879            hour: self.hour,
880            minute: self.minute,
881            second: self.second,
882            fractional_seconds: self.fractional_seconds,
883            nanoseconds: self.nanoseconds,
884        }
885    }
886
887    /// Sets all of the fields on the given [`NaiveDateTime`] or [`DateTime<FixedOffset>`] using the
888    /// values from the TimestampBuilder. Only those fields required by the TimestampBuilder's
889    /// configured [`TimestampPrecision`] will be set.
890    fn configure_datetime<D>(&mut self, mut datetime: D) -> IonResult<D>
891    where
892        D: Datelike + Timelike + Debug,
893    {
894        if self.year == 0 || self.year > 9999 {
895            return IonResult::illegal_operation(format!(
896                "Timestamp year '{}' out of range (1-9999)",
897                self.year
898            ));
899        }
900        datetime = datetime.with_year(self.year as i32).ok_or_else(|| {
901            IonError::illegal_operation(format!("specified year ('{}') is invalid", self.year))
902        })?;
903        if self.precision == TimestampPrecision::Year {
904            return Ok(datetime);
905        }
906
907        // If precision >= Month, the month must be set.
908        let month = self.month;
909        datetime = datetime.with_month(month).ok_or_else(|| {
910            IonError::illegal_operation(format!("specified month ('{month}') is invalid"))
911        })?;
912        if self.precision == TimestampPrecision::Month {
913            return Ok(datetime);
914        }
915
916        // If precision >= Day, the day must be set.
917        let day = self.day;
918        datetime = datetime.with_day(day).ok_or_else(|| {
919            IonError::illegal_operation(format!("specified day ('{day}') is invalid"))
920        })?;
921        if self.precision == TimestampPrecision::Day {
922            return Ok(datetime);
923        }
924
925        // If precision >= HourAndMinute, the hour and minute must be set.
926        let hour = self.hour;
927        datetime = datetime.with_hour(hour).ok_or_else(|| {
928            IonError::illegal_operation(format!("specified hour ('{hour}') is invalid"))
929        })?;
930        let minute = self.minute;
931        datetime = datetime.with_minute(minute).ok_or_else(|| {
932            IonError::illegal_operation(format!("specified minute ('{minute}') is invalid"))
933        })?;
934        if self.precision == TimestampPrecision::HourAndMinute {
935            return Ok(datetime);
936        }
937
938        // If precision >= Second, the second must be set...
939        let second = self.second;
940        datetime = datetime.with_second(second).ok_or_else(|| {
941            IonError::illegal_operation(format!("provided second ('{second}') is invalid."))
942        })?;
943
944        // ...along with the fractional seconds.
945        // If fractional seconds is Digit, self.nanoseconds will be Some(_).
946        // If it's Arbitrary, self.nanoseconds will be None and we should set the nanoseconds
947        // field to 0. The real value will be stored in the Timestamp alongside the DateTime
948        // as a Decimal.
949        datetime = datetime
950            .with_nanosecond(self.nanoseconds.unwrap_or(0))
951            .ok_or_else(|| {
952                IonError::illegal_operation(format!("provided nanosecond ('{second}') is invalid"))
953            })?;
954
955        Ok(datetime)
956    }
957
958    // A [NaiveDateTime] has no offset. This function attempts to apply the provided offset to the
959    // NaiveDateTime, producing a DateTime<FixedOffset>. If the offset is invalid or the combination
960    // of offset and datetime would produce an invalid Timestamp, this function will return Err.
961    fn apply_offset(
962        offset_minutes: i32,
963        fields_are_utc: bool,
964        datetime: NaiveDateTime,
965    ) -> IonResult<DateTime<FixedOffset>> {
966        // The chrono APIs express their DateTime offsets in seconds, but the Ion APIs use minutes.
967        const SECONDS_PER_MINUTE: i32 = 60;
968        let offset_seconds = offset_minutes * SECONDS_PER_MINUTE;
969        let offset = FixedOffset::east_opt(offset_seconds).ok_or_else(|| {
970            IonError::illegal_operation(format!(
971                "specified offset ({offset_minutes} minutes) is invalid"
972            ))
973        })?;
974
975        // If the fields of the datetime are UTC, constructing a DateTime<FixedOffset> is guaranteed
976        // to succeed. Return it directly.
977        if fields_are_utc {
978            return Ok(offset.from_utc_datetime(&datetime));
979        }
980
981        // Otherwise, apply the offset to our (local) NaiveDateTime and make sure the resulting
982        // DateTime<FixedOffset> is valid.
983        match offset.from_local_datetime(&datetime) {
984            LocalResult::None => {
985                IonResult::illegal_operation(
986                    format!(
987                        "specified offset/datetime pair is invalid (offset={offset_minutes}, datetime={datetime})"
988                    )
989                )
990            },
991            LocalResult::Single(datetime) => Ok(datetime),
992            LocalResult::Ambiguous(_min, _max) => {
993                IonResult::illegal_operation(
994                    format!(
995                        "specified offset/datetime pair produces an ambiguous timestamp (offset={offset_minutes}, datetime={datetime})"
996                    )
997                )
998            }
999        }
1000    }
1001
1002    /// Attempt to construct a [Timestamp] using the values configured on the [TimestampBuilder].
1003    /// If any of the individual fields are invalid (for example, a `month` value that is greater
1004    /// than `12`) or if the resulting timestamp would represent a non-existent point in time
1005    /// (like those bypassed by daylight saving time), this method will return an `Err(IonError)`.
1006    pub fn build(mut self) -> IonResult<Timestamp> {
1007        // Start with a clean slate NaiveDateTime that we can configure. (These are cheap to copy.)
1008        let mut datetime: NaiveDateTime = NaiveDate::from_ymd_opt(0, 1, 1)
1009            .unwrap()
1010            .and_hms_nano_opt(0, 0, 0, 0)
1011            .unwrap();
1012        // Set all of the time fields on the datetime using the data from our TimestampBuilder
1013        datetime = self.configure_datetime(datetime)?;
1014        // If the timestamp we're building has a known offset...
1015        let mut timestamp: Timestamp = if let Some(offset_minutes) = self.offset {
1016            // ...apply the offset to our NaiveDateTime, producing a DateTime<FixedOffset>
1017            let datetime_with_offset: DateTime<FixedOffset> =
1018                Self::apply_offset(offset_minutes, self.fields_are_utc, datetime)?;
1019            // ...and convert the DateTime<FixedOffset> into a full Timestamp.
1020            Timestamp::from_fixed_offset_datetime(datetime_with_offset)
1021        } else {
1022            // Otherwise, there's not a known offset. We can directly convert our NaiveDateTime
1023            // into a full Timestamp.
1024            Timestamp::from_naive_datetime(datetime)
1025        };
1026        if self.precision < TimestampPrecision::Second {
1027            timestamp.fractional_seconds = None;
1028        }
1029        timestamp.precision = self.precision;
1030
1031        // Copy the fractional seconds from the builder to the Timestamp.
1032        if self.precision == TimestampPrecision::Second {
1033            timestamp.fractional_seconds = self.fractional_seconds;
1034            if let Some(Mantissa::Arbitrary(ref decimal)) = &timestamp.fractional_seconds {
1035                if decimal.is_less_than_zero() {
1036                    return IonResult::illegal_operation(
1037                        "cannot create a timestamp with negative fractional seconds",
1038                    );
1039                }
1040                if decimal.is_greater_than_or_equal_to_one() {
1041                    return IonResult::illegal_operation(
1042                        "cannot create a timestamp with a fractional seconds >= 1.0",
1043                    );
1044                }
1045                if decimal.is_zero() && decimal.exponent >= 0 {
1046                    timestamp.fractional_seconds = None;
1047                }
1048            }
1049        }
1050        Ok(timestamp)
1051    }
1052
1053    /// Like [Self::build], but the fields provided for each time unit are understood
1054    /// to be in UTC rather than in the local time of the specified offset (if there is one).
1055    pub(crate) fn build_utc_fields_at_offset(
1056        mut self,
1057        offset_minutes: i32,
1058    ) -> IonResult<Timestamp> {
1059        self.fields_are_utc = true;
1060        self.offset = Some(offset_minutes);
1061        self.build()
1062    }
1063}
1064
1065// The type states (HasYear, HasMonth, etc.) are pub in this module, but they do not appear as types
1066// in the documentation, they cannot be imported, and they are not nameable from outside this crate.
1067#[derive(Debug, Clone)]
1068pub struct HasYear;
1069impl TimestampBuilder<HasYear> {
1070    pub fn with_year(year: u32) -> Self {
1071        TimestampBuilder {
1072            _state: Default::default(),
1073            fields_are_utc: false,
1074            precision: TimestampPrecision::Year,
1075            offset: None,
1076            year,
1077            month: 1,
1078            day: 1,
1079            hour: 0,
1080            minute: 0,
1081            second: 0,
1082            fractional_seconds: None,
1083            nanoseconds: None,
1084        }
1085    }
1086
1087    pub fn with_ymd(year: u32, month: u32, day: u32) -> TimestampBuilder<HasDay> {
1088        Self::with_year(year).with_month(month).with_day(day)
1089    }
1090
1091    // Libraries have conflicting opinions about whether months should be
1092    // 0- or 1-indexed, so Timestamp follows chrono's lead and provides
1093    // convenient ways to do both. Internally, it uses a 1-based representation.
1094
1095    // 0-indexed month
1096    pub fn with_month0(self, month: u32) -> TimestampBuilder<HasMonth> {
1097        self.with_month(month + 1)
1098    }
1099
1100    // 1-indexed month
1101    pub fn with_month(mut self, month: u32) -> TimestampBuilder<HasMonth> {
1102        self.precision = TimestampPrecision::Month;
1103        self.month = month;
1104        self.change_state()
1105    }
1106}
1107
1108#[derive(Debug, Clone)]
1109pub struct HasMonth;
1110impl TimestampBuilder<HasMonth> {
1111    // Libraries have conflicting opinions about whether days should be
1112    // 0- or 1-indexed, so Timestamp follows chrono's lead and provides
1113    // convenient ways to do both. Internally, it uses a 1-based representation.
1114
1115    // 0-indexed day
1116    pub fn with_day0(self, day: u32) -> TimestampBuilder<HasDay> {
1117        self.with_day(day + 1)
1118    }
1119
1120    // 1-indexed day
1121    pub fn with_day(mut self, day: u32) -> TimestampBuilder<HasDay> {
1122        self.precision = TimestampPrecision::Day;
1123        self.day = day;
1124        self.change_state()
1125    }
1126}
1127
1128#[derive(Debug, Clone)]
1129pub struct HasDay;
1130impl TimestampBuilder<HasDay> {
1131    pub fn with_hms(self, hour: u32, minute: u32, second: u32) -> TimestampBuilder<HasSeconds> {
1132        self.with_hour(hour).with_minute(minute).with_second(second)
1133    }
1134
1135    pub fn with_hour_and_minute(mut self, hour: u32, minute: u32) -> TimestampBuilder<HasMinute> {
1136        self.precision = TimestampPrecision::HourAndMinute;
1137        self.hour = hour;
1138        self.minute = minute;
1139        self.change_state()
1140    }
1141
1142    pub fn with_hour(mut self, hour: u32) -> TimestampBuilder<HasHour> {
1143        self.precision = TimestampPrecision::HourAndMinute;
1144        self.hour = hour;
1145        self.change_state()
1146    }
1147}
1148
1149macro_rules! with_offset {
1150    () => {
1151        /// Sets the difference, in minutes, from UTC. A positive value indicates
1152        /// Eastern Hemisphere, while a negative value indicates Western Hemisphere.
1153        // The unit (minutes) could be seconds (which is what the chrono crate uses
1154        // internally), but Ion uses minutes in its binary representation, so it
1155        // makes sense to be consistent.
1156        pub fn with_offset(mut self, offset_minutes: i32) -> TimestampBuilder<HasOffset> {
1157            self.offset = Some(offset_minutes);
1158            self.change_state()
1159        }
1160    };
1161}
1162
1163#[derive(Debug, Clone)]
1164pub struct HasHour;
1165impl TimestampBuilder<HasHour> {
1166    pub fn with_minute(mut self, minute: u32) -> TimestampBuilder<HasMinute> {
1167        self.precision = TimestampPrecision::HourAndMinute;
1168        self.minute = minute;
1169        self.change_state()
1170    }
1171
1172    with_offset!();
1173}
1174
1175#[derive(Debug, Clone)]
1176pub struct HasMinute;
1177impl TimestampBuilder<HasMinute> {
1178    pub fn with_second(mut self, second: u32) -> TimestampBuilder<HasSeconds> {
1179        self.precision = TimestampPrecision::Second;
1180        self.second = second;
1181        self.change_state()
1182    }
1183
1184    with_offset!();
1185}
1186
1187#[derive(Debug, Clone)]
1188pub struct HasSeconds;
1189impl TimestampBuilder<HasSeconds> {
1190    // Note that in order to create a `FractionalSecondSetter`, the user will have had to first
1191    // create a `SecondSetter`. Because of this, the builder's precision is already set to
1192    // `TimestampPrecision::Second`.
1193    pub fn with_nanoseconds(mut self, nanosecond: u32) -> TimestampBuilder<HasFractionalSeconds> {
1194        self.fractional_seconds = Some(Mantissa::Digits(9));
1195        self.nanoseconds = Some(nanosecond);
1196        self.change_state()
1197    }
1198
1199    pub fn with_microseconds(mut self, microsecond: u32) -> TimestampBuilder<HasFractionalSeconds> {
1200        self.fractional_seconds = Some(Mantissa::Digits(6));
1201        self.nanoseconds = Some(microsecond * 1000);
1202        self.change_state()
1203    }
1204
1205    pub fn with_milliseconds(mut self, millisecond: u32) -> TimestampBuilder<HasFractionalSeconds> {
1206        self.fractional_seconds = Some(Mantissa::Digits(3));
1207        self.nanoseconds = Some(millisecond * 1_000_000);
1208        self.change_state()
1209    }
1210
1211    pub fn with_nanoseconds_and_precision(
1212        mut self,
1213        nanoseconds: u32,
1214        precision_digits: u32,
1215    ) -> TimestampBuilder<HasFractionalSeconds> {
1216        self.fractional_seconds = Some(Mantissa::Digits(precision_digits));
1217        self.nanoseconds = Some(nanoseconds);
1218        self.change_state()
1219    }
1220
1221    pub fn with_fractional_seconds(
1222        mut self,
1223        fractional_seconds: Decimal,
1224    ) -> TimestampBuilder<HasFractionalSeconds> {
1225        self.fractional_seconds = Some(Mantissa::Arbitrary(fractional_seconds));
1226        self.nanoseconds = None;
1227        self.change_state()
1228    }
1229
1230    with_offset!();
1231}
1232
1233#[derive(Debug, Clone)]
1234pub struct HasFractionalSeconds;
1235impl TimestampBuilder<HasFractionalSeconds> {
1236    with_offset!();
1237}
1238
1239#[derive(Debug, Clone)]
1240pub struct HasOffset;
1241// No impl for TimestampBuilder<HasOffset> because `build()` is included in TimestampBuilder<T>
1242
1243fn downconvert_to_naive_datetime_with_nanoseconds(timestamp: &Timestamp) -> NaiveDateTime {
1244    if timestamp.precision == TimestampPrecision::Second {
1245        // DateTime always uses nanosecond precision. If our Timestamp uses a Decimal for
1246        // its fractional seconds, attempt to convert it to a number of nanoseconds.
1247        // This operation may add or lose precision, but is necessary to conform with
1248        // chrono's expectations.
1249        let nanoseconds = timestamp.fractional_seconds_as_nanoseconds().unwrap_or(0);
1250        // Copy `self.date_time` and set the copy's nanoseconds to this new value.
1251        // Modifying the nanoseconds should never be invalid.
1252        timestamp.date_time.with_nanosecond(nanoseconds).unwrap()
1253    } else {
1254        // NaiveDateTime implements `Copy`
1255        timestamp.date_time
1256    }
1257}
1258
1259#[cfg(feature = "experimental-chrono")]
1260impl TryInto<NaiveDateTime> for Timestamp {
1261    type Error = IonError;
1262
1263    fn try_into(self) -> Result<NaiveDateTime, Self::Error> {
1264        self.try_to_naive_datetime()
1265    }
1266}
1267
1268#[cfg(feature = "experimental-chrono")]
1269impl TryInto<DateTime<FixedOffset>> for Timestamp {
1270    type Error = IonError;
1271
1272    fn try_into(self) -> Result<DateTime<FixedOffset>, Self::Error> {
1273        self.try_to_datetime_fixed_offset()
1274    }
1275}
1276
1277#[cfg(feature = "experimental-chrono")]
1278impl From<NaiveDateTime> for Timestamp {
1279    fn from(date_time: NaiveDateTime) -> Self {
1280        Self::from_naive_datetime(date_time)
1281    }
1282}
1283
1284#[cfg(feature = "experimental-chrono")]
1285impl From<DateTime<FixedOffset>> for Timestamp {
1286    fn from(fixed_offset_date_time: DateTime<FixedOffset>) -> Self {
1287        Self::from_fixed_offset_datetime(fixed_offset_date_time)
1288    }
1289}
1290
1291#[cfg(test)]
1292mod timestamp_tests {
1293    use super::*;
1294    use crate::ion_data::IonEq;
1295    use crate::result::IonResult;
1296    use crate::types::Mantissa;
1297    use crate::{Decimal, Int, Timestamp, TimestampPrecision};
1298    use chrono::NaiveDateTime;
1299    use rstest::*;
1300    use std::cmp::Ordering;
1301    use std::io::Write;
1302    use std::ops::Mul;
1303    use std::str::FromStr;
1304
1305    #[test]
1306    fn test_timestamps_with_same_ymd_hms_millis_at_known_offset_are_equal() -> IonResult<()> {
1307        let builder = TimestampBuilder::with_ymd(2021, 2, 5)
1308            .with_hms(16, 43, 51)
1309            .with_milliseconds(192);
1310        let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
1311        let timestamp2 = builder.with_offset(5 * 60).build()?;
1312        assert_eq!(timestamp1, timestamp2);
1313        assert!(timestamp1.ion_eq(&timestamp2));
1314        Ok(())
1315    }
1316
1317    #[test]
1318    fn test_timestamps_with_same_ymd_hms_millis_at_known_offset_are_equal_ordering() -> IonResult<()>
1319    {
1320        let builder = TimestampBuilder::with_ymd(2021, 2, 5)
1321            .with_hms(16, 43, 51)
1322            .with_milliseconds(192);
1323        let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
1324        let timestamp2 = builder.with_offset(5 * 60).build()?;
1325        assert!(timestamp1 == timestamp2);
1326        Ok(())
1327    }
1328
1329    #[test]
1330    fn test_timestamps_with_same_ymd_hms_millis_at_unknown_offset_are_equal() -> IonResult<()> {
1331        let builder = TimestampBuilder::with_ymd(2021, 2, 5)
1332            .with_hms(16, 43, 51)
1333            .with_milliseconds(192);
1334        let timestamp1 = builder.clone().build()?;
1335        let timestamp2 = builder.build()?;
1336        assert_eq!(timestamp1, timestamp2);
1337        assert!(timestamp1.ion_eq(&timestamp2));
1338        Ok(())
1339    }
1340
1341    #[test]
1342    fn test_timestamps_with_same_ymd_hms_at_known_offset_are_equal() -> IonResult<()> {
1343        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
1344        let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
1345        let timestamp2 = builder.with_offset(5 * 60).build()?;
1346        assert_eq!(timestamp1, timestamp2);
1347        assert!(timestamp1.ion_eq(&timestamp2));
1348        Ok(())
1349    }
1350
1351    #[test]
1352    fn test_timestamps_from_utc_and_local_hm_fields_at_same_offset_are_equal() -> IonResult<()> {
1353        // Builder 1 specifies its time fields in the local time of the specified offset
1354        let builder1 = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(11, 43);
1355        let timestamp1 = builder1.with_offset(-5 * 60).build()?;
1356        // Builder 2 specifies its time fields in UTC and expects the offset to be applied afterwards
1357        let builder2 = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
1358        let timestamp2 = builder2.build_utc_fields_at_offset(-5 * 60)?;
1359        assert_eq!(timestamp1, timestamp2);
1360        assert!(timestamp1.ion_eq(&timestamp2));
1361        Ok(())
1362    }
1363
1364    #[test]
1365    fn test_timestamps_from_utc_and_local_hms_fields_at_same_offset_are_equal() -> IonResult<()> {
1366        // Builder 1 specifies its time fields in the local time of the specified offset
1367        let builder1 = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(11, 43, 51);
1368        let timestamp1 = builder1.with_offset(-5 * 60).build()?;
1369        // Builder 2 specifies its time fields in UTC and expects the offset to be applied afterwards
1370        let builder2 = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
1371        let timestamp2 = builder2.build_utc_fields_at_offset(-5 * 60)?;
1372        assert_eq!(timestamp1, timestamp2);
1373        assert!(timestamp1.ion_eq(&timestamp2));
1374        Ok(())
1375    }
1376
1377    #[test]
1378    fn test_timestamps_with_same_ymd_hms_at_unknown_offset_are_equal() -> IonResult<()> {
1379        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
1380        let timestamp1 = builder.clone().build()?;
1381        let timestamp2 = builder.build()?;
1382        assert_eq!(timestamp1, timestamp2);
1383        assert!(timestamp1.ion_eq(&timestamp2));
1384        Ok(())
1385    }
1386
1387    #[test]
1388    fn test_timestamps_with_same_ymd_hm_at_known_offset_are_equal() -> IonResult<()> {
1389        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
1390        let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
1391        let timestamp2 = builder.with_offset(5 * 60).build()?;
1392        assert_eq!(timestamp1, timestamp2);
1393        assert!(timestamp1.ion_eq(&timestamp2));
1394        Ok(())
1395    }
1396
1397    #[test]
1398    fn test_timestamps_with_same_ymd_hm_at_unknown_offset_are_equal() -> IonResult<()> {
1399        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
1400        let timestamp1 = builder.clone().build()?;
1401        let timestamp2 = builder.build()?;
1402        assert_eq!(timestamp1, timestamp2);
1403        assert!(timestamp1.ion_eq(&timestamp2));
1404        Ok(())
1405    }
1406
1407    #[test]
1408    fn test_timestamps_with_same_ymd_at_unknown_offset_are_equal() -> IonResult<()> {
1409        let builder = TimestampBuilder::with_ymd(2021, 2, 5);
1410        let timestamp1 = builder.clone().build()?;
1411        let timestamp2 = builder.build()?;
1412        assert_eq!(timestamp1, timestamp2);
1413        assert!(timestamp1.ion_eq(&timestamp2));
1414        Ok(())
1415    }
1416
1417    #[test]
1418    fn test_timestamps_with_same_ym_at_unknown_offset_are_equal() -> IonResult<()> {
1419        let builder = TimestampBuilder::with_year(2021).with_month(2);
1420        let timestamp1 = builder.clone().build()?;
1421        let timestamp2 = builder.build()?;
1422        assert_eq!(timestamp1, timestamp2);
1423        assert!(timestamp1.ion_eq(&timestamp2));
1424        Ok(())
1425    }
1426
1427    #[test]
1428    fn test_timestamps_with_same_year_at_unknown_offset_are_equal() -> IonResult<()> {
1429        let builder = TimestampBuilder::with_year(2021);
1430        let timestamp1 = builder.clone().build()?;
1431        let timestamp2 = builder.build()?;
1432        assert_eq!(timestamp1, timestamp2);
1433        assert!(timestamp1.ion_eq(&timestamp2));
1434        Ok(())
1435    }
1436
1437    #[test]
1438    fn test_timestamps_at_different_offsets_are_not_equal() -> IonResult<()> {
1439        let builder = TimestampBuilder::with_ymd(2021, 2, 5)
1440            .with_hms(16, 43, 51)
1441            .with_milliseconds(192);
1442        let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
1443        let timestamp2 = builder.with_offset(4 * 60).build()?;
1444        assert_ne!(timestamp1, timestamp2);
1445        assert!(!timestamp1.ion_eq(&timestamp2));
1446        Ok(())
1447    }
1448
1449    #[test]
1450    fn test_timestamps_with_known_and_unknown_offsets_are_not_equal() -> IonResult<()> {
1451        let builder = TimestampBuilder::with_ymd(2021, 2, 5)
1452            .with_hms(16, 43, 51)
1453            .with_milliseconds(192);
1454        let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
1455        let timestamp2 = builder.build()?;
1456        assert_ne!(timestamp1, timestamp2);
1457        assert!(!timestamp1.ion_eq(&timestamp2));
1458        Ok(())
1459    }
1460
1461    #[test]
1462    fn test_timestamps_with_different_precisions_are_not_equal() -> IonResult<()> {
1463        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
1464        let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
1465        let timestamp2 = builder.with_milliseconds(192).with_offset(5 * 60).build()?;
1466        assert_ne!(timestamp1, timestamp2);
1467        assert!(!timestamp1.ion_eq(&timestamp2));
1468        Ok(())
1469    }
1470
1471    #[test]
1472    fn test_timestamps_with_different_fractional_second_precision_are_not_equal() -> IonResult<()> {
1473        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
1474        let timestamp1 = builder
1475            .clone()
1476            .with_milliseconds(192)
1477            .with_offset(5 * 60)
1478            .build()?;
1479        // The microseconds field has the same amount of time, but a different precision.
1480        let timestamp2 = builder
1481            .with_microseconds(193 * 1_000)
1482            .with_offset(5 * 60)
1483            .build()?;
1484        assert_ne!(timestamp1, timestamp2);
1485        assert!(!timestamp1.ion_eq(&timestamp2));
1486        Ok(())
1487    }
1488
1489    #[test]
1490    fn test_timestamps_with_different_fractional_seconds_are_not_equal() -> IonResult<()> {
1491        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
1492        let timestamp1 = builder
1493            .clone()
1494            .with_milliseconds(192)
1495            .with_offset(5 * 60)
1496            .build()?;
1497        let timestamp2 = builder.with_milliseconds(193).with_offset(5 * 60).build()?;
1498        assert_ne!(timestamp1, timestamp2);
1499        assert!(!timestamp1.ion_eq(&timestamp2));
1500        Ok(())
1501    }
1502
1503    #[test]
1504    fn test_timestamps_with_different_seconds_are_not_equal() -> IonResult<()> {
1505        let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
1506        let timestamp1 = builder
1507            .clone()
1508            .with_second(12)
1509            .with_offset(5 * 60)
1510            .build()?;
1511        let timestamp2 = builder.with_second(13).with_offset(5 * 60).build()?;
1512        assert_ne!(timestamp1, timestamp2);
1513        assert!(!timestamp1.ion_eq(&timestamp2));
1514        Ok(())
1515    }
1516
1517    #[test]
1518    fn test_timestamps_with_different_minutes_are_not_equal() -> IonResult<()> {
1519        let builder = TimestampBuilder::with_ymd(2021, 2, 5);
1520        let timestamp1 = builder
1521            .clone()
1522            .with_hour_and_minute(16, 42)
1523            .with_offset(5 * 60)
1524            .build()?;
1525        let timestamp2 = builder
1526            .with_hour_and_minute(16, 43)
1527            .with_offset(5 * 60)
1528            .build()?;
1529        assert_ne!(timestamp1, timestamp2);
1530        assert!(!timestamp1.ion_eq(&timestamp2));
1531        Ok(())
1532    }
1533
1534    #[test]
1535    fn test_timestamps_with_different_hours_are_not_equal() -> IonResult<()> {
1536        let builder = TimestampBuilder::with_ymd(2021, 2, 5);
1537        let timestamp1 = builder
1538            .clone()
1539            .with_hour_and_minute(16, 42)
1540            .with_offset(5 * 60)
1541            .build()?;
1542        let timestamp2 = builder
1543            .with_hour_and_minute(17, 42)
1544            .with_offset(5 * 60)
1545            .build()?;
1546        assert_ne!(timestamp1, timestamp2);
1547        assert!(!timestamp1.ion_eq(&timestamp2));
1548        Ok(())
1549    }
1550
1551    #[test]
1552    fn test_timestamps_with_different_days_are_not_equal() -> IonResult<()> {
1553        let builder = TimestampBuilder::with_year(2021).with_month(2);
1554        let timestamp1 = builder.clone().with_day(5).build()?;
1555        let timestamp2 = builder.with_day(6).build()?;
1556        assert_ne!(timestamp1, timestamp2);
1557        assert!(!timestamp1.ion_eq(&timestamp2));
1558        Ok(())
1559    }
1560
1561    #[test]
1562    fn test_timestamps_with_different_months_are_not_equal() -> IonResult<()> {
1563        let builder = TimestampBuilder::with_year(2021);
1564        let timestamp1 = builder.clone().with_month(2).build()?;
1565        let timestamp2 = builder.with_month(3).build()?;
1566        assert_ne!(timestamp1, timestamp2);
1567        assert!(!timestamp1.ion_eq(&timestamp2));
1568        Ok(())
1569    }
1570
1571    #[test]
1572    fn test_timestamps_with_different_years_are_not_equal() -> IonResult<()> {
1573        let timestamp1 = TimestampBuilder::with_year(2021).build()?;
1574        let timestamp2 = TimestampBuilder::with_year(2022).build()?;
1575        assert_ne!(timestamp1, timestamp2);
1576        assert!(!timestamp1.ion_eq(&timestamp2));
1577        Ok(())
1578    }
1579
1580    #[cfg(feature = "experimental-chrono")]
1581    #[test]
1582    fn test_timestamp_try_into_naive_datetime() -> IonResult<()> {
1583        let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
1584            .with_hms(10, 15, 0)
1585            .build()?;
1586        let naive_datetime: NaiveDateTime = timestamp.try_into()?;
1587        let expected = NaiveDate::from_ymd_opt(2021, 4, 6)
1588            .unwrap()
1589            .and_hms_opt(10, 15, 0)
1590            .unwrap();
1591        assert_eq!(expected, naive_datetime);
1592        Ok(())
1593    }
1594
1595    #[cfg(feature = "experimental-chrono")]
1596    #[test]
1597    fn test_timestamp_try_into_naive_datetime_fractional_seconds() -> IonResult<()> {
1598        let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
1599            .with_hms(10, 15, 0)
1600            .with_milliseconds(449)
1601            .build()?;
1602        let datetime: NaiveDateTime = timestamp.try_into()?;
1603        let naive_datetime = NaiveDate::from_ymd_opt(2021, 4, 6)
1604            .unwrap()
1605            .and_hms_opt(10, 15, 0)
1606            .unwrap()
1607            .with_nanosecond(449000000)
1608            .unwrap();
1609        assert_eq!(datetime, naive_datetime);
1610        Ok(())
1611    }
1612
1613    #[cfg(feature = "experimental-chrono")]
1614    #[test]
1615    fn test_timestamp_try_into_naive_datetime_error() -> IonResult<()> {
1616        let timestamp = TimestampBuilder::with_ymd(2021, 1, 1)
1617            .with_hms(0, 0, 0)
1618            .with_offset(0)
1619            .build()?;
1620        //     ^---- This timestamp has a known offset, so we cannot convert it into a NaiveDateTime
1621        let result: IonResult<NaiveDateTime> = timestamp.try_into();
1622        assert!(result.is_err());
1623        Ok(())
1624    }
1625
1626    #[cfg(feature = "experimental-chrono")]
1627    #[test]
1628    fn test_timestamp_try_into_fixed_offset_datetime() -> IonResult<()> {
1629        let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
1630            .with_hms(10, 15, 0)
1631            .with_offset(-5 * 60)
1632            .build()?;
1633        //                    ^-- Timestamp's offset API takes minutes
1634        let datetime: DateTime<FixedOffset> = timestamp.try_into()?;
1635        // chrono's FixedOffset takes seconds ----------v
1636        let expected_offset = offset_east(-5 * 60 * 60);
1637        let naive_datetime = NaiveDate::from_ymd_opt(2021, 4, 6)
1638            .unwrap()
1639            .and_hms_opt(10, 15, 0)
1640            .unwrap();
1641        let expected_datetime = expected_offset
1642            .from_local_datetime(&naive_datetime)
1643            .unwrap();
1644        assert_eq!(datetime, expected_datetime);
1645        Ok(())
1646    }
1647
1648    #[cfg(feature = "experimental-chrono")]
1649    #[test]
1650    fn test_timestamp_try_into_fixed_offset_datetime_fractional_seconds() -> IonResult<()> {
1651        let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
1652            .with_hms(10, 15, 0)
1653            .with_milliseconds(449)
1654            .with_offset(-5 * 60)
1655            .build()?;
1656        //                    ^-- Timestamp's offset API takes minutes
1657        let datetime: DateTime<FixedOffset> = timestamp.try_into()?;
1658        // chrono's FixedOffset takes seconds ----------v
1659        let expected_offset = offset_east(-5 * 60 * 60);
1660        let naive_datetime = NaiveDate::from_ymd_opt(2021, 4, 6)
1661            .unwrap()
1662            .and_hms_opt(10, 15, 0)
1663            .unwrap()
1664            .with_nanosecond(449000000)
1665            .unwrap();
1666        let expected_datetime = expected_offset
1667            .from_local_datetime(&naive_datetime)
1668            .unwrap();
1669        assert_eq!(datetime, expected_datetime);
1670        Ok(())
1671    }
1672
1673    #[cfg(feature = "experimental-chrono")]
1674    #[test]
1675    fn test_timestamp_try_into_datetime_fixedoffset_error() -> IonResult<()> {
1676        let timestamp = TimestampBuilder::with_ymd(2021, 1, 1)
1677            .with_hms(0, 0, 0)
1678            .build()?;
1679        //     ^---- This timestamp has an unknown offset, so we cannot convert it into a DateTime<FixedOffset>
1680        let result: IonResult<DateTime<FixedOffset>> = timestamp.try_into();
1681        assert!(result.is_err());
1682        Ok(())
1683    }
1684
1685    #[test]
1686    fn test_timestamp_builder() {
1687        // Using individual field setters produces the same Timestamp as using setters
1688        // for common combinations of fields (with_ymd, with_hms).
1689        let timestamp1 = TimestampBuilder::with_year(2021)
1690            .with_month(2)
1691            .with_day(5)
1692            .with_hour(17)
1693            .with_minute(39)
1694            .with_second(51)
1695            .with_milliseconds(194)
1696            .with_offset(-4 * 60)
1697            .build()
1698            .unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
1699
1700        let timestamp2 = TimestampBuilder::with_ymd(2021, 2, 5)
1701            .with_hms(17, 39, 51)
1702            .with_milliseconds(194)
1703            .with_offset(-4 * 60)
1704            .build()
1705            .unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
1706
1707        assert_eq!(timestamp1.precision, TimestampPrecision::Second);
1708        assert_eq!(timestamp1.fractional_seconds, Some(Mantissa::Digits(3)));
1709        assert_eq!(timestamp1, timestamp2);
1710
1711        assert!(timestamp1.ion_eq(&timestamp2));
1712    }
1713
1714    #[test]
1715    fn test_timestamp_builder_without_minutes() {
1716        // Even though we set hour and not minute, this should still have a precision of HourAndMinute.
1717        let timestamp1 = TimestampBuilder::with_year(2021)
1718            .with_month(2)
1719            .with_day(5)
1720            .with_hour(17)
1721            .with_offset(60)
1722            .build()
1723            .unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
1724
1725        let timestamp2 = TimestampBuilder::with_ymd(2021, 2, 5)
1726            .with_hour_and_minute(17, 0)
1727            .with_offset(60)
1728            .build()
1729            .unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
1730
1731        assert_eq!(timestamp1.precision, TimestampPrecision::HourAndMinute);
1732        assert_eq!(timestamp1, timestamp2)
1733    }
1734
1735    #[test]
1736    fn test_timestamp_fixed_offset() -> IonResult<()> {
1737        let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
1738            .with_hms(10, 15, 0)
1739            .with_milliseconds(449)
1740            .with_offset(-5 * 60)
1741            .build()?;
1742        //                    ^-- Timestamp's offset API takes minutes
1743        // expected offset in minutes
1744        let expected_offset = -5 * 60;
1745
1746        assert_eq!(timestamp.offset().unwrap(), expected_offset);
1747        Ok(())
1748    }
1749
1750    #[test]
1751    fn test_timestamp_precision() -> IonResult<()> {
1752        let timestamp = Timestamp::with_year(2021).with_month(2).build()?;
1753        assert_eq!(timestamp.precision(), TimestampPrecision::Month);
1754        Ok(())
1755    }
1756
1757    #[test]
1758    fn test_timestamp_year() -> IonResult<()> {
1759        let timestamp_1 = TimestampBuilder::with_year(2021).with_month(2).build()?;
1760        assert_eq!(timestamp_1.year(), 2021);
1761
1762        let timestamp_2 = TimestampBuilder::with_ymd(2021, 12, 31)
1763            .with_hms(10, 15, 30)
1764            .with_offset(-11 * 60)
1765            .build()?;
1766
1767        assert_eq!(timestamp_2.year(), 2021);
1768
1769        let timestamp_3 = TimestampBuilder::with_ymd(2021, 12, 31)
1770            .with_hms(15, 15, 30)
1771            .with_offset(10 * 60)
1772            .build()?;
1773
1774        assert_eq!(timestamp_3.year(), 2021);
1775
1776        Ok(())
1777    }
1778
1779    #[test]
1780    fn test_timestamp_month() -> IonResult<()> {
1781        let timestamp_1 = TimestampBuilder::with_year(2021).with_month(2).build()?;
1782        assert_eq!(timestamp_1.month(), 2);
1783
1784        let timestamp_2 = TimestampBuilder::with_ymd(2021, 1, 31)
1785            .with_hms(10, 15, 30)
1786            .with_offset(-11 * 60)
1787            .build()?;
1788
1789        assert_eq!(timestamp_2.month(), 1);
1790
1791        let timestamp_3 = TimestampBuilder::with_ymd(2021, 1, 31)
1792            .with_hms(15, 15, 30)
1793            .with_offset(10 * 60)
1794            .build()?;
1795
1796        assert_eq!(timestamp_3.month(), 1);
1797
1798        Ok(())
1799    }
1800
1801    #[test]
1802    fn test_timestamp_day() -> IonResult<()> {
1803        let timestamp_1 = TimestampBuilder::with_year(2021).with_month(2).build()?;
1804        assert_eq!(timestamp_1.day(), 1);
1805
1806        let timestamp_2 = TimestampBuilder::with_year(2021)
1807            .with_month(2)
1808            .with_day(4)
1809            .build()?;
1810
1811        assert_eq!(timestamp_2.day(), 4);
1812
1813        let timestamp_3 = TimestampBuilder::with_ymd(2021, 4, 6)
1814            .with_hms(10, 15, 30)
1815            .with_offset(-11 * 60)
1816            .build()?;
1817
1818        assert_eq!(timestamp_3.day(), 6);
1819
1820        let timestamp_4 = TimestampBuilder::with_ymd(2021, 4, 6)
1821            .with_hms(15, 15, 30)
1822            .with_offset(10 * 60)
1823            .build()?;
1824
1825        assert_eq!(timestamp_4.day(), 6);
1826
1827        Ok(())
1828    }
1829
1830    #[rstest]
1831    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-90).build(), 10)]
1832    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-5 * 60).build(), 10)]
1833    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(5 * 60).build(), 10)]
1834    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(15).build(), 10)]
1835    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(30).build(), 10)]
1836    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(0).build(), 10)]
1837    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(0, 15, 30).with_offset(5 * 60).build(), 0)]
1838    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(23, 15, 30).with_offset(-5 * 60).build(), 23)]
1839    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(0, 15, 30).with_offset(23 * 60).build(), 0)]
1840    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-11 * 60).build(), 10)]
1841    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(15, 15, 30).with_offset(10 * 60).build(), 15)]
1842    fn test_timestamp_hour(
1843        #[case] timestamp: IonResult<Timestamp>,
1844        #[case] expected_hours: u32,
1845    ) -> IonResult<()> {
1846        assert_eq!(timestamp?.hour(), expected_hours);
1847        Ok(())
1848    }
1849
1850    #[rstest]
1851    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-90).build(), 15)]
1852    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-5 * 60).build(), 15)]
1853    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(5 * 60).build(), 15)]
1854    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(0).build(), 15)]
1855    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 0, 30).with_offset(5 * 60).build(), 0)]
1856    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 59, 30).with_offset(5 * 60).build(), 59)]
1857    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-11 * 60).build(), 15)]
1858    #[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(15, 15, 30).with_offset(10 * 60).build(), 15)]
1859    fn test_timestamp_minute(
1860        #[case] timestamp: IonResult<Timestamp>,
1861        #[case] expected_minutes: u32,
1862    ) -> IonResult<()> {
1863        assert_eq!(timestamp?.minute(), expected_minutes);
1864        Ok(())
1865    }
1866
1867    #[test]
1868    fn test_timestamp_second() -> IonResult<()> {
1869        let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
1870            .with_hms(10, 15, 30)
1871            .with_offset(-5 * 60)
1872            .build()?;
1873        assert_eq!(timestamp.second(), 30);
1874        Ok(())
1875    }
1876
1877    #[test]
1878    fn test_timestamp_nanoseconds() -> IonResult<()> {
1879        let timestamp_1 = TimestampBuilder::with_ymd(2021, 4, 6)
1880            .with_hms(10, 15, 30)
1881            .with_nanoseconds(192)
1882            .with_offset(-5 * 60)
1883            .build()?;
1884        assert_eq!(timestamp_1.nanoseconds(), 192);
1885
1886        let timestamp_2 = TimestampBuilder::with_ymd(2021, 4, 6)
1887            .with_hms(10, 15, 30)
1888            .with_milliseconds(192)
1889            .with_offset(-5 * 60)
1890            .build()?;
1891        assert_eq!(timestamp_2.nanoseconds(), 192000000);
1892
1893        let timestamp_3 = TimestampBuilder::with_ymd(2021, 4, 6)
1894            .with_hms(10, 15, 30)
1895            .with_offset(-5 * 60)
1896            .build()?;
1897        assert_eq!(timestamp_3.nanoseconds(), 0);
1898
1899        // Big fractional coefficient
1900        let big_coefficient: Int = Int::from(i128::MAX).data.mul(4).into();
1901        let timestamp_4 = Timestamp::with_ymd(2023, 1, 1)
1902            .with_hour_and_minute(0, 0)
1903            .with_second(0)
1904            .with_fractional_seconds(Decimal::new(big_coefficient, -39))
1905            .build()?;
1906        assert_eq!(timestamp_4.nanoseconds(), 680564733);
1907
1908        // Exponent delta > 38: result should be 0
1909        let timestamp_5 = Timestamp::with_ymd(2023, 1, 1)
1910            .with_hour_and_minute(0, 0)
1911            .with_second(0)
1912            .with_fractional_seconds(Decimal::new(1i64, -50))
1913            .build()?;
1914        assert_eq!(timestamp_5.nanoseconds(), 0);
1915
1916        Ok(())
1917    }
1918
1919    #[test]
1920    fn test_timestamp_milliseconds() -> IonResult<()> {
1921        let timestamp_1 = TimestampBuilder::with_ymd(2021, 4, 6)
1922            .with_hms(10, 15, 30)
1923            .with_milliseconds(192)
1924            .with_offset(-5 * 60)
1925            .build()?;
1926        assert_eq!(timestamp_1.milliseconds(), 192);
1927
1928        let timestamp_2 = TimestampBuilder::with_ymd(2021, 4, 6)
1929            .with_hms(10, 15, 30)
1930            .with_offset(-5 * 60)
1931            .build()?;
1932        assert_eq!(timestamp_2.milliseconds(), 0);
1933        Ok(())
1934    }
1935
1936    #[test]
1937    fn test_timestamp_to_utc() -> IonResult<()> {
1938        let new_years_eve_nyc = TimestampBuilder::with_ymd(2022, 12, 31)
1939            .with_hms(23, 59, 00)
1940            .with_offset(-5 * 60)
1941            .build()?;
1942
1943        let london = new_years_eve_nyc.to_utc();
1944        assert_eq!(london.year(), 2023);
1945        assert_eq!(london.month(), 1);
1946        assert_eq!(london.day(), 1);
1947        assert_eq!(london.hour(), 4);
1948        assert_eq!(london.minute(), 59);
1949        assert_eq!(london.second(), 0);
1950        Ok(())
1951    }
1952
1953    #[test]
1954    fn test_timestamp_fractional_seconds_scale() -> IonResult<()> {
1955        // Set fractional seconds as Decimal
1956        let timestamp_with_micro_seconds = TimestampBuilder::with_ymd(2021, 4, 6)
1957            .with_hms(10, 15, 0)
1958            .with_fractional_seconds(Decimal::new(553u64, -6))
1959            .with_offset(-5 * 60)
1960            .build()?;
1961
1962        assert_eq!(
1963            timestamp_with_micro_seconds
1964                .fractional_seconds_scale()
1965                .unwrap(),
1966            6
1967        );
1968
1969        // Set fractional seconds as Decimal with 0 coefficient and non-negative exponent
1970        // "Fractions whose coefficient is zero and exponent is greater than -1 are ignored."
1971        let timestamp_with_redundant_fractional_seconds = TimestampBuilder::with_ymd(2021, 4, 6)
1972            .with_hms(10, 15, 0)
1973            .with_fractional_seconds(Decimal::new(0, 6))
1974            .with_offset(-5 * 60)
1975            .build()?;
1976        assert_eq!(
1977            timestamp_with_redundant_fractional_seconds.precision,
1978            TimestampPrecision::Second
1979        );
1980        assert_eq!(
1981            timestamp_with_redundant_fractional_seconds.fractional_seconds_scale(),
1982            None
1983        );
1984
1985        // Set fractional seconds with milliseconds
1986        let timestamp_with_milliseconds = TimestampBuilder::with_ymd(2021, 4, 6)
1987            .with_hms(10, 15, 0)
1988            .with_milliseconds(449)
1989            .with_offset(-5 * 60)
1990            .build()?;
1991
1992        assert_eq!(
1993            timestamp_with_milliseconds
1994                .fractional_seconds_scale()
1995                .unwrap(),
1996            3
1997        );
1998
1999        // Set a fractional seconds as Decimal with low precision
2000        let timestamp_with_seconds = TimestampBuilder::with_ymd(2021, 4, 6)
2001            .with_hms(10, 15, 0)
2002            .with_offset(-5 * 60)
2003            .build()?;
2004
2005        // For low precision fractional_seconds_scale should return a None
2006        assert_eq!(timestamp_with_seconds.fractional_seconds_scale(), None);
2007        Ok(())
2008    }
2009
2010    #[test]
2011    fn test_first_n_digits_of() {
2012        assert_eq!(0, super::first_n_digits_of(1, 0));
2013        assert_eq!(1, super::first_n_digits_of(1, 1));
2014        assert_eq!(2, super::first_n_digits_of(1, 2));
2015
2016        assert_eq!(0, super::first_n_digits_of(3, 0));
2017        assert_eq!(1, super::first_n_digits_of(3, 1));
2018        assert_eq!(2, super::first_n_digits_of(3, 2));
2019        assert_eq!(99, super::first_n_digits_of(9, 99));
2020        assert_eq!(999, super::first_n_digits_of(9, 999));
2021        assert_eq!(9999, super::first_n_digits_of(9, 9999));
2022
2023        assert_eq!(0, super::first_n_digits_of(0, 123456789));
2024        assert_eq!(1, super::first_n_digits_of(1, 123456789));
2025        assert_eq!(12, super::first_n_digits_of(2, 123456789));
2026        assert_eq!(123, super::first_n_digits_of(3, 123456789));
2027        assert_eq!(1234, super::first_n_digits_of(4, 123456789));
2028        assert_eq!(12345, super::first_n_digits_of(5, 123456789));
2029        assert_eq!(123456, super::first_n_digits_of(6, 123456789));
2030        assert_eq!(1234567, super::first_n_digits_of(7, 123456789));
2031        assert_eq!(12345678, super::first_n_digits_of(8, 123456789));
2032        assert_eq!(123456789, super::first_n_digits_of(9, 123456789));
2033    }
2034
2035    #[rstest]
2036    #[case::timestamp_with_same_year(TimestampBuilder::with_year(2020).build().unwrap(), TimestampBuilder::with_year(2020).build().unwrap(), Ordering::Equal)]
2037    #[case::timestamp_with_different_year(TimestampBuilder::with_year(2020).build().unwrap(), TimestampBuilder::with_year(2021).build().unwrap(), Ordering::Less)]
2038    #[case::timestamp_with_milliseconds(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_milliseconds(449).with_offset(5 * 60).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_milliseconds(449).with_offset(5 * 60).build().unwrap(), Ordering::Equal)]
2039    #[case::timestamp_with_milliseconds_nanoseconds(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_milliseconds(449).with_offset(5 * 60).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_nanoseconds(449000005).with_offset(5 * 60).build().unwrap(), Ordering::Less)]
2040    #[case::timestamp_with_fractional_seconds(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_fractional_seconds(Decimal::new(449u64, -3)).with_offset(5 * 60).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_nanoseconds(449000000).with_offset(5 * 60).build().unwrap(), Ordering::Equal)]
2041    #[case::timestamp_with_different_precision(TimestampBuilder::with_year(2020).with_month(3).build().unwrap(), TimestampBuilder::with_year(2020).build().unwrap(), Ordering::Greater)]
2042    #[case::timestamp_with_same_offset(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_offset(-5 * 60).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_offset(-5 * 60).build().unwrap(), Ordering::Equal)]
2043    #[case::timestamp_with_different_offset(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_offset(5 * 60).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_offset(-5 * 60).build().unwrap(), Ordering::Less)]
2044    #[case::timestamp_with_unknown_offset(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_offset(-5 * 60).build().unwrap(), Ordering::Less)]
2045    #[case::timestamp_with_unknown_offset(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_nanoseconds(0).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).build().unwrap(), Ordering::Equal)]
2046    #[case::timestamp_with_unknown_offset(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).with_nanoseconds(449000005).build().unwrap(), TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 0).build().unwrap(), Ordering::Greater)]
2047    #[case::timestamp_with_second_precison_and_year_precision(TimestampBuilder::with_ymd(2001, 1, 1).build().unwrap(), TimestampBuilder::with_ymd(2001, 1, 1).with_hms(0, 0, 0).with_fractional_seconds(Decimal::new(00000000000000000000i128, -20)).build().unwrap(), Ordering::Equal)]
2048    fn timestamp_ordering_tests(
2049        #[case] this: Timestamp,
2050        #[case] other: Timestamp,
2051        #[case] expected: Ordering,
2052    ) {
2053        assert_eq!(this.cmp(&other), expected)
2054    }
2055
2056    #[test]
2057    fn ion_eq_fraction_seconds_mixed_mantissa() {
2058        let t1 = Timestamp {
2059            date_time: NaiveDateTime::from_str("1857-05-29T19:25:59.100").unwrap(),
2060            offset: Some(offset_east(60 * 60 * 23 + 60 * 59)),
2061            precision: TimestampPrecision::Second,
2062            fractional_seconds: Some(Mantissa::Digits(1)),
2063        };
2064        let t2 = Timestamp {
2065            date_time: NaiveDateTime::from_str("1857-05-29T19:25:59").unwrap(),
2066            offset: Some(offset_east(60 * 60 * 23 + 60 * 59)),
2067            precision: TimestampPrecision::Second,
2068            fractional_seconds: Some(Mantissa::Arbitrary(Decimal::new(1u64, -1))),
2069        };
2070        assert_eq!(t1, t2);
2071        assert!(t1.ion_eq(&t2));
2072    }
2073
2074    #[test]
2075    fn ion_eq_fraction_seconds_mixed_mantissa_2() {
2076        let t1 = Timestamp {
2077            date_time: NaiveDateTime::from_str("2001-08-01T18:18:49.006").unwrap(),
2078            offset: Some(offset_east(60 * 60 + 60)),
2079            precision: TimestampPrecision::Second,
2080            fractional_seconds: Some(Mantissa::Digits(5)),
2081        };
2082        let t2 = Timestamp {
2083            date_time: NaiveDateTime::from_str("2001-08-01T18:18:49").unwrap(),
2084            offset: Some(offset_east(60 * 60 + 60)),
2085            precision: TimestampPrecision::Second,
2086            fractional_seconds: Some(Mantissa::Arbitrary(Decimal::new(600u64, -5))),
2087        };
2088        assert_eq!(t1, t2);
2089        assert!(t1.ion_eq(&t2));
2090    }
2091
2092    #[rstest]
2093    #[case(TimestampBuilder::with_year(3030).build().unwrap(), "3030T")]
2094    #[case(TimestampBuilder::with_year(3030).with_month(11).build().unwrap(), "3030-11T")]
2095    #[case(TimestampBuilder::with_ymd(3030, 3, 31).build().unwrap(), "3030-03-31T")]
2096    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hour_and_minute(17, 31).build().unwrap(), "3030-03-31T17:31-00:00")]
2097    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hour_and_minute(17, 31).with_offset(-420).build().unwrap(), "3030-03-31T17:31-07:00")]
2098    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hour_and_minute(17, 31).build_utc_fields_at_offset(-420).unwrap(), "3030-03-31T10:31-07:00")]
2099    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hms(17, 31, 57).with_offset(0).build().unwrap(), "3030-03-31T17:31:57+00:00")]
2100    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hms(17, 31, 57).with_milliseconds(27).with_offset(0).build().unwrap(), "3030-03-31T17:31:57.027+00:00")]
2101    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hms(17, 31, 57).with_microseconds(27).with_offset(0).build().unwrap(), "3030-03-31T17:31:57.000027+00:00")]
2102    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hms(17, 31, 57).with_nanoseconds(27).with_offset(0).build().unwrap(), "3030-03-31T17:31:57.000000027+00:00")]
2103    #[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hms(17, 31, 57).with_fractional_seconds(Decimal::new(27, -12)).with_offset(0).build().unwrap(), "3030-03-31T17:31:57.000000000027+00:00")]
2104    fn test_display(#[case] ts: Timestamp, #[case] expect: String) {
2105        let mut buf = Vec::new();
2106        write!(&mut buf, "{ts}").unwrap();
2107        assert_eq!(expect, String::from_utf8(buf).unwrap());
2108    }
2109}