Skip to content

Labels

View as Markdown

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).

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 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 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:

labels.py
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
FY2023 P12
period 2023 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.

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).

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
{
"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}" } }
}
}
}
ambiguous_label.py
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
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}.