This document defines how the refactored pyroj library aligns with Python’s datetime module, how calendar math is performed without third-party packages, and how naming, locales, and security constraints fit together.
- Single runtime dependency: the Python standard library (
datetime,logging,enum,typing,dataclassesas needed). Calendar algorithms (Julian day number, Persian solar, tabular Islamic) are implemented in pure Python inside the package—nopersiantools, nohijriwheels. - Interop first: Kurdish calendar values are not opaque strings. They behave like
datetime.date/datetime.datetimecompanions: comparable, hashable where appropriate, and convertible to/fromdatetime.dateanddatetime.datetime. - Extensibility: Locale strings live in
pyroj/locales/catalog.json; new display languages can be added by extending that file andLocaleIdwhen needed. - Observability: Structured logging (module-level loggers), no secrets in logs.
- Safety: Strict validation on construction; no
eval, no unsafe deserialization; reject out-of-range dates with documented exceptions.
- Instant in civil time: Represented as
datetime.datetime(timezone-aware when the user supplies atzinfo; naive when they do not). All arithmetic that involves hours/minutes/seconds/microseconds usestimedeltaand followsdatetimesemantics. - Date-only: Represented as
datetime.datefor the Gregorian civil date of that instant in the same timezone context. - Julian day (JDN): Internal hub for converting between Gregorian, Persian (Jalali), and tabular Islamic natively. Floating JDN includes the fractional day for time-of-day.
Historical sources describe different epoch conventions:
| Mode | Rule (conceptual) | Used by |
|---|---|---|
| Solar offset (default) | Kurdish year = Persian (Jalali) year + 1321; month/day match Persian solar structure. | Default offset calculations (e.g. 2726 from 1405). |
| Median / Nineveh era | Year count tied to 612 BCE and vernal equinox (Newroz); formulas such as 1 + (gregorian_year + 611) appear in literature—not interchangeable with the +1321 solar calendar without explicit conversion. |
Academic / cultural articles (Roshani, etc.). |
The v2 API must expose this as an explicit KurdishEra (or similarly named) enum so users and tests never mix eras silently.
pyroj/
_core/convert.py # Julian day hub (Gregorian, Persian, Islamic)
kurdish.py # KurdishDate
locales/catalog.json # Month/weekday strings per locale (loaded at import)
locales/catalog.py # Loader + LOCALE_BY_ID
formatting.py # Fixed-token date formatting
exceptions.py
Naming follows PEP 8; KurdishDate mirrors datetime.date where practical (replace, ordering, hash).
- Fields:
year,month,dayin Kurdish solar calendar under selectedKurdishEra. - Construction:
from_gregorian(date: datetime.date),from_persian(year, month, day),from_jdn(jdn: float), etc. - Conversion:
to_gregorian() -> datetime.date,to_persian() -> tuple[int,int,int],to_islamic() -> ...,to_datetime(...) -> datetime.datetimewhen time is zero or user-supplied. - Implements ordering based on the underlying Gregorian
dateor JDN for consistency.
- Wraps
datetime.datetimeplus Kurdish calendar projection for the date part; delegates all time fields todatetime.
- Separate script (Arabic, Latin, etc.) from dialect (Kurmanji, Sorani, Hawrami, …).
- Month and weekday tables live in data modules; no user-controlled format strings executed as code (formatting uses safe templates or
str.formatwith fixed keys only). - Kurdish dialects are modeled explicitly as
kmrandckb; legacykuremains a compatibility alias. - Locale lookup uses normalization + fallback (
kmr/ckb+ compatibility aliases such askuandku-latn, thenenfallback) via a single resolver path.
PyrojRangeError: day out of range for month, invalid month, impossible Persian/Islamic date.PyrojValueError: inconsistent parameters, unsupported era combination.- All public functions document which exceptions they raise.
- Use
logging.getLogger(__name__). - Log levels: DEBUG for conversion steps (optional), INFO for library init, WARNING/ERROR for recoverable vs fatal issues.
- Never log environment variables, tokens, or paths from user home unless explicitly part of a debug feature behind a flag.
- Validate all integer inputs (ranges, types).
- No
picklefor user data; if serialization is added later, use explicit JSON schema or similar. - Format strings for user-facing output must not evaluate arbitrary expressions.
- Golden tests: Known Gregorian ↔ Persian ↔ Kurdish ↔ Islamic conversions.
- Edge cases: Persian leap years (mod 128), Islamic year boundaries, Kurdish month 12 length (29/30).