Metadata-Version: 2.4
Name: retailcalendar
Version: 1.0.0
Summary: A package to handle NRF's 454 calendar (Retail Calendar)
Author-email: Thiago Weidman <tw@weidman.com.br>
Project-URL: Homepage, https://github.com/thig0w/454calendar
Project-URL: Bug Tracker, https://github.com/thig0w/454calendar/issues
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
Classifier: Operating System :: OS Independent
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: click
Requires-Dist: rich
Requires-Dist: pyyaml
Dynamic: license-file

# retailcalendar

A Python package for working with the [NRF 4-5-4 Retail Calendar](https://nrf.com/resources/4-5-4-calendar) and public holiday calendars for Brazil and the United States.

The retail calendar groups weeks into months following a **4-5-4 week pattern** per quarter, giving retailers a consistent structure for fiscal year reporting. Some years have 53 weeks (a "53-week year") — this package handles that automatically.

## Installation

```bash
pip install retailcalendar
```

## CLI Usage

```bash
# Display the 4-5-4 calendar for the current year
454cal

# Display for a specific year
454cal 2025

# Use a different fiscal year start month (default is January)
454cal --start_month 2 2025

# Disable today and holiday highlights
454cal -d 2025

# Use a color theme
454cal -t ocean 2025
454cal -t sunset 2025

# Use a different holiday region
454cal -r US 2025
454cal -r BR-SP-SAO 2025

# Combine options
454cal -t matrix -r US 2025
```

The output is a color-formatted calendar printed to the terminal, organized with 3 months per row and week numbers on the left. Public holidays and today's date are highlighted using the active color theme.

### CLI Options

| Option | Default | Description |
|--------|---------|-------------|
| `-s` / `--start_month` | `1` | Fiscal year start month (1–12) |
| `-d` / `--days_highlight_off` | off | Disable today and holiday highlights |
| `-t` / `--theme` | `default` | Color theme (see [Themes](#themes)) |
| `-r` / `--region` | `BR-PR-CWB` | Holiday region (see [Supported Regions](#supported-regions)) |
| `YEAR` | current year | Fiscal year to display |

### Themes

| Theme | Description |
|-------|-------------|
| `default` | Classic terminal colors — red, yellow, green, purple |
| `ocean` | Blues and teals |
| `sunset` | Warm oranges and golds |
| `monokai` | Editor-inspired — magentas, chartreuse, purple |
| `retro` | Amber CRT monitor — warm monochrome |
| `matrix` | Green phosphor terminal — monochrome green |
| `sakura` | Japanese cherry blossom — soft pinks |
| `ice` | Arctic frost — crisp whites and pale blues |
| `volcano` | Volcanic lava — deep reds and glowing yellows |

## Python API

### `Cal454` — NRF 4-5-4 Retail Calendar

```python
from retailcalendar import Cal454, CalendarTheme

# Create a calendar for fiscal year 2025 (starts February 2025)
cal = Cal454(year=2025, s_month=2)

# Display the full calendar (holidays highlighted by default)
cal.format_year()
cal.format_year(highlight_today=False, highlight_holidays=False)

# Use a built-in color theme
cal.format_year(theme="ocean")
cal.format_year(theme="volcano", region="US")

# Define a custom theme
my_theme = CalendarTheme(
    year_title="bold blue",
    month_name="cyan",
    day_header="grey50",
    week_number="bright_green",
    today="bold white on dark_blue",
    holiday="bold white on dark_green",
)
cal.format_year(theme=my_theme)

# Get month start/end dates (list of 12 dates)
cal.month_start_dates()
cal.month_end_dates()

# Get quarter start/end dates (list of 4 dates)
cal.quarter_start_dates()
cal.quarter_end_dates()

# Get all weeks in a given month (1-indexed)
cal.month_days_by_week(month=1)

# Get all weeks for the entire year
cal.year_days_by_week()

# Check if a year has 53 weeks
Cal454.has_43_weeks(year=2023)            # True
Cal454.has_43_weeks(year=2023, s_month=1) # False
```

#### `Cal454` Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `year` | current year | Fiscal year to calculate |
| `s_month` | `2` | Fiscal year start month (1–12) |

#### `format_year()` Parameters

| Parameter | Default | Description |
|-----------|---------|-------------|
| `w_col` | `2` | Column width per day (min 2) |
| `space_month` | `3` | Space between month columns (min 3) |
| `line_months` | `3` | Number of months per row (min 3) |
| `highlight_today` | `True` | Highlight today's date |
| `highlight_holidays` | `True` | Highlight public holidays |
| `theme` | `"default"` | Color theme name or `CalendarTheme` instance |
| `region` | `"BR-PR-CWB"` | Holiday region code |

---

### `HolidayCalendar` — Public Holiday Calculator

Calculates public holidays for a given region and year. Regions are defined in `holidays.yaml` and support inheritance — a city-level region automatically includes state and national holidays.

```python
from retailcalendar import HolidayCalendar

cal = HolidayCalendar("BR-PR-CWB", 2025)

# Full list of holidays as dicts, sorted by date
for h in cal.holidays():
    print(h["date"], h["name"])

# Just the canonical holiday dates
cal.dates()           # list[date]

# Observed (legally effective) dates after weekend shifts
cal.observed_dates()  # list[date]
```

Each dict returned by `holidays()` contains:

| Key | Type | Description |
|-----|------|-------------|
| `date` | `date` | Canonical holiday date |
| `observed` | `date` | Effective date after weekend adjustment (`next_monday` / `nearest_weekday`) |
| `name` | `str` | Local name |
| `name_en` | `str \| None` | English name |
| `type` | `str` | Rule type: `fixed`, `relative`, `offset`, or `complex` |
| `region` | `str` | Region code used for the lookup |

#### Supported Regions

| Code | Description |
|------|-------------|
| `BR` | Brazil — national holidays |
| `BR-PR` | Paraná state (inherits `BR`) |
| `BR-PR-CWB` | Curitiba city (inherits `BR-PR`) |
| `BR-SP` | São Paulo state (inherits `BR`) |
| `BR-SP-SAO` | São Paulo city (inherits `BR-SP`) |
| `US` | United States — federal holidays |

#### Holiday rule types

| Type | Description | Example |
|------|-------------|---------|
| `fixed` | Same day/month every year | Christmas (Dec 25) |
| `relative` | Nth weekday of a given month | Thanksgiving (4th Thursday of November) |
| `offset` | N days before/after a base algorithm | Good Friday (Easter − 2 days) |
| `complex` | Full algorithm | Easter Sunday |

#### `since` field

Holidays with a `since` year are excluded for earlier years. For example, Brazil's Dia da Consciência Negra (`since: 2024`) does not appear for 2023 and earlier.

---

## How the 4-5-4 Calendar Works

Each fiscal quarter follows a 4-5-4 week pattern:

| Month in Quarter | Weeks |
|-----------------|-------|
| First           | 4     |
| Second          | 5     |
| Third           | 4     |

The fiscal year normally has **52 weeks**. Per NRF rules, a **53rd week** is added to the last month of the year when the last day of a standard 52-week year has 4 or more days remaining in the start month. Known 53-week years (February start): 2000, 2006, 2012, 2017, 2023, 2028.

The fiscal year starts on the **Sunday on or before February 1** (or the configured start month).

## Development

```bash
git clone https://github.com/thig0w/454calendar.git
cd 454calendar

# Set up development environment (venv, dev deps, pre-commit hooks)
make devenv

# Run formatter, linter, and tests
make precommit

# Run tests only
uv run pytest

# Build distribution package
make build

# Clean everything
make clean
```

**Requirements:** Python 3.10+

## License

GNU General Public License v3.0 — see [LICENSE](LICENSE).
