Functional Weave
Code in Python

dates.uk-tax-year@1.0.0

impl/rust.rs

2,685 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::{format_iso_date, parse_iso_date, CivilDate};  ← from dates.add-days ^1.0.0 · built alongside by fune

/// The UK personal tax year begins on 6 April, a leftover of the 1752 calendar
/// reform that nobody has been willing to move since.
const TAX_YEAR_START_MONTH: i64 = 4;
const TAX_YEAR_START_DAY: i64 = 6;

/// The UK personal tax year containing a date: 6 April to 5 April inclusive.
///
/// The boundary is where this gets implemented wrong. 5 April 2026 belongs to
/// 2025/26 and 6 April 2026 belongs to 2026/27, so a comparison written as
/// `month >= 4` puts the whole of 1-5 April in the wrong year, and one written
/// against 1 April - the company financial year end, a different thing entirely
/// - moves the boundary by five days for everybody.
///
/// # Panics
/// Panics on a malformed or impossible date, or one whose tax year would fall
/// outside the supported range.
pub fn uk_tax_year(iso: &str) -> UkTaxYear {
    let date = parse_iso_date(iso);

    // Compare (month, day) against (4, 6) as a pair. On or after 6 April the
    // date is in the tax year that started this calendar year; before it, the
    // date still belongs to the one that started last April.
    let on_or_after_start = (date.month, date.day) >= (TAX_YEAR_START_MONTH, TAX_YEAR_START_DAY);
    let start_year = if on_or_after_start {
        date.year
    } else {
        date.year - 1
    };

    if start_year < 1 || start_year + 1 > 9999 {
        panic!(
            "\"{}\" is outside the supported range 0001-01-01 to 9999-12-31",
            iso
        );
    }

    UkTaxYear {
        start: format_iso_date(&CivilDate {
            year: start_year,
            month: TAX_YEAR_START_MONTH,
            day: TAX_YEAR_START_DAY,
        }),
        end: format_iso_date(&CivilDate {
            year: start_year + 1,
            month: TAX_YEAR_START_MONTH,
            day: TAX_YEAR_START_DAY - 1,
        }),
        label: tax_year_label(start_year),
    }
}

/// Just the label, for report headings: "2026/27".
pub fn uk_tax_year_label(iso: &str) -> String {
    uk_tax_year(iso).label
}

fn tax_year_label(start_year: i64) -> String {
    // The second half is two digits of the end year, zero-padded, so 1999/00 and
    // 2000/01 come out right rather than as 1999/0 or 1999/2000.
    format!("{:04}/{:02}", start_year, (start_year + 1) % 100)
}

pub fn uk_tax_year_to_value(year: &UkTaxYear) -> Value {
    Value::obj(vec![
        ("start", Value::str(&year.start)),
        ("end", Value::str(&year.end)),
        ("label", Value::str(&year.label)),
    ])
}

pub fn fune_vector(args: &[Value]) -> Value {
    uk_tax_year_to_value(&uk_tax_year(args[0].as_str()))
}