///|
#derive(Eq, Debug)
pub(all) enum NativeOffset {
KnownOffsetMinutes(Int32)
UnknownOffset
}
///|
/// A native timestamp's precision and offset, independent of any format library.
#derive(Debug)
pub struct NativeTimestamp {
year : Int32
month : Int32?
day : Int32?
hour : Int32?
minute : Int32?
second : Int32?
fraction : @smithy.NodeNumber?
offset : NativeOffset
}
///|
pub impl Eq for NativeTimestamp with fn equal(self, other) {
self.year == other.year &&
self.month == other.month &&
self.day == other.day &&
self.hour == other.hour &&
self.minute == other.minute &&
self.second == other.second &&
self.offset == other.offset &&
self.fraction.map(n => n.to_string()) ==
other.fraction.map(n => n.to_string())
}
///|
fn calendar_day(year : Int32, month : Int32, day : Int32) -> Bool {
if year < 1 || year > 9999 || month < 1 || month > 12 || day < 1 {
return false
}
let leap = year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
let days = match month {
2 => if leap { 29 } else { 28 }
4 | 6 | 9 | 11 => 30
_ => 31
}
day <= days
}
///|
/// Construct a neutral timestamp with seconds 0..59. Leap-second notation
/// is supported by the RFC 3339 string validator, not this native value type.
pub fn NativeTimestamp::new(
year : Int32,
month~ : Int32? = None,
day~ : Int32? = None,
hour~ : Int32? = None,
minute~ : Int32? = None,
second~ : Int32? = None,
fraction~ : @smithy.NodeNumber? = None,
offset~ : NativeOffset = UnknownOffset,
) -> NativeTimestamp raise ConfigurationError {
let bad = fn() {
ConfigurationError(
"I expected consistent timestamp precision, a valid calendar date and a valid offset.",
)
}
guard year >= 1 && year <= 9999 else { raise bad() }
if month is Some(m) && (m < 1 || m > 12) {
raise bad()
}
if day is Some(d) {
guard month is Some(m) && calendar_day(year, m, d) else { raise bad() }
}
if hour is Some(h) {
guard day is Some(_) && h >= 0 && h < 24 && minute is Some(_) else {
raise bad()
}
}
if minute is Some(m) {
guard hour is Some(_) && m >= 0 && m < 60 else { raise bad() }
}
if second is Some(s) {
guard minute is Some(_) && s >= 0 && s < 60 else { raise bad() }
}
if fraction is Some(f) {
guard second is Some(_) &&
f.numeric_compare(@smithy.NodeNumber::parse("0").unwrap()) >= 0 &&
f.numeric_compare(@smithy.NodeNumber::parse("1").unwrap()) < 0 else {
raise bad()
}
}
if offset is KnownOffsetMinutes(m) &&
(minute is None || m <= -1440 || m >= 1440) {
raise bad()
}
{ year, month, day, hour, minute, second, fraction, offset, }
}
///|
pub fn NativeTimestamp::year(self : NativeTimestamp) -> Int32 {
self.year
}
///|
pub fn NativeTimestamp::month(self : NativeTimestamp) -> Int32? {
self.month
}
///|
pub fn NativeTimestamp::day(self : NativeTimestamp) -> Int32? {
self.day
}
///|
pub fn NativeTimestamp::hour(self : NativeTimestamp) -> Int32? {
self.hour
}
///|
pub fn NativeTimestamp::minute(self : NativeTimestamp) -> Int32? {
self.minute
}
///|
pub fn NativeTimestamp::second(self : NativeTimestamp) -> Int32? {
self.second
}
///|
pub fn NativeTimestamp::fraction(self : NativeTimestamp) -> @smithy.NodeNumber? {
self.fraction
}
///|
pub fn NativeTimestamp::offset(self : NativeTimestamp) -> NativeOffset {
self.offset
}
///|
/// Expand a finite IEEE 754 value exactly, with at most 1074 fractional digits.
/// Decimal display strings round away the information needed at range boundaries.
fn exact_float(value : Double) -> @smithy.NodeNumber? {
let bits = value.reinterpret_as_int64()
let exponent_bits = ((bits >> 52) & 2047L).to_int()
if exponent_bits == 2047 {
return None
}
let fraction = bits & 4503599627370495L
let coefficient = BigInt::from_int64(
if exponent_bits == 0 {
fraction
} else {
fraction + 4503599627370496L
},
)
let exponent = if exponent_bits == 0 { -1074 } else { exponent_bits - 1075 }
let expanded = if exponent >= 0 {
coefficient << exponent
} else {
coefficient * BigInt::from_int(5).pow(BigInt::from_int(-exponent))
}
let text = (if bits < 0L { "-" } else { "" }) +
expanded.to_string() +
"e" +
(if exponent < 0 { exponent } else { 0 }).to_string()
@smithy.NodeNumber::parse(text)
}
///|
fn[V] exact_numeric(node : ValueNode[V]) -> @smithy.NodeNumber? {
match node {
Number(n) | Native(Decimal(n)) => Some(n)
Native(Float(n)) => exact_float(n)
_ => None
}
}
///|
fn NativeTimestamp::full(self : NativeTimestamp) -> Bool {
self.second is Some(_) && self.offset is KnownOffsetMinutes(_)
}