use crate::DayFlags;
use crate::data::{FEDERAL_HOLIDAYS, MonthDay, NON_JANUARY_HOLIDAYS};
use crate::raw_date::RawDate;
#[inline]
pub(crate) fn flags(date: RawDate) -> DayFlags {
let weekend = is_weekend(date);
let holiday = is_federal_holiday(date);
let extra_day_off = predicted_extra_day_off(date);
let working_override = false;
let day_off = !working_override && (weekend || holiday || extra_day_off);
let working_day = !day_off;
let short_day = if working_day {
is_federal_holiday(date.next_day())
} else {
false
};
let transferred = extra_day_off || working_override;
DayFlags::EMPTY
.with_if(weekend, DayFlags::WEEKEND)
.with_if(holiday, DayFlags::HOLIDAY)
.with_if(day_off, DayFlags::DAY_OFF)
.with_if(working_day, DayFlags::WORKING_DAY)
.with_if(short_day, DayFlags::SHORT_DAY)
.with_if(transferred, DayFlags::TRANSFERRED)
}
#[inline]
fn is_weekend(date: RawDate) -> bool {
date.weekday >= 5
}
#[inline]
fn is_federal_holiday(date: RawDate) -> bool {
FEDERAL_HOLIDAYS.contains(date.month, date.day)
}
fn predicted_extra_day_off(date: RawDate) -> bool {
is_transfer_target_from_non_january_holiday(date)
|| is_transfer_target_from_january_holiday(date)
}
fn is_transfer_target_from_non_january_holiday(date: RawDate) -> bool {
let mut i = 0;
while i < NON_JANUARY_HOLIDAYS.len() {
let h = NON_JANUARY_HOLIDAYS[i];
let holiday_date = RawDate::from_ymd_unchecked(date.year, h.month, h.day);
if is_weekend(holiday_date) {
let target = next_predicted_working_day_after(holiday_date);
if target.month == date.month && target.day == date.day {
return true;
}
}
i += 1;
}
false
}
fn is_transfer_target_from_january_holiday(date: RawDate) -> bool {
predicted_january_transfer_targets(date.year).contains(date.month, date.day)
}
fn predicted_january_transfer_targets(year: i32) -> TransferTargets {
let mut count: u8 = 0;
let mut targets = TransferTargets {
first: None,
second: None,
};
let mut candidate = RawDate::from_ymd_unchecked(year, 1, 9);
let mut day: u8 = 1;
while day <= 8 {
let holiday = RawDate::from_ymd_unchecked(year, 1, day);
if is_weekend(holiday) {
candidate = next_predicted_working_day_from(candidate);
let md = MonthDay::new(candidate.month, candidate.day);
if count == 0 {
targets.first = Some(md);
} else if count == 1 {
targets.second = Some(md);
} else {
break;
}
count += 1;
candidate = candidate.next_day();
}
day += 1;
}
targets
}
fn next_predicted_working_day_after(date: RawDate) -> RawDate {
next_predicted_working_day_from(date.next_day())
}
fn next_predicted_working_day_from(mut date: RawDate) -> RawDate {
let mut i = 0;
while i < 16 {
if is_predicted_base_working_day(date) {
return date;
}
date = date.next_day();
i += 1;
}
unreachable!("в окне 16 дней всегда есть базовый рабочий день");
}
#[inline]
fn is_predicted_base_working_day(date: RawDate) -> bool {
!is_weekend(date) && !is_federal_holiday(date)
}
#[derive(Debug, Clone, Copy)]
struct TransferTargets {
first: Option<MonthDay>,
second: Option<MonthDay>,
}
impl TransferTargets {
#[inline]
fn contains(self, month: u8, day: u8) -> bool {
let needle = MonthDay::new(month, day);
self.first == Some(needle) || self.second == Some(needle)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_2027_jan1_is_holiday() {
let d = RawDate::from_ymd(2027, 1, 1).unwrap();
let flags = flags(d);
assert!(flags.is_holiday());
assert!(flags.is_day_off());
}
#[test]
fn test_2027_jan10_is_weekend() {
let d = RawDate::from_ymd(2027, 1, 10).unwrap();
let flags = flags(d);
assert!(flags.is_weekend());
assert!(flags.is_day_off());
}
#[test]
fn test_2027_feb22_is_short_day() {
let d = RawDate::from_ymd(2027, 2, 22).unwrap();
let flags = flags(d);
assert!(flags.is_short_day());
assert!(flags.is_working_day());
}
#[test]
fn test_2027_mar7_is_weekend_not_short() {
let d = RawDate::from_ymd(2027, 3, 7).unwrap();
let flags = flags(d);
assert!(flags.is_weekend());
assert!(!flags.is_short_day());
}
#[test]
fn test_2027_mar5_is_short_day() {
let d = RawDate::from_ymd(2027, 3, 6).unwrap();
let flags = flags(d);
assert!(flags.is_weekend());
assert!(!flags.is_short_day());
}
#[test]
fn test_2027_transfers_may9_sunday() {
let d = RawDate::from_ymd(2027, 5, 10).unwrap();
let flags = flags(d);
assert!(flags.is_day_off());
assert!(flags.is_transferred());
}
}