use bigdecimal::BigDecimal;
use chrono::{DateTime, FixedOffset};
use num_bigint::BigInt;
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DateType {
Date,
Time,
DateTime,
Unknown,
}
impl DateType {
pub fn name(&self) -> &'static str {
match self {
DateType::Date => "date",
DateType::Time => "time",
DateType::DateTime => "date-time",
DateType::Unknown => "unknown date type",
}
}
}
#[derive(Debug, Clone)]
pub struct DateValue {
pub dt: DateTime<FixedOffset>,
pub kind: DateType,
pub is_sql: bool,
}
impl DateValue {
pub fn new(dt: DateTime<FixedOffset>, kind: DateType) -> DateValue {
DateValue {
dt,
kind,
is_sql: false,
}
}
}
#[derive(Debug, Clone)]
pub enum TNumber {
Int(i32),
Long(i64),
BigInt(BigInt),
Float(f32),
Double(f64),
Decimal(BigDecimal),
}
impl TNumber {
pub fn from_i64(v: i64) -> TNumber {
if let Ok(i) = i32::try_from(v) {
TNumber::Int(i)
} else {
TNumber::Long(v)
}
}
pub fn is_integer(&self) -> bool {
matches!(
self,
TNumber::Int(_) | TNumber::Long(_) | TNumber::BigInt(_)
)
}
pub fn is_integer_value(&self) -> bool {
match self {
TNumber::Int(_) | TNumber::Long(_) | TNumber::BigInt(_) => true,
TNumber::Decimal(d) => d.is_integer(),
TNumber::Float(f) => f.fract() == 0.0 && f.is_finite(),
TNumber::Double(f) => f.fract() == 0.0 && f.is_finite(),
}
}
pub fn as_f64(&self) -> Option<f64> {
match self {
TNumber::Int(v) => Some(*v as f64),
TNumber::Long(v) => Some(*v as f64),
TNumber::BigInt(v) => v.to_string().parse().ok(),
TNumber::Float(v) => Some(*v as f64),
TNumber::Double(v) => Some(*v),
TNumber::Decimal(v) => v.to_string().parse().ok(),
}
}
pub fn as_f32(&self) -> Option<f32> {
self.as_f64().map(|v| v as f32)
}
pub fn as_big_decimal(&self) -> BigDecimal {
match self {
TNumber::Int(v) => BigDecimal::from(*v),
TNumber::Long(v) => BigDecimal::from(*v),
TNumber::BigInt(v) => BigDecimal::from_bigint(v.clone(), 0),
TNumber::Float(v) => BigDecimal::from_str(&format!("{v}")).unwrap_or_default(),
TNumber::Double(v) => BigDecimal::from_str(&format!("{v}")).unwrap_or_default(),
TNumber::Decimal(v) => v.clone(),
}
}
pub fn as_i64(&self) -> Option<i64> {
match self {
TNumber::Int(v) => Some(*v as i64),
TNumber::Long(v) => Some(*v),
TNumber::BigInt(v) => i64::try_from(v.clone()).ok(),
TNumber::Decimal(v) => v.to_string().parse().ok(),
TNumber::Float(v) if v.fract() == 0.0 => Some(*v as i64),
TNumber::Double(v) if v.fract() == 0.0 => Some(*v as i64),
_ => None,
}
}
pub fn to_plain_string(&self) -> String {
match self {
TNumber::Int(v) => v.to_string(),
TNumber::Long(v) => v.to_string(),
TNumber::BigInt(v) => v.to_string(),
TNumber::Float(v) => format!("{v}"),
TNumber::Double(v) => format!("{v}"),
TNumber::Decimal(v) => v.to_string(),
}
}
}
impl PartialEq for TNumber {
fn eq(&self, other: &Self) -> bool {
if self.is_integer() && other.is_integer() {
return self.as_i64() == other.as_i64();
}
if self.is_integer_value() && other.is_integer_value() {
return self.as_i64() == other.as_i64();
}
match (self.as_f64(), other.as_f64()) {
(Some(a), Some(b)) => a == b,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn int_long_decimal_equality() {
assert_eq!(TNumber::Int(1), TNumber::Long(1));
assert_eq!(TNumber::Int(1), TNumber::Decimal(BigDecimal::from(1)));
assert_ne!(TNumber::Int(1), TNumber::Int(2));
}
#[test]
fn big_decimal_roundtrip() {
let d = BigDecimal::from_str("1.50").unwrap();
let n = TNumber::Decimal(d);
assert_eq!(n.as_big_decimal().to_string(), "1.50");
assert!(!n.is_integer_value());
}
#[test]
fn integer_forms_detected() {
assert!(TNumber::Int(5).is_integer_value());
assert!(TNumber::Decimal(BigDecimal::from(5)).is_integer_value());
assert!(!TNumber::Decimal(BigDecimal::from_str("5.5").unwrap()).is_integer_value());
}
}