use super::funejson::Value; use super::dates_add_days::{days_between, parse_iso_date}; use super::dates_uk_tax_year::uk_tax_year; /// The tax week, tax month and payroll period a pay date falls in. /// /// The tax year is 365 or 366 days, which is 52 weeks and one or two days over. /// Those spare days are week 53, and they are the reason `period` exists: HMRC /// numbers the extra payday 53, 54 or 56 by frequency and taxes it /// non-cumulatively, so a payroll that just counts weeks gets it wrong. /// /// # Panics /// Panics on a malformed date or an unknown frequency. pub fn tax_period(pay_date: &str, frequency: &str) -> TaxPeriod { if !matches!(frequency, "weekly" | "fortnightly" | "four-weekly" | "monthly") { panic!("unknown pay frequency \"{}\"", frequency); } let year = uk_tax_year(pay_date); let week = days_between(&year.start, pay_date) / 7 + 1; // Tax months run from the 6th to the 5th, so the calendar month moves on // five days before the tax month does. let date = parse_iso_date(pay_date); let mut month = if date.day >= 6 { date.month - 3 } else { date.month - 4 }; if month <= 0 { month += 12; } let period = if frequency == "monthly" { month } else if week < 53 { week } else { match frequency { "weekly" => 53, "fortnightly" => 54, _ => 56, } }; TaxPeriod { tax_year: year.label, tax_year_start: year.start, week, month, period, } } pub fn tax_period_to_value(p: &TaxPeriod) -> Value { Value::obj(vec![ ("taxYear", Value::str(&p.tax_year)), ("taxYearStart", Value::str(&p.tax_year_start)), ("week", Value::Int(p.week)), ("month", Value::Int(p.month)), ("period", Value::Int(p.period)), ]) } pub fn fune_vector(args: &[Value]) -> Value { tax_period_to_value(&tax_period(args[0].as_str(), args[1].as_str())) }