use crate::common::*;
use crate::error::DateTimeError;
use crate::interval::Interval;
use crate::parse::{decode_date_time, parse_date_time, timestamp2time};
use crate::time::Time;
use crate::timestamp::{Timestamp, DATE_TIME_BEGIN_VAL, DATE_TIME_END_VAL};
use crate::token::*;
use crate::{DateOrder, DateStyle, DateTime, DateUnit, FieldType};
use std::convert::TryFrom;
use std::mem::MaybeUninit;
const DATETIME_MIN_JULIAN: i32 = 0;
const DATE_END_JULIAN: i32 = 2_147_483_494;
#[inline]
fn is_valid_date(d: i32) -> bool {
(DATETIME_MIN_JULIAN - POSTGRES_EPOCH_JDATE) <= d
&& d < (DATE_END_JULIAN - POSTGRES_EPOCH_JDATE)
}
#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
#[repr(transparent)]
pub struct Date(i32);
impl Date {
#[inline]
pub(crate) const fn new(v: i32) -> Self {
Self(v)
}
#[inline]
pub(crate) const fn value(self) -> i32 {
self.0
}
#[inline]
pub fn try_from_ymd(y: i32, m: i32, d: i32) -> Result<Self, DateTimeError> {
let mut tm: PgTime = PgTime::new();
let mut bc = false;
tm.year = y;
tm.mon = m;
tm.mday = d;
if tm.year < 0 {
bc = true;
tm.year = -tm.year;
}
tm.validate_date(DTK_DATE_M, false, false, bc)?;
if !is_valid_julian(tm.year, tm.mon, tm.mday) {
return Err(DateTimeError::overflow());
}
let date = date2julian(tm.year, tm.mon, tm.mday) - POSTGRES_EPOCH_JDATE;
if !is_valid_date(date) {
Err(DateTimeError::overflow())
} else {
Ok(Date::new(date))
}
}
#[inline]
pub(crate) const fn is_begin(self) -> bool {
self.0 == std::i32::MIN
}
#[inline]
pub(crate) const fn is_end(self) -> bool {
self.0 == std::i32::MAX
}
#[inline]
fn is_valid(self) -> bool {
is_valid_date(self.0)
}
#[inline]
pub fn add_days(self, days: i32) -> Result<Self, DateTimeError> {
if self.is_infinite() {
return Ok(self);
}
let result = match self.0.checked_add(days) {
Some(v) => v,
None => return Err(DateTimeError::overflow()),
};
let date_ret = Date::new(result);
if date_ret.is_valid() {
Ok(date_ret)
} else {
Err(DateTimeError::overflow())
}
}
#[inline]
pub fn add_interval(self, span: Interval) -> Result<Timestamp, DateTimeError> {
let date_stamp: Timestamp = TryFrom::try_from(self)?;
date_stamp.add_interval(span)
}
#[inline]
pub fn add_time(self, time: Time) -> Result<Timestamp, DateTimeError> {
let mut timestamp: Timestamp = TryFrom::try_from(self)?;
if timestamp.is_finite() {
timestamp = timestamp.add_time(time)?;
if !timestamp.is_valid() {
return Err(DateTimeError::overflow());
}
}
Ok(timestamp)
}
#[inline]
pub fn sub_date(self, date: Date) -> Result<i32, DateTimeError> {
if self.is_infinite() || date.is_infinite() {
Err(DateTimeError::invalid(format!(
"sub invalid date, left: {:?} right: {:?}",
self, date
)))
} else {
Ok(self.value() - date.value())
}
}
#[inline]
pub fn sub_days(self, days: i32) -> Result<Date, DateTimeError> {
if self.is_infinite() {
return Ok(self);
}
let result = self.value().checked_sub(days);
match result {
Some(d) => {
if is_valid_date(d) {
Ok(Date::new(d))
} else {
Err(DateTimeError::invalid(format!(
"date sub result: {:?} is invalid",
d
)))
}
}
None => Err(DateTimeError::overflow()),
}
}
#[inline]
pub fn sub_interval(self, span: Interval) -> Result<Timestamp, DateTimeError> {
let date_stamp: Timestamp = TryFrom::try_from(self)?;
date_stamp.sub_interval(span)
}
#[allow(clippy::uninit_assumed_init)]
pub fn try_from_str(s: &str, date_order: DateOrder) -> Result<Self, DateTimeError> {
let s = s.as_bytes();
let mut fields: [&[u8]; MAX_DATE_FIELDS] = unsafe { MaybeUninit::uninit().assume_init() };
let mut work_buf: [u8; MAX_DATE_LEN] = unsafe { MaybeUninit::uninit().assume_init() };
let mut f_types: [TokenField; MAX_DATE_FIELDS] =
unsafe { MaybeUninit::uninit().assume_init() };
let field_num =
parse_date_time(s, &mut work_buf, &mut fields, &mut f_types, MAX_DATE_FIELDS)?;
let mut tm = PgTime::new();
let (_, d_type, _) = decode_date_time(
&fields[0..field_num],
&f_types[0..field_num],
&mut tm,
&mut None,
date_order,
)?;
match d_type {
TokenField::Date => {}
TokenField::Late => return Ok(Date::new(std::i32::MAX)),
TokenField::Early => return Ok(Date::new(std::i32::MIN)),
_ => {
return Err(DateTimeError::invalid(format!(
"date type: {:?} is invalid",
d_type
)))
}
}
if !is_valid_julian(tm.year, tm.mon, tm.mday) {
return Err(DateTimeError::invalid(format!(
"date :{}-{}-{}) is invalid julian date",
tm.year, tm.mon, tm.mday
)));
}
let date = date2julian(tm.year, tm.mon, tm.mday) - POSTGRES_EPOCH_JDATE;
if !is_valid_date(date) {
return Err(DateTimeError::invalid(format!(
"date :{}-{}-{}) is invalid julian date",
tm.year, tm.mon, tm.mday
)));
}
Ok(Date::new(date))
}
#[inline]
pub fn format(
self,
date_style: DateStyle,
date_order: DateOrder,
) -> Result<String, DateTimeError> {
if self.is_infinite() {
format_special_date(self)
} else {
let mut buf = Vec::with_capacity(128);
unsafe { buf.set_len(128) };
let mut tm: PgTime = PgTime::new();
let (y, m, d) = julian2date(self.value() + POSTGRES_EPOCH_JDATE);
tm.set_ymd(y, m, d);
let len = format_date(&tm, buf.as_mut_slice(), date_style, date_order)?;
unsafe {
buf.set_len(len);
Ok(String::from_utf8_unchecked(buf))
}
}
}
}
#[inline]
fn format_special_date(date: Date) -> Result<String, DateTimeError> {
if date.is_begin() {
let ret = unsafe { String::from_utf8_unchecked(EARLY.into()) };
Ok(ret)
} else if date.is_end() {
let ret = unsafe { String::from_utf8_unchecked(LATE.into()) };
Ok(ret)
} else {
Err(DateTimeError::invalid(format!(
"date: {:?} is not valid",
date
)))
}
}
#[inline]
fn format_date(
tm: &PgTime,
s: &mut [u8],
style: DateStyle,
date_order: DateOrder,
) -> Result<usize, DateTimeError> {
debug_assert!(tm.mon >= 1 && tm.mon <= MONTHS_PER_YEAR);
let mut offset = 0;
match style {
DateStyle::ISO | DateStyle::XSD => {
let year = if tm.year > 0 { tm.year } else { -(tm.year - 1) };
offset = pg_ltostr_zeropad(&mut s[offset..], year, 4);
offset = set_one_byte(s, offset, b'-');
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mon, 2);
offset = set_one_byte(s, offset, b'-');
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mday, 2);
}
DateStyle::SQL => {
if date_order == DateOrder::DMY {
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mday, 2);
offset = set_one_byte(s, offset, b'/');
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mon, 2);
} else {
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mon, 2);
offset = set_one_byte(s, offset, b'/');
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mday, 2);
}
offset = set_one_byte(s, offset, b'/');
let year = if tm.year > 0 { tm.year } else { -(tm.year - 1) };
offset += pg_ltostr_zeropad(&mut s[offset..], year, 4);
}
DateStyle::German => {
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mday, 2);
offset = set_one_byte(s, offset, b'.');
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mon, 2);
offset = set_one_byte(s, offset, b'.');
let year = if tm.year > 0 { tm.year } else { -(tm.year - 1) };
offset += pg_ltostr_zeropad(&mut s[offset..], year, 4);
}
_ => {
if date_order == DateOrder::DMY {
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mday, 2);
offset = set_one_byte(s, offset, b'-');
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mon, 2);
} else {
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mon, 2);
offset = set_one_byte(s, offset, b'.');
offset += pg_ltostr_zeropad(&mut s[offset..], tm.mday, 2);
}
offset = set_one_byte(s, offset, b'.');
let year = if tm.year > 0 { tm.year } else { -(tm.year - 1) };
offset += pg_ltostr_zeropad(&mut s[offset..], year, 4);
}
}
if tm.year <= 0 {
offset = copy_slice(s, offset, b" BC");
}
Ok(offset)
}
impl From<Date> for i32 {
#[inline]
fn from(d: Date) -> Self {
d.value()
}
}
impl From<i32> for Date {
#[inline]
fn from(v: i32) -> Self {
Date::new(v)
}
}
impl TryFrom<Timestamp> for Date {
type Error = DateTimeError;
#[inline]
fn try_from(value: Timestamp) -> Result<Self, Self::Error> {
if value == DATE_TIME_BEGIN_VAL {
Ok(DATE_BEGIN_VAL)
} else if value == DATE_TIME_END_VAL {
Ok(DATE_END_VAL)
} else {
let mut tm: PgTime = PgTime::new();
let mut fsec = 0;
timestamp2time(value.value(), &None, &mut tm, &mut fsec, &mut None, &None)?;
let result = date2julian(tm.year, tm.mon, tm.mday) - POSTGRES_EPOCH_JDATE;
Ok(Date::new(result))
}
}
}
pub const DATE_BEGIN_VAL: Date = Date::new(std::i32::MIN);
pub const DATE_END_VAL: Date = Date::new(std::i32::MAX);
impl DateTime for Date {
#[inline]
fn date_part(&self, ty: FieldType, unit: DateUnit) -> Result<Option<f64>, DateTimeError> {
let timestamp: Timestamp = TryFrom::try_from(*self)?;
timestamp.date_part(ty, unit)
}
#[inline]
fn is_finite(&self) -> bool {
!self.is_begin() && !self.is_end()
}
#[inline]
fn truncate(&self, ty: FieldType, unit: DateUnit) -> Result<Self, DateTimeError> {
let timestamp: Timestamp = TryFrom::try_from(*self)?;
let tp = timestamp.truncate(ty, unit)?;
TryFrom::try_from(tp)
}
}
#[cfg(test)]
mod tests {
use crate::date::Date;
use crate::error::DateTimeError;
use crate::token::MAX_DATE_FIELDS;
use crate::{DateOrder, DateStyle, DateTime, DateUnit, FieldType};
#[test]
fn tets_date_from_str() -> Result<(), DateTimeError> {
let ret = Date::try_from_str("y2.3", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str(" 2003-04-12 dow 123456-2", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str(" 2003-04-12 t 123456/2", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str(" 2003-04-12 t 123456-2", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str(" 2003-04-12 04:05:06 t 123456-2", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str(" 2003-04-12 04:05:06 America/New_Yor", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str(" 2003-04-12 04:05:06 America/New_Yor", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str("1999-7--7-08", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str("1999-at-08", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str("1999-jan-jan", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str("1999-Jqn-08", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str("20000000000000000000", DateOrder::MDY);
assert!(ret.is_err());
let ret = Date::try_from_str("j1999-3-4:3:5:5", DateOrder::MDY);
assert!(ret.is_err());
let mut str = Vec::with_capacity(1);
str.push(5);
let s = String::from_utf8(str);
assert!(s.is_ok());
let date = Date::try_from_str(s.unwrap().as_str(), DateOrder::MDY);
assert!(date.is_err());
let mut str = Vec::with_capacity((MAX_DATE_FIELDS + 1) * 2);
for i in 0..(MAX_DATE_FIELDS + 1) * 2 {
if i % 2 == 0 {
str.push(b'7');
} else {
str.push(b' ');
}
}
let s = String::from_utf8(str);
assert!(s.is_ok());
let date = Date::try_from_str(s.unwrap().as_str(), DateOrder::MDY);
assert!(date.is_err());
let date = Date::try_from_str("", DateOrder::MDY);
assert!(date.is_err());
let date = Date::try_from_str(" +", DateOrder::MDY);
assert!(date.is_err());
let date = Date::try_from_str("-/", DateOrder::MDY);
assert!(date.is_err());
let date = Date::try_from_str("1988/", DateOrder::MDY);
assert!(date.is_err());
let date: Date = Date::try_from_str(" 990619 ", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-06-19".to_string());
let date: Date = Date::try_from_str("1999-03-31 123456", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-03-31".to_string());
let date: Date = Date::try_from_str("1999-03-31 1234", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-03-31".to_string());
let date: Date = Date::try_from_str("-infinity", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "-infinity");
let date: Date = Date::try_from_str("infinity", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "infinity");
let date: Date = Date::try_from_str("1999-01-08", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let s = date.format(DateStyle::Postgres, DateOrder::YMD)?;
assert_eq!(s, "01.08.1999".to_string());
let s = date.format(DateStyle::SQL, DateOrder::YMD)?;
assert_eq!(s, "01/08/1999".to_string());
let s = date.format(DateStyle::German, DateOrder::YMD)?;
assert_eq!(s, "08.01.1999");
let s = date.format(DateStyle::XSD, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("January 8, 1999", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("1/8/1999", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("1/18/1999", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-18".to_string());
let date: Date = Date::try_from_str("01/02/03", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "2003-01-02".to_string());
let date: Date = Date::try_from_str("1999-Jan-08", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("Jan-08-1999", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("08-Jan-1999", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("99-Jan-08", DateOrder::YMD)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let ret = Date::try_from_str("99-Jan-08", DateOrder::DMY);
assert!(ret.is_err());
let ret = Date::try_from_str("99-Jan-08", DateOrder::MDY);
assert!(ret.is_err());
let date: Date = Date::try_from_str("08-Jan-99", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("08-Jan-99", DateOrder::DMY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let ret = Date::try_from_str("08-Jan-99", DateOrder::YMD);
assert!(ret.is_err());
let date: Date = Date::try_from_str("Jan-08-99", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("Jan-08-99", DateOrder::DMY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let ret = Date::try_from_str("Jan-08-99", DateOrder::YMD);
assert!(ret.is_err());
let date: Date = Date::try_from_str("19990108", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("990108", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("1999.008", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("J2451187", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "1999-01-08".to_string());
let date: Date = Date::try_from_str("January 8, 99 BC", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "0099-01-08 BC".to_string());
let ret = Date::try_from_str("-*u1981", DateOrder::MDY);
assert!(ret.is_err());
let date = Date::try_from_str("test 8, 1999", DateOrder::MDY);
assert!(date.is_err());
Ok(())
}
#[test]
fn test_date_range() -> Result<(), DateTimeError> {
let date: Date = Date::try_from_str("January 1, 4713 BC", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "4713-01-01 BC".to_string());
let date: Date = Date::try_from_str("5874897-12-31", DateOrder::MDY)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "5874897-12-31".to_string());
let date = Date::try_from_str("January 1, 4714 BC", DateOrder::MDY);
assert!(date.is_err());
let date = Date::try_from_str("5874898-2-1", DateOrder::MDY);
assert!(date.is_err());
Ok(())
}
#[test]
fn test_try_from_ymd() -> Result<(), DateTimeError> {
let date = Date::try_from_ymd(2020, 12, 23)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "2020-12-23");
let date = Date::try_from_ymd(-2020, 12, 23)?;
let s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!(s, "2020-12-23 BC");
let ret = Date::try_from_ymd(std::i32::MAX, std::i32::MAX, std::i32::MAX);
assert!(ret.is_err());
let ret = Date::try_from_ymd(5874899, 0, 0);
assert!(ret.is_err());
Ok(())
}
#[test]
fn test_date_date_part() -> Result<(), DateTimeError> {
let date = Date::try_from_str("2116-9-10", DateOrder::YMD)?;
let ret = date.date_part(FieldType::Unit, DateUnit::Millennium)?;
assert_eq!(ret, Some(3.0));
let ret = date.date_part(FieldType::Unit, DateUnit::Century)?;
assert_eq!(ret, Some(22.0));
let ret = date.date_part(FieldType::Unit, DateUnit::Decade)?;
assert_eq!(ret, Some(211.0));
let ret = date.date_part(FieldType::Unit, DateUnit::Year)?;
assert_eq!(ret, Some(2116.0));
Ok(())
}
#[test]
fn test_date_trunc() -> Result<(), DateTimeError> {
let date = Date::try_from_str("2116-9-10", DateOrder::YMD)?;
let ret = date.truncate(FieldType::Unit, DateUnit::Millennium)?;
assert_eq!(ret.format(DateStyle::ISO, DateOrder::YMD)?, "2001-01-01");
let ret = date.truncate(FieldType::Unit, DateUnit::Century)?;
assert_eq!(ret.format(DateStyle::ISO, DateOrder::YMD)?, "2101-01-01");
let ret = date.truncate(FieldType::Unit, DateUnit::Decade)?;
assert_eq!(ret.format(DateStyle::ISO, DateOrder::YMD)?, "2110-01-01");
let ret = date.truncate(FieldType::Unit, DateUnit::Year)?;
assert_eq!(ret.format(DateStyle::ISO, DateOrder::YMD)?, "2116-01-01");
let ret = date.truncate(FieldType::Unit, DateUnit::Quarter)?;
assert_eq!(ret.format(DateStyle::ISO, DateOrder::YMD)?, "2116-07-01");
let ret = date.truncate(FieldType::Unit, DateUnit::Month)?;
assert_eq!(ret.format(DateStyle::ISO, DateOrder::YMD)?, "2116-09-01");
let ret = date.truncate(FieldType::Unit, DateUnit::Day)?;
assert_eq!(ret.format(DateStyle::ISO, DateOrder::YMD)?, "2116-09-10");
Ok(())
}
#[test]
fn test_date_finite() -> Result<(), DateTimeError> {
let date = Date::try_from_str("2116-9-10", DateOrder::YMD)?;
assert!(date.is_finite());
let date = Date::new(std::i32::MIN);
assert!(date.is_infinite());
Ok(())
}
#[test]
fn test_date_to_str() -> Result<(), DateTimeError> {
let date = Date::try_from_ymd(2000, 10, 1)?;
let date_s = date.format(DateStyle::ISO, DateOrder::YMD)?;
assert_eq!("2000-10-01".to_string(), date_s);
Ok(())
}
}