API reference
This page lists the operations of API version 1 and how each language names them. The normative contract, with every argument rule and error path, is the API spec; the Rust crate’s full API is on docs.rs.
Every operation is defined once, in the Rust core. The Python and npm packages convert their native values to the core’s JSON API and back, so they return the same results as the crate.
Each example uses the definition retail-4-5-4.json from the quick start, and prints exactly the output shown under it.
Names in each language
Section titled “Names in each language”| Operation | Python | JavaScript | Rust |
|---|---|---|---|
| Build a calendar | FiscalCalendar |
new FiscalCalendar |
FiscalCalendar::from_json |
validate |
validate |
validate |
FiscalCalendar::from_json |
locate |
locate |
locate |
locate |
period |
period |
period |
period |
shift |
shift |
shift |
shift |
parent |
parent |
parent |
parent |
children |
children |
children |
children |
years |
years |
years |
years |
format |
format |
format |
format |
parse |
parse |
parse |
parse |
dimension |
dimension |
dimension |
dimension |
dimension_csv |
dimension_csv |
dimensionCsv |
dimension_csv |
explain |
explain |
explain |
explain |
All operations except validate are methods of a calendar, and take their arguments in the same order in every language, as the examples below show. Optional arguments are keyword arguments with a default of None in Python, optional parameters in JavaScript, and Option values in Rust, where period takes Option<u32> and format takes Option<&str>. In Rust, periods are passed as &Period, levels as Level and dates as Date.
In Python and JavaScript, a definition is JSON text or the equivalent dict or object. A level is one of "year", "quarter", "period" and "week"; in Rust it is a Level. A period argument is a period returned by another operation, or just its coordinates level, year and index: the core recomputes the period from them (API spec). Dates follow Dates and intervals.
Values
Section titled “Values”A period has level, year (the fiscal year number under the default scheme), index (1-based within the year, and empty for a year), start, end_exclusive, last_day, days, weeks (empty unless the year is week-based) and has_extra_week. In Python it is a frozen dataclass with datetime.date fields, in JavaScript a plain object with ISO date strings, and in Rust a Period struct whose last_day() is a method (API spec).
A location has the date, the year, quarter, period and week periods that contain it, and its 1-based day_of_year, day_of_quarter, day_of_period and day_of_week (API spec).
Errors
Section titled “Errors”A failed operation raises FiscalclockError in Python (a subclass of ValueError), throws FiscalclockError in JavaScript, and returns an Err in Rust. Every error has a stable code, the JSON path of the offending field or argument, a message and, where one helps, a hint. Some codes carry details, such as the candidates of an ambiguous label. In Python and JavaScript, the error’s attributes describe the first error and errors holds them all; in Rust, building a calendar returns every error as Errors, and the other operations return one Error.
The codes, and the operations that raise each one, are listed in the error catalogue.
The JSON API
Section titled “The JSON API”Each package also exposes the core’s JSON API, which takes and returns JSON text exactly as the core produces it: call_json and validate_json in Python, callJson and validateJson in JavaScript, and fiscalclock::json::call and fiscalclock::json::validate in Rust. For the same definition and arguments, the three return the same bytes (API spec).
validate
Section titled “validate”Checks a definition and reports every error it finds, sorted by path and then by code. For a valid definition, Python and JavaScript return ok, the calendar_id and the canonical form; Rust’s from_json returns the calendar, whose calendar_id() and canonical_json() give the same values. The Python and JavaScript validate functions return the errors instead of raising them. Write a definition shows an invalid definition.
from pathlib import Path
from fiscalclock import validate
result = validate(Path("retail-4-5-4.json").read_text(encoding="utf-8"))print(result["ok"], result["calendar_id"])True efc8868a4b19eaabimport { readFileSync } from "node:fs";import { validate } from "fiscalclock";
const result = validate(readFileSync("retail-4-5-4.json", "utf8"));console.log(result.ok, result.calendar_id);true efc8868a4b19eaabuse std::error::Error;use std::fs;
use fiscalclock::FiscalCalendar;
fn main() -> Result<(), Box<dyn Error>> { let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?; println!("{}", cal.calendar_id()); Ok(())}efc8868a4b19eaablocate
Section titled “locate”Returns the location of a date: the year, quarter, period and week that contain it, and its position in each.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
here = cal.locate("2024-02-03")print(here.year.year, here.quarter.index, here.period.index, here.week.index)print(here.day_of_year, here.day_of_quarter, here.day_of_period, here.day_of_week)2023 4 12 53371 98 35 7import { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const here = cal.locate("2024-02-03");console.log(here.year.year, here.quarter.index, here.period.index, here.week.index);console.log(here.day_of_year, here.day_of_quarter, here.day_of_period, here.day_of_week);2023 4 12 53371 98 35 7use std::error::Error;use std::fs;
use fiscalclock::{FiscalCalendar};
fn main() -> Result<(), Box<dyn Error>> { let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?;
let here = cal.locate("2024-02-03".parse()?)?; println!("{} {:?} {:?} {:?}", here.year.year, here.quarter.index, here.period.index, here.week.index); println!("{} {} {} {}", here.day_of_year, here.day_of_quarter, here.day_of_period, here.day_of_week);
Ok(())}2023 Some(4) Some(12) Some(53)371 98 35 7period
Section titled “period”Returns the period with the given coordinates. Leave out index for a year. Coordinates that name no period return PERIOD_NOT_FOUND.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
q4 = cal.period("quarter", 2023, 4)print(q4.start, q4.end_exclusive, q4.last_day, q4.days, q4.weeks, q4.has_extra_week)2023-10-29 2024-02-04 2024-02-03 98 14 Trueimport { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const q4 = cal.period("quarter", 2023, 4);console.log(q4.start, q4.end_exclusive, q4.last_day, q4.days, q4.weeks, q4.has_extra_week);2023-10-29 2024-02-04 2024-02-03 98 14 trueuse 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 q4 = cal.period(Level::Quarter, 2023, Some(4))?; println!( "{} {} {} {} {:?} {}", q4.start, q4.end_exclusive, q4.last_day(), q4.days, q4.weeks, q4.has_extra_week );
Ok(())}2023-10-29 2024-02-04 2024-02-03 98 Some(14) trueReturns the period n steps away at the same level, crossing fiscal years as needed. n may be negative.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
q4 = cal.period("quarter", 2023, 4)for n in (1, -4): moved = cal.shift(q4, n) print(n, cal.format(moved), moved.start)1 FY2024 Q1 2024-02-04-4 FY2022 Q4 2022-10-30import { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const q4 = cal.period("quarter", 2023, 4);for (const n of [1, -4]) { const moved = cal.shift(q4, n); console.log(n, cal.format(moved), moved.start);}1 FY2024 Q1 2024-02-04-4 FY2022 Q4 2022-10-30use 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 q4 = cal.period(Level::Quarter, 2023, Some(4))?; for n in [1, -4] { let moved = cal.shift(&q4, n)?; println!("{} {} {}", n, cal.format(&moved, None)?, moved.start); }
Ok(())}1 FY2024 Q1 2024-02-04-4 FY2022 Q4 2022-10-30parent
Section titled “parent”Returns the period one level up that contains the given one: a quarter’s year, a period’s quarter, and a week’s period in a week-based year or its year in a fixed-date year. A year has no parent, and returns INVALID_ARGUMENT.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
week = cal.period("week", 2023, 53)period = cal.parent(week)quarter = cal.parent(period)year = cal.parent(quarter)print(cal.format(week), "->", cal.format(period), "->", cal.format(quarter), "->", cal.format(year))FY2023 W53 -> FY2023 P12 -> FY2023 Q4 -> FY2023import { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const week = cal.period("week", 2023, 53);const period = cal.parent(week);const quarter = cal.parent(period);const year = cal.parent(quarter);console.log(cal.format(week), "->", cal.format(period), "->", cal.format(quarter), "->", cal.format(year));FY2023 W53 -> FY2023 P12 -> FY2023 Q4 -> FY2023use 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 week = cal.period(Level::Week, 2023, Some(53))?; let period = cal.parent(&week)?; let quarter = cal.parent(&period)?; let year = cal.parent(&quarter)?; println!( "{} -> {} -> {} -> {}", cal.format(&week, None)?, cal.format(&period, None)?, cal.format(&quarter, None)?, cal.format(&year, None)? );
Ok(())}FY2023 W53 -> FY2023 P12 -> FY2023 Q4 -> FY2023children
Section titled “children”Returns the periods at a level inside a period, in order. In a fixed-date year, weeks cross month boundaries, so asking for the weeks of a quarter or a period returns LEVEL_NOT_NESTED.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
q4 = cal.period("quarter", 2023, 4)for period in cal.children(q4, "period"): print(cal.format(period), period.weeks, period.has_extra_week)FY2023 P10 4 FalseFY2023 P11 5 FalseFY2023 P12 5 Trueimport { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const q4 = cal.period("quarter", 2023, 4);for (const period of cal.children(q4, "period")) { console.log(cal.format(period), period.weeks, period.has_extra_week);}FY2023 P10 4 falseFY2023 P11 5 falseFY2023 P12 5 trueuse 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 q4 = cal.period(Level::Quarter, 2023, Some(4))?; for period in cal.children(&q4, Level::Period)? { println!("{} {:?} {}", cal.format(&period, None)?, period.weeks, period.has_extra_week); }
Ok(())}FY2023 P10 Some(4) falseFY2023 P11 Some(5) falseFY2023 P12 Some(5) trueReturns the fiscal years numbered first to last, both included, at most 10,000 of them.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
for year in cal.years(2021, 2024): print(year.year, year.start, year.weeks)2021 2021-01-31 522022 2022-01-30 522023 2023-01-29 532024 2024-02-04 52import { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
for (const year of cal.years(2021, 2024)) { console.log(year.year, year.start, year.weeks);}2021 2021-01-31 522022 2022-01-30 522023 2023-01-29 532024 2024-02-04 52use std::error::Error;use std::fs;
use fiscalclock::{FiscalCalendar};
fn main() -> Result<(), Box<dyn Error>> { let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?;
for year in cal.years(2021, 2024)? { println!("{} {} {:?}", year.year, year.start, year.weeks); }
Ok(())}2021 2021-01-31 Some(52)2022 2022-01-30 Some(52)2023 2023-01-29 Some(53)2024 2024-02-04 Some(52)format
Section titled “format”Returns the label of a period under the default scheme, or under the named scheme. A scheme with no template for the period’s level returns TEMPLATE_MISSING, and an unknown scheme returns SCHEME_NOT_FOUND. See Labels.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
week = cal.period("week", 2023, 53)print(cal.format(week))print(cal.format(cal.parent(week), "nrf"))FY2023 W53FY2023 P12import { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const week = cal.period("week", 2023, 53);console.log(cal.format(week));console.log(cal.format(cal.parent(week), "nrf"));FY2023 W53FY2023 P12use 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 week = cal.period(Level::Week, 2023, Some(53))?; println!("{}", cal.format(&week, None)?); println!("{}", cal.format(&cal.parent(&week)?, Some("nrf"))?);
Ok(())}FY2023 W53FY2023 P12Returns the period that a label names. A label that matches no template returns LABEL_NOT_RECOGNISED, and one that matches several periods returns AMBIGUOUS_LABEL with every candidate (Labels).
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
week = cal.parse("FY2023 W53")print(week.level, week.year, week.index, week.start, week.last_day, week.has_extra_week)week 2023 53 2024-01-28 2024-02-03 Trueimport { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const week = cal.parse("FY2023 W53");console.log(week.level, week.year, week.index, week.start, week.last_day, week.has_extra_week);week 2023 53 2024-01-28 2024-02-03 trueuse std::error::Error;use std::fs;
use fiscalclock::{FiscalCalendar};
fn main() -> Result<(), Box<dyn Error>> { let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?;
let week = cal.parse("FY2023 W53")?; println!( "{} {} {:?} {} {} {}", week.level.name(), week.year, week.index, week.start, week.last_day(), week.has_extra_week );
Ok(())}week 2023 Some(53) 2024-01-28 2024-02-03 truedimension
Section titled “dimension”Returns one row per day from start up to but not including end_exclusive, at most 146,097 days. Python returns a list of dicts and JavaScript an array of objects, with dates as ISO strings; Rust returns DimensionRow values with a location and the labels, in the order of label_columns(). The columns are listed in Date dimension.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
for row in cal.dimension("2024-02-02", "2024-02-05"): print(row["date"], row["fiscal_year"], row["quarter"], row["period"], row["week"], row["label_nrf_week"])2024-02-02 2023 4 12 53 FY2023 W532024-02-03 2023 4 12 53 FY2023 W532024-02-04 2024 1 1 1 FY2024 W01import { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
for (const row of cal.dimension("2024-02-02", "2024-02-05")) { console.log(row.date, row.fiscal_year, row.quarter, row.period, row.week, row.label_nrf_week);}2024-02-02 2023 4 12 53 FY2023 W532024-02-03 2023 4 12 53 FY2023 W532024-02-04 2024 1 1 1 FY2024 W01use std::error::Error;use std::fs;
use fiscalclock::{FiscalCalendar};
fn main() -> Result<(), Box<dyn Error>> { let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?;
let week_label = cal.label_columns().iter().position(|c| c == "label_nrf_week").unwrap(); for row in cal.dimension("2024-02-02".parse()?, "2024-02-05".parse()?)? { let at = row.location; println!( "{} {} {:?} {:?} {:?} {}", at.date, at.year.year, at.quarter.index, at.period.index, at.week.index, row.labels[week_label].as_deref().unwrap_or("") ); }
Ok(())}2024-02-02 2023 Some(4) Some(12) Some(53) FY2023 W532024-02-03 2023 Some(4) Some(12) Some(53) FY2023 W532024-02-04 2024 Some(1) Some(1) Some(1) FY2024 W01dimension_csv
Section titled “dimension_csv”Returns the same rows as RFC 4180 CSV text, with a header row.
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
print(cal.dimension_csv("2024-02-03", "2024-02-05"), end="")date,calendar_id,fiscal_year,quarter,period,week,day_of_year,day_of_quarter,day_of_period,day_of_week,year_start,year_last_day,quarter_start,quarter_last_day,period_start,period_last_day,week_start,week_last_day,year_has_extra_week,period_has_extra_week,week_is_extra_week,label_nrf_year,label_nrf_quarter,label_nrf_period,label_nrf_week2024-02-03,efc8868a4b19eaab,2023,4,12,53,371,98,35,7,2023-01-29,2024-02-03,2023-10-29,2024-02-03,2023-12-31,2024-02-03,2024-01-28,2024-02-03,true,true,true,FY2023,FY2023 Q4,FY2023 P12,FY2023 W532024-02-04,efc8868a4b19eaab,2024,1,1,1,1,1,1,1,2024-02-04,2025-02-01,2024-02-04,2024-05-04,2024-02-04,2024-03-02,2024-02-04,2024-02-10,false,false,false,FY2024,FY2024 Q1,FY2024 P01,FY2024 W01import { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
process.stdout.write(cal.dimensionCsv("2024-02-03", "2024-02-05"));date,calendar_id,fiscal_year,quarter,period,week,day_of_year,day_of_quarter,day_of_period,day_of_week,year_start,year_last_day,quarter_start,quarter_last_day,period_start,period_last_day,week_start,week_last_day,year_has_extra_week,period_has_extra_week,week_is_extra_week,label_nrf_year,label_nrf_quarter,label_nrf_period,label_nrf_week2024-02-03,efc8868a4b19eaab,2023,4,12,53,371,98,35,7,2023-01-29,2024-02-03,2023-10-29,2024-02-03,2023-12-31,2024-02-03,2024-01-28,2024-02-03,true,true,true,FY2023,FY2023 Q4,FY2023 P12,FY2023 W532024-02-04,efc8868a4b19eaab,2024,1,1,1,1,1,1,1,2024-02-04,2025-02-01,2024-02-04,2024-05-04,2024-02-04,2024-03-02,2024-02-04,2024-02-10,false,false,false,FY2024,FY2024 Q1,FY2024 P01,FY2024 W01use std::error::Error;use std::fs;
use fiscalclock::{FiscalCalendar};
fn main() -> Result<(), Box<dyn Error>> { let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?;
print!("{}", cal.dimension_csv("2024-02-03".parse()?, "2024-02-05".parse()?)?);
Ok(())}date,calendar_id,fiscal_year,quarter,period,week,day_of_year,day_of_quarter,day_of_period,day_of_week,year_start,year_last_day,quarter_start,quarter_last_day,period_start,period_last_day,week_start,week_last_day,year_has_extra_week,period_has_extra_week,week_is_extra_week,label_nrf_year,label_nrf_quarter,label_nrf_period,label_nrf_week2024-02-03,efc8868a4b19eaab,2023,4,12,53,371,98,35,7,2023-01-29,2024-02-03,2023-10-29,2024-02-03,2023-12-31,2024-02-03,2024-01-28,2024-02-03,true,true,true,FY2023,FY2023 Q4,FY2023 P12,FY2023 W532024-02-04,efc8868a4b19eaab,2024,1,1,1,1,1,1,1,2024-02-04,2025-02-01,2024-02-04,2024-05-04,2024-02-04,2024-03-02,2024-02-04,2024-02-10,false,false,false,FY2024,FY2024 Q1,FY2024 P01,FY2024 W01explain
Section titled “explain”Returns the rules the definition applies to a date, one step per level, from the year down to the week. The rule and result text is English for people to read; its wording is not part of the API and may change in any release (API spec).
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
explanation = cal.explain("2024-02-03")for step in explanation["steps"]: print(f"{step['level']}: {step['result']}")year: fiscal year 2023 runs 2023-01-29 to 2024-02-03 (371 days, 53 weeks, including the extra week)quarter: quarter 4 runs 2023-10-29 to 2024-02-03 (98 days, 14 weeks, including the extra week)period: period 12 runs 2023-12-31 to 2024-02-03 (35 days, 5 weeks, including the extra week)week: week 53 runs 2024-01-28 to 2024-02-03 (7 days, 1 weeks, the extra week); 2024-02-03 is day 7 of the weekimport { readFileSync } from "node:fs";import { FiscalCalendar } from "fiscalclock";
const cal = new FiscalCalendar(readFileSync("retail-4-5-4.json", "utf8"));
const explanation = cal.explain("2024-02-03");for (const step of explanation.steps) { console.log(`${step.level}: ${step.result}`);}year: fiscal year 2023 runs 2023-01-29 to 2024-02-03 (371 days, 53 weeks, including the extra week)quarter: quarter 4 runs 2023-10-29 to 2024-02-03 (98 days, 14 weeks, including the extra week)period: period 12 runs 2023-12-31 to 2024-02-03 (35 days, 5 weeks, including the extra week)week: week 53 runs 2024-01-28 to 2024-02-03 (7 days, 1 weeks, the extra week); 2024-02-03 is day 7 of the weekuse std::error::Error;use std::fs;
use fiscalclock::{FiscalCalendar};
fn main() -> Result<(), Box<dyn Error>> { let cal = FiscalCalendar::from_json(&fs::read_to_string("retail-4-5-4.json")?)?;
let explanation = cal.explain("2024-02-03".parse()?)?; for step in explanation.steps { println!("{}: {}", step.level.name(), step.result); }
Ok(())}year: fiscal year 2023 runs 2023-01-29 to 2024-02-03 (371 days, 53 weeks, including the extra week)quarter: quarter 4 runs 2023-10-29 to 2024-02-03 (98 days, 14 weeks, including the extra week)period: period 12 runs 2023-12-31 to 2024-02-03 (35 days, 5 weeks, including the extra week)week: week 53 runs 2024-01-28 to 2024-02-03 (7 days, 1 weeks, the extra week); 2024-02-03 is day 7 of the week