Skip to content

Dates and intervals

View as Markdown

Three rules decide how dates go in and out of fiscalclock. Each one is a design rule of the project (AGENTS.md).

Every date argument is a civil date: a year, month and day, with no time and no time zone. Each language accepts:

Language Accepted Rejected with TIMESTAMP_REJECTED
Python an object whose type is exactly datetime.date, or an ISO YYYY-MM-DD string datetime.datetime, including a pandas Timestamp
JavaScript an ISO YYYY-MM-DD string or a Temporal.PlainDate Date, and Temporal PlainDateTime, ZonedDateTime and Instant
Rust fiscalclock::Date Not applicable: no function takes a timestamp type

A timestamp is rejected because its calendar day depends on the time zone: late evening on 3 Feb in one zone is already 4 Feb in a zone a few hours ahead, and 3 Feb 2024 and 4 Feb 2024 are in different fiscal years in the example calendar. The right day depends on a zone that only you know, so convert the timestamp to a date in the zone you mean before you pass it. A string with a time or a zone suffix is not a date either, and returns INVALID_DATE (API spec).

timestamps.py
import datetime as dt
from pathlib import Path
from fiscalclock import FiscalCalendar, FiscalclockError
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
try:
cal.locate(dt.datetime(2024, 2, 3, 23, 30))
except FiscalclockError as error:
print(error.code, error.path)
print(cal.format(cal.locate(dt.date(2024, 2, 3)).week))
Output
TIMESTAMP_REJECTED date
FY2023 W53

Every period is the half-open interval [start, end_exclusive): it contains start and every day up to, but not including, end_exclusive. last_day is the day before end_exclusive, and is given for display (API spec).

With exclusive ends, one period’s end_exclusive is the next period’s start, so periods meet without a gap or an overlap. The number of days in a period is end_exclusive minus start, and a date d is in a period when start <= d < end_exclusive. The same comparison works for ISO date strings, because they sort in date order.

half_open.py
from pathlib import Path
from fiscalclock import FiscalCalendar
cal = FiscalCalendar(Path("retail-4-5-4.json").read_text(encoding="utf-8"))
quarters = cal.children(cal.period("year", 2023), "quarter")
for quarter in quarters:
print(quarter.start, quarter.end_exclusive, quarter.days)
print(all(a.end_exclusive == b.start for a, b in zip(quarters, quarters[1:])))
Output
2023-01-29 2023-04-30 91
2023-04-30 2023-07-30 91
2023-07-30 2023-10-29 91
2023-10-29 2024-02-04 98
True

No operation reads the clock, the time zone of the machine, the environment or any other global state. A question about “now”, such as the current fiscal week, takes the date as an argument, so the same call gives the same answer on every machine and in every test. To ask about today, pass today’s date in the time zone you mean.

The API covers fiscal years that lie wholly between 2 Jan 0001 and 30 Dec 9999. A date in, or a request for, any other fiscal year returns OUT_OF_RANGE (spec).