use std::{
fmt::{Display, Formatter},
sync::LazyLock,
};
pub const GENERATED_DATE_EPOCH_OFFSET: i32 = 83966;
pub const MIN_GENERATE_DATE: i32 = 92001;
const CURRENT_DATE: i32 = 95168;
pub const TOTAL_DATE_RANGE: i32 = 2557;
const MONTH_YEAR_DAY_START: [i32; 13] =
[0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365];
static DATE_TO_STRING: LazyLock<Vec<String>> = LazyLock::new(make_date_string_index);
static JULIAN_DATE: LazyLock<Vec<i32>> = LazyLock::new(|| {
(0..TOTAL_DATE_RANGE)
.map(|date| julian(date + MIN_GENERATE_DATE))
.collect()
});
pub struct GenerateUtils;
impl GenerateUtils {
pub fn calculate_row_count(
scale_base: i32,
scale_factor: f64,
part: i32,
part_count: i32,
) -> i64 {
let total_row_count = (scale_base as f64 * scale_factor) as i64;
let mut row_count = total_row_count / part_count as i64;
if part == part_count {
row_count += total_row_count % part_count as i64;
}
row_count
}
pub fn calculate_start_index(
scale_base: i32,
scale_factor: f64,
part: i32,
part_count: i32,
) -> i64 {
let total_row_count = (scale_base as f64 * scale_factor) as i64;
let rows_per_part = total_row_count / part_count as i64;
rows_per_part * (part as i64 - 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct TPCHDate {
date_index: i32,
}
impl Display for TPCHDate {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", &DATE_TO_STRING[self.date_index as usize])
}
}
impl TPCHDate {
pub const UNIX_EPOCH_OFFSET: i32 = 8035;
pub fn new(generated_date: i32) -> Self {
Self {
date_index: generated_date - MIN_GENERATE_DATE,
}
}
pub fn to_ymd(&self) -> (i32, i32, i32) {
to_ymd(self.date_index + 1)
}
pub fn into_inner(self) -> i32 {
self.date_index
}
pub fn is_in_past(date: i32) -> bool {
Self::to_julian(date) <= CURRENT_DATE
}
fn to_julian(date: i32) -> i32 {
JULIAN_DATE[(date - MIN_GENERATE_DATE) as usize]
}
#[inline(always)]
pub fn to_unix_epoch(&self) -> i32 {
self.date_index + Self::UNIX_EPOCH_OFFSET
}
}
fn make_date_string_index() -> Vec<String> {
(0..TOTAL_DATE_RANGE)
.map(|i| {
let (y, m, dy) = to_ymd(i + 1);
format_ymd(y, m, dy)
})
.collect()
}
fn to_ymd(index: i32) -> (i32, i32, i32) {
let y = julian(index + MIN_GENERATE_DATE - 1) / 1000;
let d = julian(index + MIN_GENERATE_DATE - 1) % 1000;
let mut m = 0;
while d > MONTH_YEAR_DAY_START[m as usize] + leap_year_adjustment(y, m) {
m += 1;
}
let dy =
d - MONTH_YEAR_DAY_START[(m - 1) as usize] - if is_leap_year(y) && m > 2 { 1 } else { 0 };
(y, m, dy)
}
fn format_ymd(y: i32, m: i32, dy: i32) -> String {
format!("{:04}-{:02}-{:02}", 1900 + y, m, dy)
}
const fn julian(date: i32) -> i32 {
let mut offset = date - MIN_GENERATE_DATE;
let mut result = MIN_GENERATE_DATE;
loop {
let year = result / 1000;
let year_end = year * 1000 + 365 + if is_leap_year(year) { 1 } else { 0 };
if result + offset <= year_end {
break;
}
offset -= year_end - result + 1;
result += 1000;
}
result + offset
}
const fn is_leap_year(year: i32) -> bool {
year % 4 == 0 && year % 100 != 0
}
const fn leap_year_adjustment(year: i32, month: i32) -> i32 {
if is_leap_year(year) && month >= 2 {
1
} else {
0
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_date_strings() {
let date = TPCHDate::new(MIN_GENERATE_DATE + 1);
assert_eq!(date.to_string(), "1992-01-02");
let date = TPCHDate::new(MIN_GENERATE_DATE + 1234);
assert_eq!(date.to_string(), "1995-05-19");
let date = TPCHDate::new(MIN_GENERATE_DATE + TOTAL_DATE_RANGE - 1);
assert_eq!(date.to_string(), "1998-12-31");
}
#[test]
fn test_display_dates() {
for index in [1, 23, 321, 623, 1234, 2345, 2556] {
let date = TPCHDate::new(MIN_GENERATE_DATE + index);
let (y, m, dy) = date.to_ymd();
assert_eq!(format_ymd(y, m, dy), date.to_string());
}
}
#[test]
fn test_date_epoch_consistency() {
let date = TPCHDate::new(MIN_GENERATE_DATE + 1);
assert_eq!(date.to_unix_epoch(), 8036);
let date = TPCHDate::new(MIN_GENERATE_DATE + 1234);
assert_eq!(date.to_string(), "1995-05-19");
assert_eq!(date.to_unix_epoch(), 9269);
}
}