use crate::error::DateTimeError;
use crate::timestamp::is_valid_timestamp;
use crate::token::*;
#[inline]
pub fn copy_slice(dst: &mut [u8], offset: usize, src: &[u8]) -> usize {
debug_assert!(dst.len() - offset >= src.len());
let end = offset + src.len();
let d = &mut dst[offset..end];
d.copy_from_slice(src);
end
}
#[inline]
pub fn set_one_byte(dst: &mut [u8], offset: usize, src: u8) -> usize {
debug_assert!(dst.len() - offset >= 1);
dst[offset] = src;
offset + 1
}
#[inline]
pub fn date2julian(y: i32, m: i32, d: i32) -> i32 {
let (y, m) = if m > 2 {
(y + 4800, m + 1)
} else {
(y + 4799, m + 13)
};
let century = y / 100;
let mut julian = y * 365 - 32167;
julian += y / 4 - century + century / 4;
julian += 7834 * m / 256 + d;
julian
}
#[inline]
pub fn julian2date(jd: i32) -> (i32, i32, i32) {
let mut julian = jd as u32 + 32044;
let mut quad = julian / 146_097;
let extra = (julian - quad * 146_097) * 4 + 3;
julian += 60 + quad * 3 + extra / 146_097;
quad = julian / 1461;
julian -= quad * 1461;
let mut y: i32 = (julian * 4 / 1461) as i32;
julian = if y != 0 {
(julian + 305) % 365 + 123
} else {
(julian + 306) % 366 + 123
};
y += (quad * 4) as i32;
let year = y - 4800;
quad = julian * 2141 / 65_536;
let day = julian - 7834 * quad / 256;
let month = (quad + 10) % MONTHS_PER_YEAR as u32 + 1;
(year, month as i32, day as i32)
}
#[derive(Debug)]
pub struct PgTime {
pub sec: i32,
pub min: i32,
pub hour: i32,
pub mday: i32,
pub mon: i32,
pub year: i32,
pub wday: i32,
pub yday: i32,
pub isdst: i32,
pub gmtoff: i64,
pub zone: Option<&'static [u8]>,
}
impl PgTime {
#[inline]
pub const fn new() -> Self {
Self {
sec: 0,
min: 0,
hour: 0,
mday: 0,
mon: 0,
year: 0,
wday: 0,
yday: 0,
isdst: -1,
gmtoff: 0,
zone: None,
}
}
#[inline]
pub fn set_ymd(&mut self, y: i32, m: i32, d: i32) {
self.year = y;
self.mon = m;
self.mday = d;
}
#[inline]
pub fn truncate_timestamp_week(&mut self, fsec: &mut i64) {
let woy = date_to_iso_week(self.year, self.mon, self.mday);
if woy >= 52 && self.mon == 1 {
self.year -= 1;
}
if woy <= 1 && self.mon == MONTHS_PER_YEAR {
self.year -= 1;
}
iso_week_to_date(woy, self);
self.hour = 0;
self.min = 0;
self.sec = 0;
*fsec = 0;
}
#[inline]
pub fn truncate_micro_sec(&mut self, _fsec: &mut i64) {}
#[inline]
pub fn truncate_milli_sec(&mut self, fsec: &mut i64) {
*fsec = (*fsec / 1000) * 1000;
self.truncate_micro_sec(fsec);
}
#[inline]
pub fn truncate_sec(&mut self, fsec: &mut i64) {
*fsec = 0;
self.truncate_milli_sec(fsec);
}
#[inline]
pub fn truncate_minute(&mut self, fsec: &mut i64) {
self.sec = 0;
self.truncate_sec(fsec);
}
#[inline]
pub fn truncate_hour(&mut self, fsec: &mut i64) {
self.min = 0;
self.truncate_minute(fsec);
}
#[inline]
pub fn truncate_day(&mut self, fsec: &mut i64) {
self.hour = 0;
self.truncate_hour(fsec);
}
#[inline]
pub fn truncate_interval_month(&mut self, fsec: &mut i64) {
self.mday = 0;
self.truncate_day(fsec);
}
#[inline]
pub fn truncate_timestamp_month(&mut self, fsec: &mut i64) {
self.mday = 1;
self.truncate_day(fsec);
}
#[inline]
pub fn truncate_interval_quarter(&mut self, fsec: &mut i64) {
self.mon = 3 * (self.mon / 3);
self.truncate_interval_month(fsec);
}
#[inline]
pub fn truncate_timestamp_quarter(&mut self, fsec: &mut i64) {
self.mon = (3 * ((self.mon - 1) / 3)) + 1;
self.truncate_timestamp_month(fsec);
}
#[inline]
pub fn truncate_interval_year(&mut self, fsec: &mut i64) {
self.mon = 0;
self.truncate_interval_quarter(fsec);
}
#[inline]
pub fn truncate_timestamp_year(&mut self, fsec: &mut i64) {
self.mon = 1;
self.truncate_timestamp_quarter(fsec);
}
#[inline]
pub fn truncate_interval_decade(&mut self, fsec: &mut i64) {
self.year = (self.year / 10) * 10;
self.truncate_interval_year(fsec)
}
#[inline]
pub fn truncate_timestamp_decade(&mut self, fsec: &mut i64) {
if self.year > 0 {
self.year = (self.year / 10) * 10;
} else {
self.year = -((8 - (self.year - 1)) / 10) * 10;
}
self.truncate_timestamp_year(fsec)
}
#[inline]
pub fn truncate_interval_century(&mut self, fsec: &mut i64) {
self.year = (self.year / 100) * 100;
self.truncate_interval_decade(fsec);
}
#[inline]
pub fn truncate_timestamp_century(&mut self, fsec: &mut i64) {
if self.year > 0 {
self.year = ((self.year + 99) / 100) * 100 - 99;
} else {
self.year = -((99 - (self.year - 1)) / 100) * 100 + 1;
}
self.truncate_timestamp_year(fsec);
}
#[inline]
pub fn truncate_interval_millennium(&mut self, fsec: &mut i64) {
self.year = (self.year / 1000) * 1000;
self.truncate_interval_century(fsec);
}
#[inline]
pub fn truncate_timestamp_millennium(&mut self, fsec: &mut i64) {
if self.year > 0 {
self.year = ((self.year + 999) / 1000) * 1000 - 999;
} else {
self.year = -((999 - (self.year - 1)) / 1000) * 1000 + 1;
}
self.truncate_timestamp_century(fsec);
}
pub fn validate_date(
&mut self,
f_mask: i32,
is_julian: bool,
is2digits: bool,
bc: bool,
) -> Result<(), DateTimeError> {
if (f_mask & TokenType::YEAR.mask()) != 0 {
if is_julian {
} else if bc {
if self.year <= 0 {
return Err(DateTimeError::overflow());
}
self.year = -(self.year - 1);
} else if is2digits {
if self.year < 0 {
return Err(DateTimeError::overflow());
}
if self.year < 70 {
self.year += 2000;
} else if self.year < 100 {
self.year += 1900;
}
} else {
if self.year <= 0 {
return Err(DateTimeError::overflow());
}
}
}
if (f_mask & TokenType::DOY.mask()) != 0 {
let (y, m, d) = julian2date(date2julian(self.year, 1, 1) + self.yday - 1);
self.set_ymd(y, m, d);
}
if (f_mask & TokenType::MONTH.mask()) != 0 && (self.mon < 1 || self.mon > MONTHS_PER_YEAR) {
return Err(DateTimeError::overflow());
}
if (f_mask & TokenType::DAY.mask()) != 0 && (self.mday < 1 || self.mday > 31) {
return Err(DateTimeError::overflow());
}
if (f_mask & DTK_DATE_M) == DTK_DATE_M && self.mday > days_of_month(self.year, self.mon) {
return Err(DateTimeError::overflow());
}
Ok(())
}
pub fn to_timestamp(&self, fsec: i64, tz: Option<i32>) -> Result<i64, DateTimeError> {
if !is_valid_julian(self.year, self.mon, self.mday) {
return Err(DateTimeError::invalid(format!(
"date: ({}, {}, {}) is not valid julian date",
self.year, self.mon, self.mday
)));
}
let date = date2julian(self.year, self.mon, self.mday) - date2julian(2000, 1, 1);
let time = time2t(self.hour, self.min, self.sec, fsec);
let result = match (date as i64).checked_mul(USECS_PER_DAY) {
Some(v) => v,
None => return Err(DateTimeError::overflow()),
};
let mut result = match result.checked_add(time) {
Some(v) => v,
None => return Err(DateTimeError::overflow()),
};
if let Some(z) = tz {
result = datetime2local(result, -z);
}
if !is_valid_timestamp(result) {
return Err(DateTimeError::invalid(format!(
"timestamp: {:?} is not valid",
result
)));
}
Ok(result)
}
}
#[inline]
fn time2t(hour: i32, min: i32, sec: i32, fsec: i64) -> i64 {
(((((hour * MINS_PER_HOUR) + min) * SECS_PER_MINUTE) as i64 + sec as i64)
* USECS_PER_SEC as i64)
+ fsec
}
#[inline]
fn datetime2local(datetime: i64, tz: i32) -> i64 {
datetime - tz as i64 * USECS_PER_SEC
}
#[inline]
pub fn julian_to_week_day(date: i32) -> i32 {
let mut date = date + 1;
date %= 7;
if date < 0 {
date += 7;
}
date
}
#[inline]
pub fn date_to_iso_week(year: i32, mon: i32, mday: i32) -> i32 {
let dayn = date2julian(year, mon, mday);
let mut day4 = date2julian(year, 1, 4);
let mut day0 = julian_to_week_day(day4 - 1);
if dayn < day4 - day0 {
day4 = date2julian(year - 1, 1, 4);
day0 = julian_to_week_day(day4 - 1);
}
let mut result = (dayn - (day4 - day0)) / 7 + 1;
if result >= 52 {
day4 = date2julian(year + 1, 1, 4);
day0 = julian_to_week_day(day4 - 1);
if dayn >= day4 - day0 {
result = (dayn - (day4 - day0)) / 7 + 1;
}
}
result
}
#[inline]
fn iso_week_to_julian(year: i32, week: i32) -> i32 {
let day4 = date2julian(year, 1, 4);
let day0 = julian_to_week_day(day4 - 1);
((week - 1) * 7) + (day4 - day0)
}
#[inline]
fn iso_week_to_date(woy: i32, tm: &mut PgTime) {
let week_julian = iso_week_to_julian(tm.year, woy);
let (y, m, d) = julian2date(week_julian);
tm.set_ymd(y, m, d);
}
#[inline]
pub fn date_to_iso_year(year: i32, mon: i32, mday: i32) -> i32 {
let mut year = year;
let dayn = date2julian(year, mon, mday);
let mut day4 = date2julian(year, 1, 4);
let mut day0 = julian_to_week_day(day4 - 1);
if dayn < day4 - day0 {
day4 = date2julian(year - 1, 1, 4);
day0 = julian_to_week_day(day4 - 1);
year -= 1;
}
let result = (dayn - (day4 - day0)) / 7 + 1;
if result >= 52 {
day4 = date2julian(year + 1, 1, 4);
day0 = julian_to_week_day(day4 - 1);
if dayn >= day4 - day0 {
year += 1;
}
}
year
}
#[inline]
fn year_is_leap(y: i32) -> bool {
y % 4 == 0 && ((y % 100) != 0 || (y % 400) == 0)
}
#[inline]
pub fn days_of_month(y: i32, m: i32) -> i32 {
const DAY_TABLE: [[i32; 13]; 2] = [
[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0],
[31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 0],
];
DAY_TABLE[year_is_leap(y) as usize][m as usize - 1]
}
const JULIAN_MIN_YEAR: i32 = -4713;
const JULIAN_MIN_MONTH: i32 = 11;
const JULIAN_MAX_YEAR: i32 = 5_874_898;
const JULIAN_MAX_MONTH: i32 = 6;
pub const POSTGRES_EPOCH_JDATE: i32 = 2_451_545;
#[inline]
pub fn is_valid_julian(y: i32, m: i32, _d: i32) -> bool {
(y > JULIAN_MIN_YEAR || (y == JULIAN_MIN_YEAR && ((m) >= JULIAN_MIN_MONTH)))
&& (y < JULIAN_MAX_YEAR || (y == JULIAN_MAX_YEAR && (m < JULIAN_MAX_MONTH)))
}
#[inline]
pub fn time_modulo(t: i64, unit: i64) -> (i64, i64) {
let q = t / unit;
if q != 0 {
let left = t - q * unit;
(left, q)
} else {
(t, q)
}
}
#[inline]
pub fn timestamp_round(t: f64) -> f64 {
const TS_PREC_INV: f64 = 1_000_000.0;
(t * TS_PREC_INV).round() / TS_PREC_INV
}
#[inline]
pub fn get_month_name(month: i32) -> &'static [u8] {
const MONTHS: [&[u8]; 13] = [
b"Jan", b"Feb", b"Mar", b"Apr", b"May", b"Jun", b"Jul", b"Aug", b"Sep", b"Oct", b"Nov",
b"Dec", b"Unknow",
];
MONTHS[month as usize - 1]
}
#[inline]
pub const fn get_wday_name(wday: i32) -> &'static [u8] {
const DAYS: [&[u8]; 8] = [
b"Sun", b"Mon", b"Tue", b"Wed", b"Thu", b"Fri", b"Sat", b"unknow",
];
DAYS[wday as usize]
}
pub const DAYS_PER_YEAR: f32 = 365.25; pub const MONTHS_PER_YEAR: i32 = 12;
pub const DAYS_PER_MONTH: i32 = 30; pub const HOURS_PER_DAY: i32 = 24;
#[allow(dead_code)]
pub const SECS_PER_YEAR: i64 = 36525 * 864; pub const SECS_PER_DAY: i32 = 86400;
pub const SECS_PER_HOUR: i32 = 3600;
pub const SECS_PER_MINUTE: i32 = 60;
pub const MINS_PER_HOUR: i32 = 60;
pub const USECS_PER_DAY: i64 = 86_400_000_000i64;
pub const USECS_PER_HOUR: i64 = 3_600_000_000i64;
pub const USECS_PER_MINUTE: i64 = 60_000_000i64;
pub const USECS_PER_SEC: i64 = 1_000_000i64;
#[allow(dead_code)]
pub const MAX_TZDISP_HOUR: i32 = 15;
pub const MAX_DATE_LEN: usize = 128;
pub const MAX_TIME_PRECISION: i32 = 6;
pub const MAX_TIMESTAMP_PRECISION: usize = 6;
pub const MAX_INTERVAL_PRECISION: usize = 6;
#[inline]
pub fn pg_ltostr(s: &mut [u8], value: i32) -> usize {
let mut start = 0;
let mut new_start = 0;
let mut value = value;
if value < 0 {
s[0] = b'-';
start = 1;
let mut value = value;
new_start = start;
loop {
let old_val = value;
value /= 10;
let remainder = old_val - value * 10;
s[new_start] = (b'0' as i8 - remainder as i8) as u8;
if value == 0 {
break;
}
new_start += 1;
}
} else {
loop {
let old_val = value;
value /= 10;
let remainder = old_val - value * 10;
s[new_start] = b'0' + remainder as u8;
if value == 0 {
break;
}
new_start += 1;
}
}
let end = new_start + 1;
while start < new_start {
s.swap(start, new_start);
new_start -= 1;
start += 1;
}
end
}
#[inline]
pub fn pg_ltostr_zeropad(s: &mut [u8], value: i32, min_width: usize) -> usize {
let mut start = 0;
let end = min_width;
let mut num = value;
let mut min_width = min_width;
debug_assert!(min_width > 0);
if num < 0 {
s[0] = b'-';
start += 1;
min_width -= 1;
while min_width > 0 {
min_width -= 1;
let old_val = num;
num /= 10;
let remainder = old_val - num * 10;
s[start + min_width] = b'0' - remainder as u8;
}
} else {
while min_width > 0 {
min_width -= 1;
let oldval = num;
num /= 10;
let remainder = oldval - num * 10;
s[min_width] = b'0' + remainder as u8;
}
}
if num != 0 {
return pg_ltostr(s, value);
}
end as usize
}
#[inline]
pub fn append_seconds(
s: &mut [u8],
sec: i32,
fsec: i64,
precision: i32,
fill_zeros: bool,
) -> usize {
debug_assert!(precision >= 0);
let mut offset = if fill_zeros {
pg_ltostr_zeropad(s, sec.abs(), 2)
} else {
pg_ltostr(s, sec.abs())
};
if fsec != 0 {
let mut value = fsec.abs();
let mut got_nonzero = false;
s[offset] = b'.';
offset += 1;
let mut precision = precision;
let mut valid_num = 0;
while precision > 0 {
precision -= 1;
let old_val = value;
value /= 10;
let remainder = old_val - value * 10;
if remainder != 0 {
got_nonzero = true;
}
if got_nonzero {
s[offset + precision as usize] = (b'0' + remainder as u8) as u8;
valid_num += 1;
}
}
if value != 0 {
return pg_ltostr(&mut s[offset..], fsec.abs() as i32);
}
offset += valid_num;
offset
} else {
offset
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pg_ltostr() -> Result<(), DateTimeError> {
let value = -123456;
let mut s: [u8; 20] = [0; 20];
let ret = pg_ltostr(&mut s, value);
assert_eq!(&s[0..ret], b"-123456");
Ok(())
}
}