# Labels

Turn periods into labels such as "FY2023 Q4" and labels back into periods, and what happens when a label could mean more than one period.

A label is the name your organisation writes for a period, such as `FY2023 Q4`. The `naming` block of a definition holds one or more schemes, and each scheme has a template for some of the levels `year`, `quarter`, `period` and `week` ([spec](https://fiscalclock.karthik.nz/reference/spec/definition-v1/#naming)).

## Templates

A template is literal text with placeholders. `{yyyy}` is the year number, `{yy}` its last two digits, `{yy_next}` the last two digits of the next year number, `{q}` the quarter, `{pp}` the period and `{ww}` the week. Every template must contain `{yyyy}` or `{yy}`, and a quarter, period or week template must contain its own index, so that every label names exactly one period of one year. The [templates table](https://fiscalclock.karthik.nz/reference/spec/definition-v1/#templates) in the spec gives the padding of each placeholder and the levels it is allowed in.

A template that uses `{yy}` without `{yyyy}` needs a `century_pivot`, which says which century a two-digit year belongs to when a label is parsed.

## Format and parse

`format` gives the label of a period under the default scheme, or under the scheme you name. `parse` reads a label back and returns its period. With the example definition from [the quick start](https://fiscalclock.karthik.nz/start-here/quick-start/):

**Python**

*labels.py*

```python
from pathlib import Path

from fiscalclock import FiscalCalendar

cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))

label = cal.format(cal.period("period", 2023, 12))
print(label)

parsed = cal.parse(label)
print(parsed.level, parsed.year, parsed.index, parsed.start, parsed.last_day)
```

*Output*

```txt
FY2023 P12
period 2023 12 2023-12-31 2024-02-03
```

**JavaScript**

*labels.mjs*

```js
import { readFileSync } from "node:fs";
import { FiscalCalendar } from "fiscalclock";

const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));

const label = cal.format(cal.period("period", 2023, 12));
console.log(label);

const parsed = cal.parse(label);
console.log(parsed.level, parsed.year, parsed.index, parsed.start, parsed.last_day);
```

*Output*

```txt
FY2023 P12
period 2023 12 2023-12-31 2024-02-03
```

**Rust**

*labels.rs*

```rust
use std::error::Error;
use std::fs;

use fiscalclock::{FiscalCalendar, Level};

fn main() -> Result<(), Box<dyn Error>> {
    let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?;

    let label = cal.format(&cal.period(Level::Period, 2023, Some(12))?, None)?;
    println!("{label}");

    let parsed = cal.parse(&label)?;
    println!(
        "{} {} {:?} {} {}",
        parsed.level.name(), parsed.year, parsed.index, parsed.start, parsed.last_day()
    );

    Ok(())
}
```

*Output*

```txt
FY2023 P12
period 2023 Some(12) 2023-12-31 2024-02-03
```

`parse` matches the whole label, case-sensitively, against every template of every scheme. A label that matches nothing returns `LABEL_NOT_RECOGNISED`.

## Ambiguous labels

When a label matches more than one period, `parse` does not pick one. It returns `AMBIGUOUS_LABEL` with every candidate in the error's `details`, ordered by start date, then scheme name, then level ([spec](https://fiscalclock.karthik.nz/reference/spec/api-v1/#parse)).

This happens, for example, when two schemes use the same template but number years differently. This illustrative definition has the 4-5-4 years of the quick start and two schemes that both write `FY{yyyy}`, one numbering each year by the calendar year it starts in and one by the calendar year it ends in:

*two-schemes.json*

```json
{
  "spec_version": 1,
  "year": {
    "type": "week_based",
    "anchor": { "edge": "end", "month": 1, "day": "last", "rule": "nearest" }
  },
  "week": { "starts_on": "sunday", "numbering": "contains_start" },
  "subdivision": {
    "type": "week_pattern",
    "periods": [4, 5, 4, 4, 5, 4, 4, 5, 4, 4, 5, 4],
    "quarters": [3, 3, 3, 3],
    "extra_week": { "period": 12 }
  },
  "naming": {
    "default": "by_start",
    "schemes": {
      "by_start": { "year_number": "start", "templates": { "year": "FY{yyyy}" } },
      "by_end": { "year_number": "end", "templates": { "year": "FY{yyyy}" } }
    }
  }
}
```

**Python**

*ambiguous_label.py*

```python
from pathlib import Path

from fiscalclock import FiscalCalendar, FiscalclockError

cal = FiscalCalendar(Path("two-schemes.json").read_text(encoding="utf-8"))

try:
    cal.parse("FY2024")
except FiscalclockError as error:
    print(error.code)
    for candidate in error.details["candidates"]:
        period = candidate["period"]
        print(candidate["scheme"], period["year"], period["start"], period["last_day"])
```

*Output*

```txt
AMBIGUOUS_LABEL
by_end 2023 2023-01-29 2024-02-03
by_start 2024 2024-02-04 2025-02-01
```

**JavaScript**

*ambiguous_label.mjs*

```js
import { readFileSync } from "node:fs";
import { FiscalCalendar } from "fiscalclock";

const cal = new FiscalCalendar(readFileSync("two-schemes.json", "utf8"));

try {
  cal.parse("FY2024");
} catch (error) {
  console.log(error.code);
  for (const { scheme, period } of error.details.candidates) {
    console.log(scheme, period.year, period.start, period.last_day);
  }
}
```

*Output*

```txt
AMBIGUOUS_LABEL
by_end 2023 2023-01-29 2024-02-03
by_start 2024 2024-02-04 2025-02-01
```

**Rust**

*ambiguous_label.rs*

```rust
use std::error::Error;
use std::fs;

use fiscalclock::{Details, FiscalCalendar};

fn main() -> Result<(), Box<dyn Error>> {
    let cal = FiscalCalendar::from_json(&fs::read_to_string("two-schemes.json")?)?;

    if let Err(error) = cal.parse("FY2024") {
        println!("{}", error.code);
        if let Some(Details::Candidates(candidates)) = &error.details {
            for c in candidates {
                println!("{} {} {} {}", c.scheme, c.period.year, c.period.start, c.period.last_day());
            }
        }
    }
    Ok(())
}
```

*Output*

```txt
AMBIGUOUS_LABEL
by_end 2023 2023-01-29 2024-02-03
by_start 2024 2024-02-04 2025-02-01
```

`FY2024` is the year that starts on 4 Feb 2024 under `by_start`, and the year that ends on 3 Feb 2024 under `by_end`. Each candidate's `year` is its number under the default scheme, `by_start`, so the `by_end` reading appears as year 2023. To make every label unambiguous, give each scheme a template that the others cannot match, such as `FY{yyyy}` and `FYE{yyyy}`.
