Skip to main content

Kenat / ቀናት PyPI Version

banner

GitHub issues GitHub Repo stars GitHub forks GitHub commit activity

A complete Ethiopian Calendar & Bahire Hasab toolkit for Python


📌 Overview

Kenat (Amharic: ቀናት) is a powerful and authentic Ethiopian calendar library for Python. It offers full support for date conversion, holiday computation (including Bahire Hasab), localized formatting, Geez numerals, and more — all without external dependencies.

🚀 Ported from the original kenat JS library.


✨ Features

  • 🔄 Bidirectional Conversion: Ethiopian ↔ Gregorian calendars
  • 📅 Full Holiday System: Christian, Muslim, cultural, and public holidays
  • ⛪ Bahire Hasab: Accurate movable feast calculations (e.g. Fasika, Tsome Nebiyat)
  • 🙏 Fasting Periods: Abiy Tsome, Nineveh, Tsome Nebiyat, Filseta, Tsome Hawaryat, Tsome Dihnet, Ramadan
  • 🏛️ Orthodox Saints (Nigs): Monthly commemoration/saints-day data for calendar grids
  • 🏷️ Holiday Filtering: Filter holidays by type (public, christian, muslim, etc.)
  • 📆 Date Arithmetic: Add/subtract days, months, years (13-month calendar support)
  • 📏 Date Difference & Distance: Precise diffs plus human-friendly "distance to date/holiday" strings
  • 🌐 Calendar Preference: Render dates in Ethiopian or Gregorian on demand
  • 🔠 Localized Formatting: Amharic and English support
  • 🔢 Geez Numerals: Convert integers to ግዕዝ numeral strings
  • ⏰ Ethiopian Time Support: 12-hour Ethiopian ↔ 24-hour Gregorian
  • 🗓️ Calendar Grids: Monthly/yearly calendar generation, with holiday/saint overlays and console printing
  • 🧾 Serialization: to_json() for clean Kenat → dict/JSON output
  • ⚠️ Structured Errors: Typed exceptions with to_json() for consistent error handling

🚀 Installation

Install the library from source:

pip install kenat

For development/testing:

pip install -e ".[test]"

🟢 Quick Start

from kenat import Kenat

today = Kenat.now()

print(today.get_ethiopian())
# → {'year': 2017, 'month': 9, 'day': 26}

print(today.format({'lang': 'english', 'show_weekday': True}))
# → "Tuesday, Ginbot 26 2017"

⛪ Bahire Hasab & Holiday System

Get All Holidays in a Year

from kenat import get_holidays_for_year

holidays = get_holidays_for_year(2017)

# Example: Find Fasika (Easter)
fasika = next(h for h in holidays if h['key'] == 'fasika')
print(fasika['ethiopian'])  # → {'year': 2017, 'month': 8, 'day': 21}

Filter Holidays by Type

from kenat import get_holidays_for_year, HolidayTags

# Public only
get_holidays_for_year(2017, filter_by=HolidayTags.PUBLIC)

# Christian and Muslim holidays
get_holidays_for_year(2017, filter_by=[HolidayTags.CHRISTIAN, HolidayTags.MUSLIM])

Check if a Date is a Holiday

from kenat import Kenat

meskel = Kenat("2017/1/17")
print(meskel.is_holiday())  # → [Holiday object]

non_holiday = Kenat("2017/1/18")
print(non_holiday.is_holiday())  # → []

Access Bahire Hasab Calculations

from kenat import Kenat

bh = Kenat("2017/1/1").get_bahire_hasab()

print(bh['evangelist'])  # → {'name': 'ማቴዎስ', 'remainder': 1}
print(bh['movableFeasts']['fasika']['ethiopian'])
# → {'year': 2017, 'month': 8, 'day': 21}

➕ More API Examples

Date Arithmetic

from kenat import Kenat

date = Kenat("2017/2/10")
print(date.add(days=10))     # → Add days
print(date.add(months=-1))   # → Subtract a month
print(date.subtract(years=1))

# Single-unit convenience wrappers
print(date.add_days(7))
print(date.add_months(2))
print(date.add_years(1))

Comparing Dates

a = Kenat("2016/1/1")
b = Kenat("2016/2/5")

a.is_before(b)     # → True
b.is_after(a)      # → True
a.is_same_day(a)   # → True
a.is_same_month(b) # → False
a.is_same_year(b)  # → True

Start / End of a Period

date = Kenat("2016/5/15")

date.start_of_month().get_ethiopian()  # → {'year': 2016, 'month': 5, 'day': 1}
date.end_of_month().get_ethiopian()    # → {'year': 2016, 'month': 5, 'day': 30}

date.start_of('year').get_ethiopian()  # → {'year': 2016, 'month': 1, 'day': 1}

# endOf('year') correctly returns Pagume 6 in leap years, Pagume 5 otherwise
Kenat("2015/5/1").end_of('year').get_ethiopian()
# → {'year': 2015, 'month': 13, 'day': 6}

Date Difference & Distance API

from kenat import Kenat

a = Kenat("2015/5/15")
b = Kenat("2012/5/15")

print(a.diff_in_days(b))     # → 1095
print(a.diff_in_years(b))    # → 3

# Human-friendly distance
print(a.distance_to(b, units=['years', 'months', 'days'], output='string'))
# → "3 years 0 months 0 days"

print(Kenat("2018/1/1").distance_from_today(output='string'))

Holiday Distance Helpers

from kenat import Kenat, HolidayNames

print(Kenat.distance_to_holiday(HolidayNames['fasika'], direction='future', units=['days'], output='string'))

Fasting Periods

from kenat import get_fasting_period, get_fasting_info, get_fasting_days, is_tsome_dihnet_fast_day, FastingKeys

# Start/end of the fast
lent = get_fasting_period(FastingKeys.ABIY_TSOME, 2017)
# → {'start': {'year': 2017, 'month': 7, 'day': 16}, 'end': {'year': 2017, 'month': 8, 'day': 12}}

# Localized name/description alongside the period
info = get_fasting_info(FastingKeys.FILSETA, 2017, lang='english')

# Every day within the fast
days = get_fasting_days(FastingKeys.NINEVEH, 2017)

# Weekly Wednesday/Friday fast (skips the 50 days after Fasika)
is_tsome_dihnet_fast_day({'year': 2017, 'month': 1, 'day': 6})  # → True/False

Orthodox Saints (Nigs) & Calendar Grid Modes

MonthGrid can attach Orthodox monthly commemoration ("Nigs") data per day, sourced from the same nigs.json data as the JS/TS library:

from kenat import MonthGrid

grid = MonthGrid.create(2017, 1, mode='christian')
# Each day only lists a saint if this month is one of that saint's major-feast
# months (its `negs` list); pass show_all_saints=True to list every saint that
# recurs on that day-of-month, every month.

grid_all = MonthGrid.create(2017, 1, mode='christian', show_all_saints=True)

# mode='muslim' additionally injects a weekly Jummah (Friday prayer) entry;
# mode='public' filters holidays down to public-only.
friday_grid = MonthGrid.create(2017, 1, mode='muslim')

print(grid['month_name'])  # "መስከረም"
print(grid['days'])        # weeks-of-7, padded with None
print(grid['flat_days'])   # flat, start-padded list (mirrors the JS/TS shape)

# Month navigation: instantiate MonthGrid directly (rather than via .create())
# to get an object with .up()/.down(), then call .generate() when ready
grid_instance = MonthGrid({'year': 2017, 'month': 1, 'mode': 'christian'})
next_month = grid_instance.up().generate()
prev_month = grid_instance.down().generate()

Calendar Preference (Ethiopian / Gregorian)

If your app lets users choose between the Ethiopian and Gregorian calendar, pass calendar='gregorian' to format(), to_iso_string(), or get_date() instead of branching at every call site:

date = Kenat("2016/1/1")
user_pref = 'gregorian'  # e.g. from a user setting

date.format({'calendar': user_pref})        # → "September 12, 2023"
date.get_date(calendar=user_pref)           # → {'year': 2023, 'month': 9, 'day': 12}
date.to_iso_string(calendar=user_pref)      # → "2023-09-12T06:00" (standard ISO 8601)

date.format()  # → defaults to 'ethiopian': "መስከረም 1 2016"

Calendar Generation

from kenat import Kenat

calendar = Kenat.get_month_calendar_grid(2017, 1, use_geez=False, weekday_lang='amharic')
year_calendar = Kenat.get_year_calendar(2017)

start = Kenat("2017/1/1")
end = Kenat("2017/1/10")
date_range = Kenat.generate_date_range(start, end)

# Print the current month as a grid to the console
Kenat.now().print_this_month()

Serialization

Kenat instances serialize cleanly via to_json():

date = Kenat("2017/1/15")
print(date.to_json())
# → {
#     'ethiopian': {'year': 2017, 'month': 1, 'day': 15},
#     'gregorian': {'year': 2024, 'month': 9, 'day': 25},
#     'time': {'hour': 12, 'minute': 0, 'period': 'day'}
#   }

Custom exceptions (InvalidEthiopianDateError, UnknownHolidayError, etc.) also expose to_json() for structured error handling/logging.

Geez Numerals

from kenat import to_geez

print(to_geez(2017))  # → ፳፻፲፯

🧱 Contributing

  1. Fork & clone the repo
  2. Create a new branch: git checkout -b feature/your-feature
  3. Make your changes, add tests in /tests/
  4. Run pytest to ensure everything passes
  5. Submit a pull request 🚀

👨‍💻 Author

Melaku Demeke GitHub: @MelakuDemeke


contributors

📄 License

MIT License — see LICENSE for details.

Metadata

Release files for kenat 2.0.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kenat 2.0.0
File Size Uploaded Provenance Actions
kenat-2.0.0.tar.gz 61.3 kB Without provenance Details for kenat-2.0.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kenat 2.0.0
File Interpreter ABI Platform Actions
kenat-2.0.0-py3-none-any.whl Python 3 none any Details for kenat-2.0.0-py3-none-any.whl

Total release size: 109.0 kB

Release files / kenat-2.0.0.tar.gz

Download URL kenat-2.0.0.tar.gz
Size 61.3 kB
Tags Source
SHA-256 checksum
How to use checksums
6e0f3d1fe9b7746634f401d61d252160c87b2aadbb71b8ca667fb5a3c3fb5f91
BLAKE2b-256 checksum
How to use checksums
e18548b7b0bf6ea302d1fc388ad676e6b256bd51cde077067973cd0ee0125709
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.3

Release files / kenat-2.0.0-py3-none-any.whl

Download URL kenat-2.0.0-py3-none-any.whl
Size 47.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f9ccc1715adb454855bee4a8f69f16bcfc23c6a40c1da9fcd2e49add9d843587
BLAKE2b-256 checksum
How to use checksums
e6028c14b1bf7e4e3082abbca88e4d7309936828d97161f5ab2d49bdacc30707
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.1.0 CPython/3.12.3

Security for kenat 2.0.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page