# Calendar API — Moroccan Economic Calendar # https://calendar-api.ma # Last updated: 2026-08-22 # # This file is intended for LLM consumption. # It is self-contained: no page scraping required for standard usage tasks. # Canonical docs: https://docs.calendar-api.ma --- ## What is Calendar API? Calendar API is a REST API + Python SDK providing Morocco's economic calendar: - Public holidays: national (fixed dates), religious (lunar, Estimated → Official), and exceptional (ad-hoc government declarations) - Business day calculations: next/previous day, count between dates, full series - CalSpan: chainable business-day bounds for months, quarters, semesters, years Country scope: Morocco (MA) only. Data history: religious holidays qualified from 2006; national from further back. Weekends: Saturday and Sunday are both treated as non-business days. When to use it: - Skip or gate ETL/pipeline runs on non-business days (Airflow, Dagster, PGQueuer) - Detect gaps in Moroccan time-series data - Generate SQL BETWEEN intervals aligned to Moroccan business calendar - Disable holiday dates in UI date-pickers - OPCVM / fund NAV reporting spans (CalSpan is purpose-built for this) - Any application that needs reliable, up-to-date Moroccan holiday data --- ## Authentication All endpoints require an API key passed as a header: X-API-Key: YOUR_API_KEY Free registration (no credit card): https://calendar-api.ma/console/register Up to 5 API keys per account, managed via the console. Health check endpoint requires NO API key. --- ## Python SDK — pycalendar-api ### Installation pip install pycalendar-api ### Initialization from pycalendar_api import CalendarApi, to_date api = CalendarApi('YOUR_API_KEY') # or from environment variable PYCALENDAR_APIKEY: api = CalendarApi.from_env() # Verify connectivity (no API key needed) api.health() # → ApiHealth(api_status="UP", appname="calendar-api", timestamp=...) ### Design notes - All response objects are standard Python dataclasses (no Pydantic) - Zero heavy dependencies; HTTP via httpx, configurable via .toml - Fully typed; JSON → Python objects automatic - Config file supports: timeout, proxy, certificates (enterprise env) --- ## SDK Reference — Holidays ### Check if a date is a holiday result = api.holidays.is_holiday(to_date('14/01/2025')) # → HolidayCheckResult( # date=date(2025, 1, 14), is_holiday=True, # description="Nouvel An Amazigh", # holiday_type=CalHolidayType.NATIONAL, # 'National' # status=CalHolidayStatus.OFFICIAL, # 'Official' # country_code='MA' # ) # If not a holiday: is_holiday=False, description/holiday_type/status are None ### List holidays of a single year holidays = api.holidays.year(2025) # → list[Holiday] # Filter by type: religious = api.holidays.year(2025, holiday_type='Religious') ### List holidays for multiple years (batch) holidays = api.holidays.years([2023, 2024, 2025]) # → list[Holiday] ### Holiday dataclass fields Holiday( description: str, day: int, month: int, date: datetime.date, holiday_type: CalHolidayType, # 'National' | 'Religious' | 'Exceptional' status: CalHolidayStatus, # 'Official' | 'Estimated' country_code: str = 'MA' ) --- ## SDK Reference — Business Days ### Next business day result = api.bdays.next_date(to_date('13/01/2025')) # → NextDate(date=date(2025, 1, 13), next_date=date(2025, 1, 15)) # Jan 14 is Amazigh New Year (national holiday), so next is Jan 15 ### Previous business day result = api.bdays.previous_date(to_date('15/01/2025')) # → PreviousDate(date=date(2025, 1, 15), previous_date=date(2025, 1, 13)) ### Count business days between two dates (inclusive, open days) count = api.bdays.count(to_date('02/01/2025'), to_date('31/12/2025')) # → DaysCount(start_date=date(2025,1,2), end_date=date(2025,12,31), count=247, freq='D') ### List business days between two dates series = api.bdays.between(to_date('02/01/2025'), to_date('31/12/2025')) # → DateSeries(ref='bdays', nitems=247, serie=SortedSet([...])) ### List business days of a full year series = api.bdays.bdays_of(2025) # → DateSeries(ref='bdays-2025', min_date='2025-01-02', max_date='2025-12-31', nitems=247, ...) ### List business days of a specific month series = api.bdays.bdays_of(2025, month=6) # → DateSeries(ref='bdays-2025-06', nitems=19, ...) --- ## SDK Reference — CalSpan (Period Bounds) CalSpan returns the OPEN business-day start and end of a period. "Open" means: the start_date is the last business day BEFORE the period, and end_date is the last business day OF the period. This makes successive spans chainable with no gaps or overlaps. Ideal for: WHERE date BETWEEN :start AND :end in SQL queries. ### Full year span = api.bdays.span(2025) # → CalSpan(start_date=date(2024,12,31), end_date=date(2025,12,31), # year=2025, semester=None, quarter=None, month=None, country_code='MA') # span.key → '2025' ### Semester (1 or 2) span = api.bdays.span(2025, semester=1) # → CalSpan(start_date=date(2024,12,31), end_date=date(2025,6,30), # year=2025, semester=1, ...) # span.key → '2025-S1' ### Quarter (1–4) span = api.bdays.span(2025, quarter=3) # → CalSpan(start_date=date(2025,6,30), end_date=date(2025,9,30), # year=2025, quarter=3, ...) # span.key → '2025-Q3' ### Month (1–12) span = api.bdays.span(2025, month=7) # → CalSpan(start_date=date(2025,6,30), end_date=date(2025,7,31), # year=2025, month=7, ...) # span.key → '2025-M7' ### Chain example (Q1 end == Q2 start) q1 = api.bdays.span(2025, quarter=1) # end_date = 2025-03-31 q2 = api.bdays.span(2025, quarter=2) # start_date = 2025-03-31 ← same open day --- ## Enums ### CalHolidayType (StrEnum) 'National' — fixed Gregorian date (e.g. Fête du Trône, Jour de l'An) 'Religious' — lunar calendar; may be Estimated until moon sighting 'Exceptional' — ad-hoc government declarations (bridge days, etc.) ### CalHolidayStatus (StrEnum) 'Official' — confirmed date 'Estimated' — pending moon sighting (Religious holidays only) # Tip: poll hourly for Estimated holidays. Status changes to Official once confirmed. --- ## How Morocco's Calendar Works - National holidays: fixed Gregorian dates; always Official. - Religious holidays: tied to Islamic lunar calendar. Future dates are Estimated. The API updates status to Official after moon sighting announcement. Strategy: hourly polling on Estimated entries until status flips to Official. - King days: birthday and coronation anniversary of the current King. These are National holidays; dates shift if King changes. - Exceptional holidays: government-declared one-off days (bridge days, etc.). - Weekends: both Saturday and Sunday are non-business. The API excludes them from business day results but does NOT return them in holiday lists. - Data model: SCD Type 2 — historical records preserved; no retroactive mutation. --- ## Common Integration Patterns ### Airflow — skip DAG on non-business days (Airflow 3 Task SDK) from airflow.sdk import dag, task from pycalendar_api import CalendarApi, to_date from datetime import date api = CalendarApi.from_env() # PYCALENDAR_APIKEY env var @dag(schedule="0 8 * * *") def daily_etl(): @task.short_circuit def check_business_day(): r = api.holidays.is_holiday(date.today()) return not r.is_holiday and date.today().weekday() < 5 check_business_day() >> ... ### Detect gaps in a Moroccan time-series bdays = api.bdays.bdays_of(2025) expected = set(bdays.serie) actual = set(df['date']) # your actual observations missing = expected - actual # business days with no data ### SQL reporting with CalSpan (example: quarterly NAV) span = api.bdays.span(2025, quarter=3) cursor.execute( "SELECT * FROM nav WHERE nav_date BETWEEN %s AND %s", (span.start_date, span.end_date) ) --- ## REST API (direct HTTP, no SDK) Base URL: https://calendar-api.ma/api/v1 Auth header: X-API-Key: YOUR_API_KEY Spec (OAS 3.1): https://calendar-api.ma/schema/openapi.json Interactive: https://calendar-api.ma/api/v1/docs Health check: GET https://calendar-api.ma/health (no auth) --- ## Response Models (dataclasses) Holiday — a single holiday entry (see fields above) HolidayCheckResult — result of is_holiday(); is_holiday: bool + optional detail DaysCount — count of business days: start_date, end_date, count, freq DateSeries — ordered set of dates: ref, min_date, max_date, nitems, serie (SortedSet) CalSpan — period bounds: start_date, end_date, year/semester/quarter/month, key NextDate — date + next_date PreviousDate — date + previous_date ApiHealth — api_status, appname, timestamp --- ## Links Website: https://calendar-api.ma Register (free): https://calendar-api.ma/console/register Console (login): https://calendar-api.ma/console/login Docs: https://docs.calendar-api.ma How it Works: https://docs.calendar-api.ma/how-it-works/ Response Models: https://docs.calendar-api.ma/reference-docs/models/ Exceptions: https://docs.calendar-api.ma/reference-docs/exceptions/ Properties/Enums: https://docs.calendar-api.ma/reference-docs/properties/ Domains/Adapters: https://docs.calendar-api.ma/reference-docs/domains/ Config: https://docs.calendar-api.ma/reference-docs/config/ Utils (to_date): https://docs.calendar-api.ma/reference-docs/utils/ Changelog: https://docs.calendar-api.ma/CHANGELOG/ OpenAPI spec: https://calendar-api.ma/schema/openapi.json Interactive docs: https://calendar-api.ma/api/v1/docs PyPI: https://pypi.org/project/pycalendar-api/ GitHub mirror: https://github.com/unraveldesigns/pycalendar-api GitLab (primary): https://gitlab.com/ud-labs/py-calendar-api Postman: https://www.postman.com/unravel-designs/calendar-api/overview --- ## Support Email: support@calendar-api.ma Issues: https://gitlab.com/ud-labs/py-calendar-api/-/issues Contact form: https://calendar-api.ma/contacts.html --- ## Publisher Unravel Designs — Casablanca, Morocco (RC: 605945) https://unraveldesigns.ma Founder: Marouane FAKIR — https://linkedin.com/in/marouane-fakir-98911b72