# Compute fiscal calendars from a JSON definition

fiscalclock is an open-source fiscal calendar engine for Rust, Python and JavaScript. Describe a fiscal calendar as JSON, and it tells you which fiscal year, quarter, period and week any date falls in.

Describe your organisation's fiscal calendar as a JSON definition, and fiscalclock tells you which fiscal year,
quarter, period and week any date falls in, the bounds of every period, and the labels you use for them. One Rust
core gives the same answers in Python, JavaScript and Rust.

**Python**

```sh
pip install fiscalclock
```

**JavaScript**

```sh
npm install fiscalclock
```

**Rust**

```sh
cargo add fiscalclock
```

[Get started](https://fiscalclock.karthik.nz/start-here/quick-start/)
[Try it in the playground](https://fiscalclock.karthik.nz/playground/)

An illustrative 4-5-4 definition, not any organisation's published calendar. Its year rule gives the 53-week years NRF publishes. fiscalclock has no built-in calendars.

*retail-4-5-4.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": "nrf",
    "schemes": { "nrf": { "year_number": "start",
        "templates": { "year": "FY{yyyy}", "quarter": "FY{yyyy} Q{q}",
          "period": "FY{yyyy} P{pp}", "week": "FY{yyyy} W{ww}" } } } }
}
```

Fiscal 2023 under that definition, drawn to scale: 53 weeks from 29 Jan 2023 up to, but not including, 4 Feb 2024.
Each period is a half-open interval, so the date under a boundary line is the first day of the period to its right.
The extra 53rd week is the last week of period 12. The dates were computed by the fiscalclock 0.1.0 npm package
when this page was built, and the build fails unless the year has the 53 weeks that&#x20;
[NRF's list of 53-week years](https://nrf.com/resources/4-5-4-calendar) gives it and the dates match the&#x20;
[dates from an independent implementation](https://github.com/Karthik-beta/fiscalclock/blob/main/conformance/vectors/oracle/nrf-4-5-4.json) of the spec.

## Ask it a question

Which fiscal quarter is 3 Feb 2024 in, and what is it called? Save the definition above as `retail-4-5-4.json` and ask:

**Python**

```python
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(cal.format(here.quarter))
print(cal.call_json("locate", '{"date": "2024-02-03"}'))
```

**JavaScript**

```js
import { 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(cal.format(here.quarter));
console.log(cal.callJson("locate", '{"date": "2024-02-03"}'));
```

**Rust**

```rust
use 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!("{}", cal.format(&here.quarter, None)?);
    println!("{}", cal.call_json("locate", r#"{"date": "2024-02-03"}"#));

    Ok(())
}
```

All three print the same two lines: the label, then the core's JSON for the date, byte for byte the same in every language. Each snippet is a file in `website/examples/` that `npm run test:examples` runs against version 0.1.0 of its package, and it must print exactly the output below.

*Output*

```text
FY2023 Q4
{"date":"2024-02-03","year":{"level":"year","year":2023,"index":null,"start":"2023-01-29","end_exclusive":"2024-02-04","last_day":"2024-02-03","days":371,"weeks":53,"has_extra_week":true},"quarter":{"level":"quarter","year":2023,"index":4,"start":"2023-10-29","end_exclusive":"2024-02-04","last_day":"2024-02-03","days":98,"weeks":14,"has_extra_week":true},"period":{"level":"period","year":2023,"index":12,"start":"2023-12-31","end_exclusive":"2024-02-04","last_day":"2024-02-03","days":35,"weeks":5,"has_extra_week":true},"week":{"level":"week","year":2023,"index":53,"start":"2024-01-28","end_exclusive":"2024-02-04","last_day":"2024-02-03","days":7,"weeks":1,"has_extra_week":true},"day_of_year":371,"day_of_quarter":98,"day_of_period":35,"day_of_week":7}
```

## Why you can check the results

- **Shared conformance vectors.** The vector files in [`conformance/`](https://github.com/Karthik-beta/fiscalclock/tree/main/conformance) are run by the test suite of every package. Expected results come from cited sources or from generators written independently of the library, never from the library's own output.
- **External oracles.** Week-based years are checked against Python's ISO 8601 week dates (`date.isocalendar()`) for every year from 1600 to 2399, and the NRF 4-5-4 definition against the 53-week years [NRF publishes](https://nrf.com/resources/4-5-4-calendar) (2006, 2012, 2017 and 2023).
- **A second implementation.** A deliberately slow day-by-day reference implementation is compared with the core for every date from 1900 to 2200 across 960 generated definitions.
- **Property tests.** Periods tile their year without gaps or overlaps, nest correctly, and round-trip through `shift` and through `format` and `parse`. Week-based years have 364 or 371 days and 71 long years in every 400.
- **Identical output in every language.** On every push, [CI](https://github.com/Karthik-beta/fiscalclock/blob/main/.github/workflows/ci.yml) runs all vectors through Rust and through the Python and npm packages on Linux, macOS and Windows, and requires the raw outputs to match the Rust output byte for byte.
- **Reproducible verification.** Every date-arithmetic claim in the design documents is reproduced by a standard-library script in [`research/verification/`](https://github.com/Karthik-beta/fiscalclock/tree/main/research/verification), which CI reruns on every pull request.

These statements are taken from the [project README](https://github.com/Karthik-beta/fiscalclock#why-you-can-check-the-results).

## Packages

All three packages are built from the same Rust core and return the same JSON for the same input. Version 0.1.0 is the first release; the definition format and API are version 1 drafts and may change before 1.0.

| Package | Install | Supports |
| - | - | - |
| [Python package](https://pypi.org/project/fiscalclock/) | `pip install fiscalclock` | CPython 3.10 and later, and free-threaded 3.14; wheels for Linux, macOS and Windows |
| [npm package](https://www.npmjs.com/package/fiscalclock) | `npm install fiscalclock` | Node.js 22 and later, and browsers (WebAssembly) |
| [Rust crate](https://crates.io/crates/fiscalclock) | `cargo add fiscalclock` | Rust 1.85 and later, `no_std` with `alloc` |
