Functional Weave
Code in Python

payroll.tax-period@1.0.1

impl/rust.rs

1,985 bytes · the Rust implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

use super::funejson::Value;  ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
use super::dates_add_days::{days_between, parse_iso_date};  ← from dates.add-days ^1.0.0 · built alongside by fune
use super::dates_uk_tax_year::uk_tax_year;  ← from dates.uk-tax-year ^1.0.0 · built alongside by fune

/// 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()))
}