use chrono::{NaiveDate, NaiveDateTime, Datelike, Weekday};
use trade_date_a;
pub struct TradingCalendar;
impl TradingCalendar {
pub fn is_trading_day(date: &NaiveDate) -> bool {
let date_i64 = Self::date_to_i64(date);
trade_date_a::is_work_day(date_i64)
}
pub fn is_trading_time(datetime: &NaiveDateTime) -> bool {
if !Self::is_trading_day(&datetime.date()) {
return false;
}
let seconds = datetime.timestamp() % 86400; seconds >= 34200 && seconds < 54000
}
pub fn previous_trading_day(date: &NaiveDate) -> NaiveDate {
let mut current = *date - chrono::Duration::days(1);
for _ in 0..100 {
if Self::is_trading_day(¤t) {
return current;
}
current = current - chrono::Duration::days(1);
}
*date - chrono::Duration::days(1)
}
pub fn next_trading_day(date: &NaiveDate) -> NaiveDate {
let mut current = *date + chrono::Duration::days(1);
for _ in 0..100 {
if Self::is_trading_day(¤t) {
return current;
}
current = current + chrono::Duration::days(1);
}
*date
}
pub fn get_trading_days(start_date: &NaiveDate, count: usize) -> Vec<NaiveDate> {
let mut result = Vec::with_capacity(count);
let mut current = *start_date;
if !Self::is_trading_day(¤t) {
current = Self::next_trading_day(¤t);
}
while result.len() < count {
if Self::is_trading_day(¤t) {
result.push(current);
}
current = current + chrono::Duration::days(1);
}
result
}
pub fn get_trading_days_before(end_date: &NaiveDate, count: usize) -> Vec<NaiveDate> {
let mut result = Vec::with_capacity(count);
let mut current = *end_date;
if !Self::is_trading_day(¤t) {
current = Self::previous_trading_day(¤t);
}
while result.len() < count {
if Self::is_trading_day(¤t) {
result.push(current);
}
if current <= NaiveDate::from_ymd_opt(2000, 1, 1).unwrap() {
break;
}
current = current - chrono::Duration::days(1);
}
result.reverse();
result
}
pub fn count_trading_days(start_date: &NaiveDate, end_date: &NaiveDate) -> usize {
if start_date > end_date {
return 0;
}
let mut count = 0;
let mut current = *start_date;
while current <= *end_date {
if Self::is_trading_day(¤t) {
count += 1;
}
current = current + chrono::Duration::days(1);
}
count
}
pub fn current_trading_day() -> NaiveDate {
let today = chrono::Local::now().naive_local().date();
if Self::is_trading_day(&today) {
today
} else {
Self::next_trading_day(&today)
}
}
pub fn market_trading_day() -> NaiveDate {
let date_i64 = trade_date_a::get_market_day((9, 30));
let year = (date_i64 / 10000) as i32;
let month = ((date_i64 % 10000) / 100) as u32;
let day = (date_i64 % 100) as u32;
NaiveDate::from_ymd_opt(year, month, day).unwrap()
}
fn date_to_i64(date: &NaiveDate) -> i64 {
(date.year() * 10000 + date.month() as i32 * 100 + date.day() as i32) as i64
}
fn i64_to_date(date: i64) -> NaiveDate {
let year = (date / 10000) as i32;
let month = ((date % 10000) / 100) as u32;
let day = (date % 100) as u32;
NaiveDate::from_ymd_opt(year, month, day)
.unwrap_or_else(|| NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())
}
#[cfg(test)]
fn date_to_i32(date: &NaiveDate) -> i32 {
date.year() * 10000 + date.month() as i32 * 100 + date.day() as i32
}
#[cfg(test)]
fn i32_to_date(date: i32) -> NaiveDate {
let year = date / 10000;
let month = (date % 10000) / 100;
let day = date % 100;
NaiveDate::from_ymd_opt(year, month as u32, day as u32)
.unwrap_or_else(|| NaiveDate::from_ymd_opt(1970, 1, 1).unwrap())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_trading_day() {
let monday = NaiveDate::from_ymd_opt(2025, 1, 6).unwrap(); assert!(TradingCalendar::is_trading_day(&monday));
let saturday = NaiveDate::from_ymd_opt(2025, 1, 4).unwrap(); assert!(!TradingCalendar::is_trading_day(&saturday));
let sunday = NaiveDate::from_ymd_opt(2025, 1, 5).unwrap(); assert!(!TradingCalendar::is_trading_day(&sunday));
}
#[test]
fn test_is_trading_time() {
let dt1 = NaiveDateTime::parse_from_str("2025-01-06 10:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
assert!(TradingCalendar::is_trading_time(&dt1));
let dt2 = NaiveDateTime::parse_from_str("2025-01-06 14:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
assert!(TradingCalendar::is_trading_time(&dt2));
let dt3 = NaiveDateTime::parse_from_str("2025-01-06 09:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
assert!(!TradingCalendar::is_trading_time(&dt3));
let dt4 = NaiveDateTime::parse_from_str("2025-01-06 16:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
assert!(!TradingCalendar::is_trading_time(&dt4));
let dt5 = NaiveDateTime::parse_from_str("2025-01-04 10:00:00", "%Y-%m-%d %H:%M:%S").unwrap();
assert!(!TradingCalendar::is_trading_time(&dt5));
}
#[test]
fn test_previous_trading_day() {
let tuesday = NaiveDate::from_ymd_opt(2025, 1, 7).unwrap(); let prev = TradingCalendar::previous_trading_day(&tuesday);
let expected = NaiveDate::from_ymd_opt(2025, 1, 6).unwrap();
assert_eq!(prev, expected);
assert!(TradingCalendar::is_trading_day(&expected));
let saturday = NaiveDate::from_ymd_opt(2025, 1, 4).unwrap(); let prev_sat = TradingCalendar::previous_trading_day(&saturday);
assert_ne!(prev_sat, saturday);
}
#[test]
fn test_next_trading_day() {
let friday = NaiveDate::from_ymd_opt(2025, 1, 3).unwrap();
let next = TradingCalendar::next_trading_day(&friday);
let expected = NaiveDate::from_ymd_opt(2025, 1, 6).unwrap();
assert_eq!(next, expected);
}
#[test]
fn test_get_trading_days() {
let start = NaiveDate::from_ymd_opt(2025, 1, 6).unwrap(); let days = TradingCalendar::get_trading_days(&start, 5);
assert_eq!(days.len(), 5);
assert_eq!(days[0], NaiveDate::from_ymd_opt(2025, 1, 6).unwrap());
assert_eq!(days[1], NaiveDate::from_ymd_opt(2025, 1, 7).unwrap());
assert_eq!(days[2], NaiveDate::from_ymd_opt(2025, 1, 8).unwrap());
assert_eq!(days[3], NaiveDate::from_ymd_opt(2025, 1, 9).unwrap());
assert_eq!(days[4], NaiveDate::from_ymd_opt(2025, 1, 10).unwrap());
}
#[test]
fn test_get_trading_days_before() {
let end = NaiveDate::from_ymd_opt(2025, 1, 10).unwrap(); let days = TradingCalendar::get_trading_days_before(&end, 5);
assert_eq!(days.len(), 5);
assert_eq!(days[0], NaiveDate::from_ymd_opt(2025, 1, 6).unwrap());
assert_eq!(days[1], NaiveDate::from_ymd_opt(2025, 1, 7).unwrap());
assert_eq!(days[2], NaiveDate::from_ymd_opt(2025, 1, 8).unwrap());
assert_eq!(days[3], NaiveDate::from_ymd_opt(2025, 1, 9).unwrap());
assert_eq!(days[4], NaiveDate::from_ymd_opt(2025, 1, 10).unwrap());
}
#[test]
fn test_count_trading_days() {
let start = NaiveDate::from_ymd_opt(2025, 1, 6).unwrap(); let end = NaiveDate::from_ymd_opt(2025, 1, 10).unwrap();
let count = TradingCalendar::count_trading_days(&start, &end);
assert_eq!(count, 5);
}
#[test]
fn test_date_conversion() {
let date = NaiveDate::from_ymd_opt(2025, 1, 6).unwrap();
let i32 = TradingCalendar::date_to_i32(&date);
assert_eq!(i32, 20250106);
let converted = TradingCalendar::i32_to_date(i32);
assert_eq!(converted, date);
}
}