use crate::decimal::Sign;
use crate::ion_data::{IonDataHash, IonDataOrd, IonEq};
use crate::result::{IonError, IonFailure, IonResult};
use crate::types::{CountDecimalDigits, Decimal};
use chrono::{
DateTime, Datelike, FixedOffset, LocalResult, NaiveDate, NaiveDateTime, TimeZone, Timelike,
};
use std::cmp::Ordering;
use std::fmt::{Debug, Display, Formatter};
use std::hash::{Hash, Hasher};
use std::marker::PhantomData;
#[derive(Debug, Clone, Copy, Eq, PartialEq, PartialOrd, Ord, Default, Hash)]
pub enum TimestampPrecision {
#[default]
Year,
Month,
Day,
HourAndMinute,
Second,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Mantissa {
Digits(u32),
Arbitrary(Decimal),
}
impl Mantissa {
fn decimals_equal(d1: &Decimal, d2: &Decimal) -> bool {
(d1.is_empty() && d2.is_empty())
|| d1.eq(d2)
|| (d1.coefficient().is_zero() && d2.coefficient().is_zero() && d1.exponent == d2.exponent)
}
fn decimals_compare(d1: &Decimal, d2: &Decimal) -> Ordering {
if d1.is_empty() && d2.is_empty() {
Ordering::Equal
} else if d1.coefficient().is_zero() && d2.coefficient().is_zero() {
d1.exponent.cmp(&d2.exponent)
} else {
d1.cmp(d2)
}
}
}
trait EmptyMantissa {
fn is_empty(&self) -> bool;
}
impl EmptyMantissa for Decimal {
fn is_empty(&self) -> bool {
self.coefficient().is_zero() && self.exponent == 0
}
}
impl EmptyMantissa for Mantissa {
fn is_empty(&self) -> bool {
match self {
Mantissa::Digits(0) => true,
Mantissa::Arbitrary(d) => d.is_empty(),
_ => false,
}
}
}
fn first_n_digits_of(num_digits: u32, value: u32) -> u32 {
let total_digits = value.count_decimal_digits();
if total_digits <= num_digits {
return value;
}
value / 10u32.pow(total_digits - num_digits)
}
fn offset_east(seconds_east: i32) -> FixedOffset {
FixedOffset::east_opt(seconds_east)
.expect("seconds_east was outside the supported range")
}
fn datetime_at_offset(utc_datetime: &NaiveDateTime, seconds_east: i32) -> DateTime<FixedOffset> {
offset_east(seconds_east).from_utc_datetime(utc_datetime)
}
#[derive(Debug, Clone)]
pub struct Timestamp {
pub(crate) date_time: NaiveDateTime,
pub(crate) offset: Option<FixedOffset>,
pub(crate) precision: TimestampPrecision,
pub(crate) fractional_seconds: Option<Mantissa>,
}
impl Timestamp {
#[cfg(feature = "experimental-chrono")]
pub fn from_datetime<D>(datetime: D, precision: TimestampPrecision) -> Timestamp
where
D: Datelike + Timelike + Into<Timestamp>,
{
let mut timestamp: Timestamp = datetime.into();
if precision < TimestampPrecision::Second {
timestamp.fractional_seconds = None;
}
timestamp.precision = precision;
timestamp
}
pub(crate) fn from_naive_datetime(date_time: NaiveDateTime) -> Self {
Timestamp {
date_time,
offset: None,
precision: TimestampPrecision::Second,
fractional_seconds: Some(Mantissa::Digits(9)),
}
}
pub(crate) fn from_fixed_offset_datetime(
fixed_offset_date_time: DateTime<FixedOffset>,
) -> Self {
let date_time = fixed_offset_date_time.naive_utc();
let offset = Some(*fixed_offset_date_time.offset());
Timestamp {
date_time,
offset,
precision: TimestampPrecision::Second,
fractional_seconds: Some(Mantissa::Digits(9)),
}
}
pub(crate) fn try_to_naive_datetime(&self) -> IonResult<NaiveDateTime> {
if self.offset.is_some() {
return IonResult::illegal_operation(
"cannot convert a Timestamp with a known offset into a NaiveDateTime",
);
}
Ok(downconvert_to_naive_datetime_with_nanoseconds(self))
}
pub(crate) fn try_to_datetime_fixed_offset(&self) -> IonResult<DateTime<FixedOffset>> {
if self.offset.is_none() {
return IonResult::illegal_operation(
"cannot convert a Timestamp with an unknown offset into a DateTime<FixedOffset>",
);
}
let date_time = downconvert_to_naive_datetime_with_nanoseconds(self);
Ok(self.offset.unwrap().from_utc_datetime(&date_time))
}
pub fn fractional_seconds_scale(&self) -> Option<i64> {
use Mantissa::*;
match self.fractional_seconds.as_ref() {
Some(Digits(number_of_digits)) => Some(*number_of_digits as i64),
Some(Arbitrary(decimal)) => Some(decimal.scale()),
None => None,
}
}
pub(crate) fn fractional_seconds_as_decimal(&self) -> Option<Decimal> {
use Mantissa::*;
match self.fractional_seconds.as_ref() {
Some(Digits(number_of_digits)) => {
const MAX_NANOSECOND_DIGITS: u32 = 9; let nanoseconds = self.date_time.nanosecond();
let leading_zeros = MAX_NANOSECOND_DIGITS - nanoseconds.count_decimal_digits();
let coefficient = if leading_zeros >= *number_of_digits {
0
} else {
first_n_digits_of(*number_of_digits - leading_zeros, nanoseconds)
};
let exponent = -i64::from(*number_of_digits);
Some(Decimal::new(coefficient, exponent))
}
Some(Arbitrary(decimal)) => Some(decimal.clone()),
None => None,
}
}
fn fractional_seconds_as_nanoseconds(&self) -> Option<u32> {
use Mantissa::*;
match self.fractional_seconds.as_ref() {
Some(Digits(_number_of_digits)) => Some(self.date_time.nanosecond()),
Some(Arbitrary(decimal)) => {
let nano_exp = decimal.exponent + 9;
let mag = decimal.coefficient().magnitude();
let nanos: u128 = if nano_exp >= 0 {
let factor = 10u128.saturating_pow(nano_exp as u32);
mag.as_u128().unwrap_or(0).saturating_mul(factor)
} else {
let divisor = 10u128.saturating_pow(nano_exp.unsigned_abs() as u32);
let divided = mag.data / divisor;
u128::try_from(divided).unwrap_or(0)
};
Some(nanos as u32)
}
None => None,
}
}
fn fractional_seconds_compare(&self, other: &Timestamp) -> Ordering {
use Mantissa::*;
match (
self.fractional_seconds.as_ref(),
other.fractional_seconds.as_ref(),
) {
(None, None) => Ordering::Equal,
(Some(_m), None) => {
let d1 = self.fractional_seconds_as_decimal().unwrap();
let d2 = Decimal::new(0u64, 0);
d1.cmp(&d2)
}
(None, Some(_m)) => {
let d1 = Decimal::new(0u64, 0);
let d2 = other.fractional_seconds_as_decimal().unwrap();
d1.cmp(&d2)
}
(Some(Digits(_d1)), Some(Digits(_d2))) => {
let d1 = self.date_time.nanosecond();
let d2 = other.date_time.nanosecond();
d1.cmp(&d2)
}
(Some(Arbitrary(d1)), Some(Arbitrary(d2))) => Mantissa::decimals_compare(d1, d2),
(Some(Digits(_d1)), Some(Arbitrary(d2))) => {
let d1 = &self.fractional_seconds_as_decimal().unwrap();
Mantissa::decimals_compare(d1, d2)
}
(Some(Arbitrary(d1)), Some(Digits(_d2))) => {
let d2 = &other.fractional_seconds_as_decimal().unwrap();
Mantissa::decimals_compare(d1, d2)
}
}
}
fn fractional_seconds_equal(&self, other: &Timestamp) -> bool {
use Mantissa::*;
let m1 = match &self.fractional_seconds {
None => &Mantissa::Digits(0),
Some(m) => m,
};
let m2 = match &other.fractional_seconds {
None => &Mantissa::Digits(0),
Some(m) => m,
};
match (m1, m2) {
(Digits(d1), Digits(d2)) => {
if d1 != d2 {
return false;
}
let d1 = first_n_digits_of(*d1, self.date_time.nanosecond());
let d2 = first_n_digits_of(*d2, other.date_time.nanosecond());
d1 == d2
}
(Arbitrary(d1), Arbitrary(d2)) => Mantissa::decimals_equal(d1, d2),
(Digits(_d1), Arbitrary(d2)) => {
let d1 = match self.fractional_seconds_as_decimal() {
Some(decimal_value) => decimal_value,
None => Decimal::new(0, 0),
};
Mantissa::decimals_equal(&d1, d2)
}
(Arbitrary(d1), Digits(_d2)) => {
let d2 = match other.fractional_seconds_as_decimal() {
Some(decimal_value) => decimal_value,
None => Decimal::new(0, 0),
};
Mantissa::decimals_equal(d1, &d2)
}
}
}
fn format_fractional_seconds<W: std::fmt::Write>(&self, output: &mut W) -> IonResult<()> {
if self.fractional_seconds.is_none() {
return Ok(());
}
let mantissa = self.fractional_seconds.as_ref().unwrap();
if mantissa.is_empty() {
return Ok(());
}
match mantissa {
Mantissa::Digits(num_digits) => {
let scaled = self.date_time.nanosecond() / 10u32.pow(9 - *num_digits);
let actual_num_digits = scaled.count_decimal_digits();
let num_leading_zeros = *num_digits - actual_num_digits;
write!(output, ".")?;
for _ in 0..num_leading_zeros {
write!(output, "0")?;
}
write!(output, "{scaled}")?;
Ok(())
}
Mantissa::Arbitrary(decimal) => {
let exponent = decimal.exponent;
let coefficient = &decimal.coefficient();
if exponent >= 0 {
return IonResult::encoding_error(
"found fractional seconds decimal that was >= 1.",
);
}
let num_digits = decimal.coefficient().number_of_decimal_digits();
let abs_exponent = decimal.exponent.unsigned_abs();
let num_leading_zeros = abs_exponent - num_digits as u64;
write!(output, ".")?;
for _ in 0..num_leading_zeros {
write!(output, "0")?;
}
if coefficient.is_negative_zero() {
write!(output, "0")?;
} else if coefficient.sign() == Sign::Negative {
return IonResult::encoding_error(
"fractional seconds cannot have a negative coefficient (other than -0)",
);
} else {
write!(output, "{}", decimal.coefficient())?;
}
Ok(())
}
}
}
pub(crate) fn format<W: std::fmt::Write>(&self, output: &mut W) -> IonResult<()> {
let (offset_minutes, datetime) = if let Some(minutes) = self.offset {
let datetime: DateTime<FixedOffset> = self.try_to_datetime_fixed_offset()?;
(Some(minutes.local_minus_utc() / 60), datetime)
} else {
let datetime: NaiveDateTime = self.try_to_naive_datetime()?;
let datetime: DateTime<FixedOffset> = datetime_at_offset(&datetime, 0);
(None, datetime)
};
write!(output, "{:0>4}", datetime.year())?;
if self.precision == TimestampPrecision::Year {
write!(output, "T")?;
return Ok(());
}
write!(output, "-{:0>2}", datetime.month())?;
if self.precision == TimestampPrecision::Month {
write!(output, "T")?;
return Ok(());
}
write!(output, "-{:0>2}", datetime.day())?;
if self.precision == TimestampPrecision::Day {
write!(output, "T")?;
return Ok(());
}
write!(
output,
"T{:0>2}:{:0>2}",
datetime.hour(),
datetime.minute()
)?;
if self.precision == TimestampPrecision::HourAndMinute {
self.format_offset(offset_minutes, output)?;
return Ok(());
}
write!(output, ":{:0>2}", datetime.second())?;
self.format_fractional_seconds(output)?;
self.format_offset(offset_minutes, output)?;
Ok(())
}
fn format_offset<W: std::fmt::Write>(
&self,
offset_minutes: Option<i32>,
output: &mut W,
) -> IonResult<()> {
let (sign, hours, minutes) = match offset_minutes {
None => ("-", 0, 0),
Some(offset_minutes) => {
const MINUTES_PER_HOUR: i32 = 60;
let sign = if offset_minutes >= 0 { "+" } else { "-" };
let offset_minutes = offset_minutes.abs();
let hours = offset_minutes / MINUTES_PER_HOUR;
let minutes = offset_minutes % MINUTES_PER_HOUR;
(sign, hours, minutes)
}
};
write!(output, "{sign}{hours:0>2}:{minutes:0>2}")?;
Ok(())
}
pub fn with_year(year: u32) -> TimestampBuilder<HasYear> {
TimestampBuilder::with_year(year)
}
pub fn with_ymd(year: u32, month: u32, day: u32) -> TimestampBuilder<HasDay> {
TimestampBuilder::with_year(year)
.with_month(month)
.with_day(day)
}
pub fn offset(&self) -> Option<i32> {
self.offset.map(|offset| offset.local_minus_utc() / 60)
}
pub fn precision(&self) -> TimestampPrecision {
self.precision
}
pub fn year(&self) -> u32 {
if let Some(offset) = self.offset {
let local_date_time =
DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
return local_date_time.year() as u32;
}
self.date_time.year() as u32
}
pub fn month(&self) -> u32 {
if let Some(offset) = self.offset {
let local_date_time =
DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
return local_date_time.month();
}
self.date_time.month()
}
pub fn day(&self) -> u32 {
if let Some(offset) = self.offset {
let local_date_time =
DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
return local_date_time.day();
}
self.date_time.day()
}
pub fn hour(&self) -> u32 {
if let Some(offset) = self.offset {
let local_date_time =
DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
return local_date_time.hour();
}
self.date_time.hour()
}
pub fn minute(&self) -> u32 {
if let Some(offset) = self.offset {
let local_date_time =
DateTime::<FixedOffset>::from_naive_utc_and_offset(self.date_time, offset);
return local_date_time.minute();
}
self.date_time.minute()
}
pub fn second(&self) -> u32 {
self.date_time.second()
}
pub fn to_utc(&self) -> Timestamp {
Self::from_naive_datetime(self.date_time)
}
pub fn nanoseconds(&self) -> u32 {
self.fractional_seconds_as_nanoseconds().unwrap_or_default()
}
pub fn microseconds(&self) -> u32 {
self.fractional_seconds_as_nanoseconds()
.map(|s| s / 1_000)
.unwrap_or_default()
}
pub fn milliseconds(&self) -> u32 {
self.fractional_seconds_as_nanoseconds()
.map(|s| s / 1_000_000)
.unwrap_or_default()
}
}
impl Display for Timestamp {
fn fmt(&self, output: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
self.format(output).map_err(|_| std::fmt::Error)?;
Ok(())
}
}
impl PartialOrd for Timestamp {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Timestamp {
fn cmp(&self, other: &Self) -> Ordering {
let self_datetime = self.date_time.with_nanosecond(0).unwrap();
let other_datetime = other.date_time.with_nanosecond(0).unwrap();
let self_datetime = self
.offset
.map(|offset| offset.from_utc_datetime(&self_datetime))
.unwrap_or_else(|| datetime_at_offset(&self_datetime, 0));
let other_datetime = other
.offset
.map(|offset| offset.from_utc_datetime(&other_datetime))
.unwrap_or_else(|| datetime_at_offset(&other_datetime, 0));
let date_time_comparison = self_datetime.cmp(&other_datetime);
match date_time_comparison {
Ordering::Equal => self.fractional_seconds_compare(other),
_ => date_time_comparison,
}
}
}
impl PartialEq for Timestamp {
fn eq(&self, other: &Self) -> bool {
if !self.fractional_seconds_equal(other) {
return false;
}
let self_datetime = self.date_time.with_nanosecond(0).unwrap();
let other_datetime = other.date_time.with_nanosecond(0).unwrap();
let self_datetime = self
.offset
.map(|offset| offset.from_utc_datetime(&self_datetime))
.unwrap_or_else(|| datetime_at_offset(&self_datetime, 0));
let other_datetime = other
.offset
.map(|offset| offset.from_utc_datetime(&other_datetime))
.unwrap_or_else(|| datetime_at_offset(&other_datetime, 0));
self_datetime == other_datetime
}
}
impl Eq for Timestamp {}
impl IonEq for Timestamp {
fn ion_eq(&self, other: &Self) -> bool {
if self.precision != other.precision {
return false;
}
if self.offset != other.offset {
return false;
}
let self_dt = self.date_time;
let other_dt = other.date_time;
if self_dt.year() != other_dt.year() {
return false;
}
if self.precision >= TimestampPrecision::Month && self_dt.month() != other_dt.month() {
return false;
}
if self.precision >= TimestampPrecision::Day && self_dt.day() != other_dt.day() {
return false;
}
if self.precision >= TimestampPrecision::HourAndMinute
&& (self_dt.hour() != other_dt.hour() || self_dt.minute() != other_dt.minute())
{
return false;
}
if self.precision <= TimestampPrecision::HourAndMinute {
return true;
}
if self_dt.second() != other_dt.second() || !self.fractional_seconds_equal(other) {
return false;
}
true
}
}
impl IonDataOrd for Timestamp {
fn ion_cmp(&self, other: &Self) -> Ordering {
let ord = self.cmp(other);
if ord != Ordering::Equal {
return ord;
};
let ord = self.precision.cmp(&other.precision);
if ord != Ordering::Equal {
return ord;
};
match [
self.fractional_seconds_scale(),
other.fractional_seconds_scale(),
] {
[None, Some(b)] if b > 0 => return Ordering::Less,
[Some(a), None] if a > 0 => return Ordering::Greater,
[Some(a), Some(b)] => {
let ord = a.cmp(&b);
if ord != Ordering::Equal {
return ord;
}
}
_ => {}
}
match [self.offset, other.offset] {
[None, Some(_)] => Ordering::Less,
[None, None] => Ordering::Equal,
[Some(_), None] => Ordering::Greater,
[Some(o1), Some(o2)] => o1.local_minus_utc().cmp(&o2.local_minus_utc()),
}
}
}
impl IonDataHash for Timestamp {
fn ion_data_hash<H: Hasher>(&self, state: &mut H) {
self.precision.hash(state);
let self_dt = self.date_time;
self_dt.year().hash(state);
if self.precision >= TimestampPrecision::Month {
self_dt.month().hash(state)
}
if self.precision >= TimestampPrecision::Day {
self_dt.day().hash(state)
}
if self.precision >= TimestampPrecision::HourAndMinute {
self_dt.hour().hash(state);
self_dt.minute().hash(state);
}
if self.precision == TimestampPrecision::Second {
self_dt.second().hash(state);
let fractional_seconds_scale = self.fractional_seconds_scale();
match fractional_seconds_scale {
None | Some(0) => {}
Some(1..=9) => {
fractional_seconds_scale.unwrap().hash(state);
self.fractional_seconds_as_nanoseconds()
.unwrap()
.hash(state);
}
Some(_) => {
self.fractional_seconds_as_decimal()
.unwrap()
.ion_data_hash(state);
}
}
}
self.offset.hash(state);
}
}
#[derive(Debug, Clone)]
pub struct TimestampBuilder<T> {
_state: PhantomData<T>,
fields_are_utc: bool,
precision: TimestampPrecision,
offset: Option<i32>,
year: u32,
month: u32,
day: u32,
hour: u32,
minute: u32,
second: u32,
fractional_seconds: Option<Mantissa>,
nanoseconds: Option<u32>,
}
impl<T> TimestampBuilder<T> {
fn change_state<U>(self) -> TimestampBuilder<U> {
TimestampBuilder {
_state: PhantomData,
fields_are_utc: self.fields_are_utc,
precision: self.precision,
offset: self.offset,
year: self.year,
month: self.month,
day: self.day,
hour: self.hour,
minute: self.minute,
second: self.second,
fractional_seconds: self.fractional_seconds,
nanoseconds: self.nanoseconds,
}
}
fn configure_datetime<D>(&mut self, mut datetime: D) -> IonResult<D>
where
D: Datelike + Timelike + Debug,
{
if self.year == 0 || self.year > 9999 {
return IonResult::illegal_operation(format!(
"Timestamp year '{}' out of range (1-9999)",
self.year
));
}
datetime = datetime.with_year(self.year as i32).ok_or_else(|| {
IonError::illegal_operation(format!("specified year ('{}') is invalid", self.year))
})?;
if self.precision == TimestampPrecision::Year {
return Ok(datetime);
}
let month = self.month;
datetime = datetime.with_month(month).ok_or_else(|| {
IonError::illegal_operation(format!("specified month ('{month}') is invalid"))
})?;
if self.precision == TimestampPrecision::Month {
return Ok(datetime);
}
let day = self.day;
datetime = datetime.with_day(day).ok_or_else(|| {
IonError::illegal_operation(format!("specified day ('{day}') is invalid"))
})?;
if self.precision == TimestampPrecision::Day {
return Ok(datetime);
}
let hour = self.hour;
datetime = datetime.with_hour(hour).ok_or_else(|| {
IonError::illegal_operation(format!("specified hour ('{hour}') is invalid"))
})?;
let minute = self.minute;
datetime = datetime.with_minute(minute).ok_or_else(|| {
IonError::illegal_operation(format!("specified minute ('{minute}') is invalid"))
})?;
if self.precision == TimestampPrecision::HourAndMinute {
return Ok(datetime);
}
let second = self.second;
datetime = datetime.with_second(second).ok_or_else(|| {
IonError::illegal_operation(format!("provided second ('{second}') is invalid."))
})?;
datetime = datetime
.with_nanosecond(self.nanoseconds.unwrap_or(0))
.ok_or_else(|| {
IonError::illegal_operation(format!("provided nanosecond ('{second}') is invalid"))
})?;
Ok(datetime)
}
fn apply_offset(
offset_minutes: i32,
fields_are_utc: bool,
datetime: NaiveDateTime,
) -> IonResult<DateTime<FixedOffset>> {
const SECONDS_PER_MINUTE: i32 = 60;
let offset_seconds = offset_minutes * SECONDS_PER_MINUTE;
let offset = FixedOffset::east_opt(offset_seconds).ok_or_else(|| {
IonError::illegal_operation(format!(
"specified offset ({offset_minutes} minutes) is invalid"
))
})?;
if fields_are_utc {
return Ok(offset.from_utc_datetime(&datetime));
}
match offset.from_local_datetime(&datetime) {
LocalResult::None => {
IonResult::illegal_operation(
format!(
"specified offset/datetime pair is invalid (offset={offset_minutes}, datetime={datetime})"
)
)
},
LocalResult::Single(datetime) => Ok(datetime),
LocalResult::Ambiguous(_min, _max) => {
IonResult::illegal_operation(
format!(
"specified offset/datetime pair produces an ambiguous timestamp (offset={offset_minutes}, datetime={datetime})"
)
)
}
}
}
pub fn build(mut self) -> IonResult<Timestamp> {
let mut datetime: NaiveDateTime = NaiveDate::from_ymd_opt(0, 1, 1)
.unwrap()
.and_hms_nano_opt(0, 0, 0, 0)
.unwrap();
datetime = self.configure_datetime(datetime)?;
let mut timestamp: Timestamp = if let Some(offset_minutes) = self.offset {
let datetime_with_offset: DateTime<FixedOffset> =
Self::apply_offset(offset_minutes, self.fields_are_utc, datetime)?;
Timestamp::from_fixed_offset_datetime(datetime_with_offset)
} else {
Timestamp::from_naive_datetime(datetime)
};
if self.precision < TimestampPrecision::Second {
timestamp.fractional_seconds = None;
}
timestamp.precision = self.precision;
if self.precision == TimestampPrecision::Second {
timestamp.fractional_seconds = self.fractional_seconds;
if let Some(Mantissa::Arbitrary(ref decimal)) = ×tamp.fractional_seconds {
if decimal.is_less_than_zero() {
return IonResult::illegal_operation(
"cannot create a timestamp with negative fractional seconds",
);
}
if decimal.is_greater_than_or_equal_to_one() {
return IonResult::illegal_operation(
"cannot create a timestamp with a fractional seconds >= 1.0",
);
}
if decimal.is_zero() && decimal.exponent >= 0 {
timestamp.fractional_seconds = None;
}
}
}
Ok(timestamp)
}
pub(crate) fn build_utc_fields_at_offset(
mut self,
offset_minutes: i32,
) -> IonResult<Timestamp> {
self.fields_are_utc = true;
self.offset = Some(offset_minutes);
self.build()
}
}
#[derive(Debug, Clone)]
pub struct HasYear;
impl TimestampBuilder<HasYear> {
pub fn with_year(year: u32) -> Self {
TimestampBuilder {
_state: Default::default(),
fields_are_utc: false,
precision: TimestampPrecision::Year,
offset: None,
year,
month: 1,
day: 1,
hour: 0,
minute: 0,
second: 0,
fractional_seconds: None,
nanoseconds: None,
}
}
pub fn with_ymd(year: u32, month: u32, day: u32) -> TimestampBuilder<HasDay> {
Self::with_year(year).with_month(month).with_day(day)
}
pub fn with_month0(self, month: u32) -> TimestampBuilder<HasMonth> {
self.with_month(month + 1)
}
pub fn with_month(mut self, month: u32) -> TimestampBuilder<HasMonth> {
self.precision = TimestampPrecision::Month;
self.month = month;
self.change_state()
}
}
#[derive(Debug, Clone)]
pub struct HasMonth;
impl TimestampBuilder<HasMonth> {
pub fn with_day0(self, day: u32) -> TimestampBuilder<HasDay> {
self.with_day(day + 1)
}
pub fn with_day(mut self, day: u32) -> TimestampBuilder<HasDay> {
self.precision = TimestampPrecision::Day;
self.day = day;
self.change_state()
}
}
#[derive(Debug, Clone)]
pub struct HasDay;
impl TimestampBuilder<HasDay> {
pub fn with_hms(self, hour: u32, minute: u32, second: u32) -> TimestampBuilder<HasSeconds> {
self.with_hour(hour).with_minute(minute).with_second(second)
}
pub fn with_hour_and_minute(mut self, hour: u32, minute: u32) -> TimestampBuilder<HasMinute> {
self.precision = TimestampPrecision::HourAndMinute;
self.hour = hour;
self.minute = minute;
self.change_state()
}
pub fn with_hour(mut self, hour: u32) -> TimestampBuilder<HasHour> {
self.precision = TimestampPrecision::HourAndMinute;
self.hour = hour;
self.change_state()
}
}
macro_rules! with_offset {
() => {
pub fn with_offset(mut self, offset_minutes: i32) -> TimestampBuilder<HasOffset> {
self.offset = Some(offset_minutes);
self.change_state()
}
};
}
#[derive(Debug, Clone)]
pub struct HasHour;
impl TimestampBuilder<HasHour> {
pub fn with_minute(mut self, minute: u32) -> TimestampBuilder<HasMinute> {
self.precision = TimestampPrecision::HourAndMinute;
self.minute = minute;
self.change_state()
}
with_offset!();
}
#[derive(Debug, Clone)]
pub struct HasMinute;
impl TimestampBuilder<HasMinute> {
pub fn with_second(mut self, second: u32) -> TimestampBuilder<HasSeconds> {
self.precision = TimestampPrecision::Second;
self.second = second;
self.change_state()
}
with_offset!();
}
#[derive(Debug, Clone)]
pub struct HasSeconds;
impl TimestampBuilder<HasSeconds> {
pub fn with_nanoseconds(mut self, nanosecond: u32) -> TimestampBuilder<HasFractionalSeconds> {
self.fractional_seconds = Some(Mantissa::Digits(9));
self.nanoseconds = Some(nanosecond);
self.change_state()
}
pub fn with_microseconds(mut self, microsecond: u32) -> TimestampBuilder<HasFractionalSeconds> {
self.fractional_seconds = Some(Mantissa::Digits(6));
self.nanoseconds = Some(microsecond * 1000);
self.change_state()
}
pub fn with_milliseconds(mut self, millisecond: u32) -> TimestampBuilder<HasFractionalSeconds> {
self.fractional_seconds = Some(Mantissa::Digits(3));
self.nanoseconds = Some(millisecond * 1_000_000);
self.change_state()
}
pub fn with_nanoseconds_and_precision(
mut self,
nanoseconds: u32,
precision_digits: u32,
) -> TimestampBuilder<HasFractionalSeconds> {
self.fractional_seconds = Some(Mantissa::Digits(precision_digits));
self.nanoseconds = Some(nanoseconds);
self.change_state()
}
pub fn with_fractional_seconds(
mut self,
fractional_seconds: Decimal,
) -> TimestampBuilder<HasFractionalSeconds> {
self.fractional_seconds = Some(Mantissa::Arbitrary(fractional_seconds));
self.nanoseconds = None;
self.change_state()
}
with_offset!();
}
#[derive(Debug, Clone)]
pub struct HasFractionalSeconds;
impl TimestampBuilder<HasFractionalSeconds> {
with_offset!();
}
#[derive(Debug, Clone)]
pub struct HasOffset;
fn downconvert_to_naive_datetime_with_nanoseconds(timestamp: &Timestamp) -> NaiveDateTime {
if timestamp.precision == TimestampPrecision::Second {
let nanoseconds = timestamp.fractional_seconds_as_nanoseconds().unwrap_or(0);
timestamp.date_time.with_nanosecond(nanoseconds).unwrap()
} else {
timestamp.date_time
}
}
#[cfg(feature = "experimental-chrono")]
impl TryInto<NaiveDateTime> for Timestamp {
type Error = IonError;
fn try_into(self) -> Result<NaiveDateTime, Self::Error> {
self.try_to_naive_datetime()
}
}
#[cfg(feature = "experimental-chrono")]
impl TryInto<DateTime<FixedOffset>> for Timestamp {
type Error = IonError;
fn try_into(self) -> Result<DateTime<FixedOffset>, Self::Error> {
self.try_to_datetime_fixed_offset()
}
}
#[cfg(feature = "experimental-chrono")]
impl From<NaiveDateTime> for Timestamp {
fn from(date_time: NaiveDateTime) -> Self {
Self::from_naive_datetime(date_time)
}
}
#[cfg(feature = "experimental-chrono")]
impl From<DateTime<FixedOffset>> for Timestamp {
fn from(fixed_offset_date_time: DateTime<FixedOffset>) -> Self {
Self::from_fixed_offset_datetime(fixed_offset_date_time)
}
}
#[cfg(test)]
mod timestamp_tests {
use super::*;
use crate::ion_data::IonEq;
use crate::result::IonResult;
use crate::types::Mantissa;
use crate::{Decimal, Int, Timestamp, TimestampPrecision};
use chrono::NaiveDateTime;
use rstest::*;
use std::cmp::Ordering;
use std::io::Write;
use std::ops::Mul;
use std::str::FromStr;
#[test]
fn test_timestamps_with_same_ymd_hms_millis_at_known_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5)
.with_hms(16, 43, 51)
.with_milliseconds(192);
let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
let timestamp2 = builder.with_offset(5 * 60).build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_ymd_hms_millis_at_known_offset_are_equal_ordering() -> IonResult<()>
{
let builder = TimestampBuilder::with_ymd(2021, 2, 5)
.with_hms(16, 43, 51)
.with_milliseconds(192);
let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
let timestamp2 = builder.with_offset(5 * 60).build()?;
assert!(timestamp1 == timestamp2);
Ok(())
}
#[test]
fn test_timestamps_with_same_ymd_hms_millis_at_unknown_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5)
.with_hms(16, 43, 51)
.with_milliseconds(192);
let timestamp1 = builder.clone().build()?;
let timestamp2 = builder.build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_ymd_hms_at_known_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
let timestamp2 = builder.with_offset(5 * 60).build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_from_utc_and_local_hm_fields_at_same_offset_are_equal() -> IonResult<()> {
let builder1 = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(11, 43);
let timestamp1 = builder1.with_offset(-5 * 60).build()?;
let builder2 = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
let timestamp2 = builder2.build_utc_fields_at_offset(-5 * 60)?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_from_utc_and_local_hms_fields_at_same_offset_are_equal() -> IonResult<()> {
let builder1 = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(11, 43, 51);
let timestamp1 = builder1.with_offset(-5 * 60).build()?;
let builder2 = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
let timestamp2 = builder2.build_utc_fields_at_offset(-5 * 60)?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_ymd_hms_at_unknown_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
let timestamp1 = builder.clone().build()?;
let timestamp2 = builder.build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_ymd_hm_at_known_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
let timestamp2 = builder.with_offset(5 * 60).build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_ymd_hm_at_unknown_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
let timestamp1 = builder.clone().build()?;
let timestamp2 = builder.build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_ymd_at_unknown_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5);
let timestamp1 = builder.clone().build()?;
let timestamp2 = builder.build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_ym_at_unknown_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_year(2021).with_month(2);
let timestamp1 = builder.clone().build()?;
let timestamp2 = builder.build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_same_year_at_unknown_offset_are_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_year(2021);
let timestamp1 = builder.clone().build()?;
let timestamp2 = builder.build()?;
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_at_different_offsets_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5)
.with_hms(16, 43, 51)
.with_milliseconds(192);
let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
let timestamp2 = builder.with_offset(4 * 60).build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_known_and_unknown_offsets_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5)
.with_hms(16, 43, 51)
.with_milliseconds(192);
let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
let timestamp2 = builder.build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_precisions_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
let timestamp1 = builder.clone().with_offset(5 * 60).build()?;
let timestamp2 = builder.with_milliseconds(192).with_offset(5 * 60).build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_fractional_second_precision_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
let timestamp1 = builder
.clone()
.with_milliseconds(192)
.with_offset(5 * 60)
.build()?;
let timestamp2 = builder
.with_microseconds(193 * 1_000)
.with_offset(5 * 60)
.build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_fractional_seconds_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hms(16, 43, 51);
let timestamp1 = builder
.clone()
.with_milliseconds(192)
.with_offset(5 * 60)
.build()?;
let timestamp2 = builder.with_milliseconds(193).with_offset(5 * 60).build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_seconds_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5).with_hour_and_minute(16, 43);
let timestamp1 = builder
.clone()
.with_second(12)
.with_offset(5 * 60)
.build()?;
let timestamp2 = builder.with_second(13).with_offset(5 * 60).build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_minutes_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5);
let timestamp1 = builder
.clone()
.with_hour_and_minute(16, 42)
.with_offset(5 * 60)
.build()?;
let timestamp2 = builder
.with_hour_and_minute(16, 43)
.with_offset(5 * 60)
.build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_hours_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_ymd(2021, 2, 5);
let timestamp1 = builder
.clone()
.with_hour_and_minute(16, 42)
.with_offset(5 * 60)
.build()?;
let timestamp2 = builder
.with_hour_and_minute(17, 42)
.with_offset(5 * 60)
.build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_days_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_year(2021).with_month(2);
let timestamp1 = builder.clone().with_day(5).build()?;
let timestamp2 = builder.with_day(6).build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_months_are_not_equal() -> IonResult<()> {
let builder = TimestampBuilder::with_year(2021);
let timestamp1 = builder.clone().with_month(2).build()?;
let timestamp2 = builder.with_month(3).build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[test]
fn test_timestamps_with_different_years_are_not_equal() -> IonResult<()> {
let timestamp1 = TimestampBuilder::with_year(2021).build()?;
let timestamp2 = TimestampBuilder::with_year(2022).build()?;
assert_ne!(timestamp1, timestamp2);
assert!(!timestamp1.ion_eq(×tamp2));
Ok(())
}
#[cfg(feature = "experimental-chrono")]
#[test]
fn test_timestamp_try_into_naive_datetime() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.build()?;
let naive_datetime: NaiveDateTime = timestamp.try_into()?;
let expected = NaiveDate::from_ymd_opt(2021, 4, 6)
.unwrap()
.and_hms_opt(10, 15, 0)
.unwrap();
assert_eq!(expected, naive_datetime);
Ok(())
}
#[cfg(feature = "experimental-chrono")]
#[test]
fn test_timestamp_try_into_naive_datetime_fractional_seconds() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_milliseconds(449)
.build()?;
let datetime: NaiveDateTime = timestamp.try_into()?;
let naive_datetime = NaiveDate::from_ymd_opt(2021, 4, 6)
.unwrap()
.and_hms_opt(10, 15, 0)
.unwrap()
.with_nanosecond(449000000)
.unwrap();
assert_eq!(datetime, naive_datetime);
Ok(())
}
#[cfg(feature = "experimental-chrono")]
#[test]
fn test_timestamp_try_into_naive_datetime_error() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 1, 1)
.with_hms(0, 0, 0)
.with_offset(0)
.build()?;
let result: IonResult<NaiveDateTime> = timestamp.try_into();
assert!(result.is_err());
Ok(())
}
#[cfg(feature = "experimental-chrono")]
#[test]
fn test_timestamp_try_into_fixed_offset_datetime() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_offset(-5 * 60)
.build()?;
let datetime: DateTime<FixedOffset> = timestamp.try_into()?;
let expected_offset = offset_east(-5 * 60 * 60);
let naive_datetime = NaiveDate::from_ymd_opt(2021, 4, 6)
.unwrap()
.and_hms_opt(10, 15, 0)
.unwrap();
let expected_datetime = expected_offset
.from_local_datetime(&naive_datetime)
.unwrap();
assert_eq!(datetime, expected_datetime);
Ok(())
}
#[cfg(feature = "experimental-chrono")]
#[test]
fn test_timestamp_try_into_fixed_offset_datetime_fractional_seconds() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_milliseconds(449)
.with_offset(-5 * 60)
.build()?;
let datetime: DateTime<FixedOffset> = timestamp.try_into()?;
let expected_offset = offset_east(-5 * 60 * 60);
let naive_datetime = NaiveDate::from_ymd_opt(2021, 4, 6)
.unwrap()
.and_hms_opt(10, 15, 0)
.unwrap()
.with_nanosecond(449000000)
.unwrap();
let expected_datetime = expected_offset
.from_local_datetime(&naive_datetime)
.unwrap();
assert_eq!(datetime, expected_datetime);
Ok(())
}
#[cfg(feature = "experimental-chrono")]
#[test]
fn test_timestamp_try_into_datetime_fixedoffset_error() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 1, 1)
.with_hms(0, 0, 0)
.build()?;
let result: IonResult<DateTime<FixedOffset>> = timestamp.try_into();
assert!(result.is_err());
Ok(())
}
#[test]
fn test_timestamp_builder() {
let timestamp1 = TimestampBuilder::with_year(2021)
.with_month(2)
.with_day(5)
.with_hour(17)
.with_minute(39)
.with_second(51)
.with_milliseconds(194)
.with_offset(-4 * 60)
.build()
.unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
let timestamp2 = TimestampBuilder::with_ymd(2021, 2, 5)
.with_hms(17, 39, 51)
.with_milliseconds(194)
.with_offset(-4 * 60)
.build()
.unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
assert_eq!(timestamp1.precision, TimestampPrecision::Second);
assert_eq!(timestamp1.fractional_seconds, Some(Mantissa::Digits(3)));
assert_eq!(timestamp1, timestamp2);
assert!(timestamp1.ion_eq(×tamp2));
}
#[test]
fn test_timestamp_builder_without_minutes() {
let timestamp1 = TimestampBuilder::with_year(2021)
.with_month(2)
.with_day(5)
.with_hour(17)
.with_offset(60)
.build()
.unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
let timestamp2 = TimestampBuilder::with_ymd(2021, 2, 5)
.with_hour_and_minute(17, 0)
.with_offset(60)
.build()
.unwrap_or_else(|e| panic!("Couldn't build timestamp: {e:?}"));
assert_eq!(timestamp1.precision, TimestampPrecision::HourAndMinute);
assert_eq!(timestamp1, timestamp2)
}
#[test]
fn test_timestamp_fixed_offset() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_milliseconds(449)
.with_offset(-5 * 60)
.build()?;
let expected_offset = -5 * 60;
assert_eq!(timestamp.offset().unwrap(), expected_offset);
Ok(())
}
#[test]
fn test_timestamp_precision() -> IonResult<()> {
let timestamp = Timestamp::with_year(2021).with_month(2).build()?;
assert_eq!(timestamp.precision(), TimestampPrecision::Month);
Ok(())
}
#[test]
fn test_timestamp_year() -> IonResult<()> {
let timestamp_1 = TimestampBuilder::with_year(2021).with_month(2).build()?;
assert_eq!(timestamp_1.year(), 2021);
let timestamp_2 = TimestampBuilder::with_ymd(2021, 12, 31)
.with_hms(10, 15, 30)
.with_offset(-11 * 60)
.build()?;
assert_eq!(timestamp_2.year(), 2021);
let timestamp_3 = TimestampBuilder::with_ymd(2021, 12, 31)
.with_hms(15, 15, 30)
.with_offset(10 * 60)
.build()?;
assert_eq!(timestamp_3.year(), 2021);
Ok(())
}
#[test]
fn test_timestamp_month() -> IonResult<()> {
let timestamp_1 = TimestampBuilder::with_year(2021).with_month(2).build()?;
assert_eq!(timestamp_1.month(), 2);
let timestamp_2 = TimestampBuilder::with_ymd(2021, 1, 31)
.with_hms(10, 15, 30)
.with_offset(-11 * 60)
.build()?;
assert_eq!(timestamp_2.month(), 1);
let timestamp_3 = TimestampBuilder::with_ymd(2021, 1, 31)
.with_hms(15, 15, 30)
.with_offset(10 * 60)
.build()?;
assert_eq!(timestamp_3.month(), 1);
Ok(())
}
#[test]
fn test_timestamp_day() -> IonResult<()> {
let timestamp_1 = TimestampBuilder::with_year(2021).with_month(2).build()?;
assert_eq!(timestamp_1.day(), 1);
let timestamp_2 = TimestampBuilder::with_year(2021)
.with_month(2)
.with_day(4)
.build()?;
assert_eq!(timestamp_2.day(), 4);
let timestamp_3 = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 30)
.with_offset(-11 * 60)
.build()?;
assert_eq!(timestamp_3.day(), 6);
let timestamp_4 = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(15, 15, 30)
.with_offset(10 * 60)
.build()?;
assert_eq!(timestamp_4.day(), 6);
Ok(())
}
#[rstest]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-90).build(), 10)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-5 * 60).build(), 10)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(5 * 60).build(), 10)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(15).build(), 10)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(30).build(), 10)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(0).build(), 10)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(0, 15, 30).with_offset(5 * 60).build(), 0)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(23, 15, 30).with_offset(-5 * 60).build(), 23)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(0, 15, 30).with_offset(23 * 60).build(), 0)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-11 * 60).build(), 10)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(15, 15, 30).with_offset(10 * 60).build(), 15)]
fn test_timestamp_hour(
#[case] timestamp: IonResult<Timestamp>,
#[case] expected_hours: u32,
) -> IonResult<()> {
assert_eq!(timestamp?.hour(), expected_hours);
Ok(())
}
#[rstest]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-90).build(), 15)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-5 * 60).build(), 15)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(5 * 60).build(), 15)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(0).build(), 15)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 0, 30).with_offset(5 * 60).build(), 0)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 59, 30).with_offset(5 * 60).build(), 59)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(10, 15, 30).with_offset(-11 * 60).build(), 15)]
#[case(TimestampBuilder::with_ymd(2021, 4, 6).with_hms(15, 15, 30).with_offset(10 * 60).build(), 15)]
fn test_timestamp_minute(
#[case] timestamp: IonResult<Timestamp>,
#[case] expected_minutes: u32,
) -> IonResult<()> {
assert_eq!(timestamp?.minute(), expected_minutes);
Ok(())
}
#[test]
fn test_timestamp_second() -> IonResult<()> {
let timestamp = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 30)
.with_offset(-5 * 60)
.build()?;
assert_eq!(timestamp.second(), 30);
Ok(())
}
#[test]
fn test_timestamp_nanoseconds() -> IonResult<()> {
let timestamp_1 = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 30)
.with_nanoseconds(192)
.with_offset(-5 * 60)
.build()?;
assert_eq!(timestamp_1.nanoseconds(), 192);
let timestamp_2 = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 30)
.with_milliseconds(192)
.with_offset(-5 * 60)
.build()?;
assert_eq!(timestamp_2.nanoseconds(), 192000000);
let timestamp_3 = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 30)
.with_offset(-5 * 60)
.build()?;
assert_eq!(timestamp_3.nanoseconds(), 0);
let big_coefficient: Int = Int::from(i128::MAX).data.mul(4).into();
let timestamp_4 = Timestamp::with_ymd(2023, 1, 1)
.with_hour_and_minute(0, 0)
.with_second(0)
.with_fractional_seconds(Decimal::new(big_coefficient, -39))
.build()?;
assert_eq!(timestamp_4.nanoseconds(), 680564733);
let timestamp_5 = Timestamp::with_ymd(2023, 1, 1)
.with_hour_and_minute(0, 0)
.with_second(0)
.with_fractional_seconds(Decimal::new(1i64, -50))
.build()?;
assert_eq!(timestamp_5.nanoseconds(), 0);
Ok(())
}
#[test]
fn test_timestamp_milliseconds() -> IonResult<()> {
let timestamp_1 = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 30)
.with_milliseconds(192)
.with_offset(-5 * 60)
.build()?;
assert_eq!(timestamp_1.milliseconds(), 192);
let timestamp_2 = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 30)
.with_offset(-5 * 60)
.build()?;
assert_eq!(timestamp_2.milliseconds(), 0);
Ok(())
}
#[test]
fn test_timestamp_to_utc() -> IonResult<()> {
let new_years_eve_nyc = TimestampBuilder::with_ymd(2022, 12, 31)
.with_hms(23, 59, 00)
.with_offset(-5 * 60)
.build()?;
let london = new_years_eve_nyc.to_utc();
assert_eq!(london.year(), 2023);
assert_eq!(london.month(), 1);
assert_eq!(london.day(), 1);
assert_eq!(london.hour(), 4);
assert_eq!(london.minute(), 59);
assert_eq!(london.second(), 0);
Ok(())
}
#[test]
fn test_timestamp_fractional_seconds_scale() -> IonResult<()> {
let timestamp_with_micro_seconds = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_fractional_seconds(Decimal::new(553u64, -6))
.with_offset(-5 * 60)
.build()?;
assert_eq!(
timestamp_with_micro_seconds
.fractional_seconds_scale()
.unwrap(),
6
);
let timestamp_with_redundant_fractional_seconds = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_fractional_seconds(Decimal::new(0, 6))
.with_offset(-5 * 60)
.build()?;
assert_eq!(
timestamp_with_redundant_fractional_seconds.precision,
TimestampPrecision::Second
);
assert_eq!(
timestamp_with_redundant_fractional_seconds.fractional_seconds_scale(),
None
);
let timestamp_with_milliseconds = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_milliseconds(449)
.with_offset(-5 * 60)
.build()?;
assert_eq!(
timestamp_with_milliseconds
.fractional_seconds_scale()
.unwrap(),
3
);
let timestamp_with_seconds = TimestampBuilder::with_ymd(2021, 4, 6)
.with_hms(10, 15, 0)
.with_offset(-5 * 60)
.build()?;
assert_eq!(timestamp_with_seconds.fractional_seconds_scale(), None);
Ok(())
}
#[test]
fn test_first_n_digits_of() {
assert_eq!(0, super::first_n_digits_of(1, 0));
assert_eq!(1, super::first_n_digits_of(1, 1));
assert_eq!(2, super::first_n_digits_of(1, 2));
assert_eq!(0, super::first_n_digits_of(3, 0));
assert_eq!(1, super::first_n_digits_of(3, 1));
assert_eq!(2, super::first_n_digits_of(3, 2));
assert_eq!(99, super::first_n_digits_of(9, 99));
assert_eq!(999, super::first_n_digits_of(9, 999));
assert_eq!(9999, super::first_n_digits_of(9, 9999));
assert_eq!(0, super::first_n_digits_of(0, 123456789));
assert_eq!(1, super::first_n_digits_of(1, 123456789));
assert_eq!(12, super::first_n_digits_of(2, 123456789));
assert_eq!(123, super::first_n_digits_of(3, 123456789));
assert_eq!(1234, super::first_n_digits_of(4, 123456789));
assert_eq!(12345, super::first_n_digits_of(5, 123456789));
assert_eq!(123456, super::first_n_digits_of(6, 123456789));
assert_eq!(1234567, super::first_n_digits_of(7, 123456789));
assert_eq!(12345678, super::first_n_digits_of(8, 123456789));
assert_eq!(123456789, super::first_n_digits_of(9, 123456789));
}
#[rstest]
#[case::timestamp_with_same_year(TimestampBuilder::with_year(2020).build().unwrap(), TimestampBuilder::with_year(2020).build().unwrap(), Ordering::Equal)]
#[case::timestamp_with_different_year(TimestampBuilder::with_year(2020).build().unwrap(), TimestampBuilder::with_year(2021).build().unwrap(), Ordering::Less)]
#[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)]
#[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)]
#[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)]
#[case::timestamp_with_different_precision(TimestampBuilder::with_year(2020).with_month(3).build().unwrap(), TimestampBuilder::with_year(2020).build().unwrap(), Ordering::Greater)]
#[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)]
#[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)]
#[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)]
#[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)]
#[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)]
#[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)]
fn timestamp_ordering_tests(
#[case] this: Timestamp,
#[case] other: Timestamp,
#[case] expected: Ordering,
) {
assert_eq!(this.cmp(&other), expected)
}
#[test]
fn ion_eq_fraction_seconds_mixed_mantissa() {
let t1 = Timestamp {
date_time: NaiveDateTime::from_str("1857-05-29T19:25:59.100").unwrap(),
offset: Some(offset_east(60 * 60 * 23 + 60 * 59)),
precision: TimestampPrecision::Second,
fractional_seconds: Some(Mantissa::Digits(1)),
};
let t2 = Timestamp {
date_time: NaiveDateTime::from_str("1857-05-29T19:25:59").unwrap(),
offset: Some(offset_east(60 * 60 * 23 + 60 * 59)),
precision: TimestampPrecision::Second,
fractional_seconds: Some(Mantissa::Arbitrary(Decimal::new(1u64, -1))),
};
assert_eq!(t1, t2);
assert!(t1.ion_eq(&t2));
}
#[test]
fn ion_eq_fraction_seconds_mixed_mantissa_2() {
let t1 = Timestamp {
date_time: NaiveDateTime::from_str("2001-08-01T18:18:49.006").unwrap(),
offset: Some(offset_east(60 * 60 + 60)),
precision: TimestampPrecision::Second,
fractional_seconds: Some(Mantissa::Digits(5)),
};
let t2 = Timestamp {
date_time: NaiveDateTime::from_str("2001-08-01T18:18:49").unwrap(),
offset: Some(offset_east(60 * 60 + 60)),
precision: TimestampPrecision::Second,
fractional_seconds: Some(Mantissa::Arbitrary(Decimal::new(600u64, -5))),
};
assert_eq!(t1, t2);
assert!(t1.ion_eq(&t2));
}
#[rstest]
#[case(TimestampBuilder::with_year(3030).build().unwrap(), "3030T")]
#[case(TimestampBuilder::with_year(3030).with_month(11).build().unwrap(), "3030-11T")]
#[case(TimestampBuilder::with_ymd(3030, 3, 31).build().unwrap(), "3030-03-31T")]
#[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hour_and_minute(17, 31).build().unwrap(), "3030-03-31T17:31-00:00")]
#[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hour_and_minute(17, 31).with_offset(-420).build().unwrap(), "3030-03-31T17:31-07:00")]
#[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")]
#[case(TimestampBuilder::with_ymd(3030, 3, 31).with_hms(17, 31, 57).with_offset(0).build().unwrap(), "3030-03-31T17:31:57+00:00")]
#[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")]
#[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")]
#[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")]
#[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")]
fn test_display(#[case] ts: Timestamp, #[case] expect: String) {
let mut buf = Vec::new();
write!(&mut buf, "{ts}").unwrap();
assert_eq!(expect, String::from_utf8(buf).unwrap());
}
}