Kenat โ€” Ethiopian Calendar for Dart ๐Ÿ—“๏ธ

pub package Dart License: MIT

Kenat (แ‰€แŠ“แ‰ต โ€” "days" in Amharic) is a comprehensive pure-Dart library for the Ethiopian calendar system. It provides everything you need to work with Ethiopian dates, times, and cultural/religious data in your Dart or Flutter application.


โœจ Features

  • ๐Ÿ”„ Date Conversions โ€” Seamless Ethiopian โ†” Gregorian conversions
  • ๐Ÿ“… Date Arithmetic โ€” Add/subtract days, months, and years; compute differences
  • ๐Ÿ“ Distance API โ€” Human-friendly "distance to date/holiday" breakdowns and strings
  • ๐Ÿ•Œ๐Ÿ• Holidays โ€” Fixed public holidays, movable Christian feasts, and Islamic holidays (Eid, Moulid)
  • ๐Ÿ™ Fasting Periods โ€” Abiy Tsome, Nineveh, Filseta, Tsome Hawaryat, Tsome Dihenet, Ramadan
  • ๐Ÿ“– Bahire Hasab โ€” Full Ethiopian ecclesiastical computation system for movable feasts
  • โฐ Ethiopian Time โ€” 12-hour day/night period system with Gregorian conversion
  • ๐Ÿ”ข Geez Numerals โ€” Convert Arabic โ†” Ethiopic (Geez) numerals
  • ๐Ÿ—“๏ธ Calendar Grid โ€” Generate month/year grids with holiday and saint data overlays, mode-based filtering, and console printing
  • ๐Ÿ›๏ธ Orthodox Saints (Nigs) โ€” Monthly commemoration data sourced from the same nigs.json schema as the JS/TS library, gated by negs/showAllSaints
  • ๐ŸŒ Calendar Preference โ€” Render dates in Ethiopian or Gregorian on demand (format, getDate, toISOString)
  • ๐Ÿงพ Serialization โ€” toJson() for clean Kenat โ†’ Map/JSON output
  • ๐ŸŒ Multilingual โ€” Amharic and English support throughout the API
  • โš ๏ธ Error Handling โ€” Descriptive, typed error classes (with toJson()) for all failure modes

๐Ÿ“ฆ Installation

Add to your pubspec.yaml:

dependencies:
  kenat: ^1.0.0

Then run:

dart pub get

๐Ÿš€ Quick Start

import 'package:kenat/kenat.dart';

void main() {
  // Today's Ethiopian date
  final today = Kenat.now();
  print(today.formatStandard());           // e.g. "แŒแŠ•แ‰ฆแ‰ต 27 2016"
  print(today.formatStandard('english'));  // e.g. "Ginbot 27 2016"

  // Create from Ethiopian date
  final eth = Kenat.fromEthiopian(2016, 1, 1);
  print(eth.getGregorian());  // {year: 2023, month: 9, day: 11}

  // Create from Gregorian date
  final greg = Kenat.fromGregorian(2024, 5, 23);
  print(greg.getEthiopian()); // {year: 2016, month: 9, day: 15}
}

๐Ÿ“– API Reference

Kenat โ€” The Main Class

The primary class for all calendar operations. Can be constructed from a DateTime, an Ethiopian date map, or a date string.

Constructors & Factories

Kenat.now()                                      // Current date
Kenat.fromEthiopian(int year, int month, int day)
Kenat.fromGregorian(int year, int month, int day)
Kenat.fromString('2016/9/15')                    // "yyyy/mm/dd" or "yyyy-mm-dd"

Date Access

final d = Kenat.fromEthiopian(2016, 9, 15);

d.getEthiopian();  // {year: 2016, month: 9, day: 15}
d.getGregorian();  // {year: 2024, month: 5, day: 23}
d.getWeekday();    // 4  (0=Sunday โ€ฆ 6=Saturday)
d.getWeekdayName();         // "แˆแˆ™แˆต" (Amharic)
d.getWeekdayName('english'); // "Thursday"
d.getDaysInMonth();  // 30
d.getDayOfYear();    // day number within the Ethiopian year
d.isLeapYear();      // false
d.isToday;           // bool
d.isWeekend;         // bool

Formatting

d.formatStandard();            // "แŒแŠ•แ‰ฆแ‰ต 15 2016"
d.formatStandard('english');   // "Ginbot 15 2016"
d.formatInGeez();              // "แŒแŠ•แ‰ฆแ‰ต แฒแญ แณแปแฒแฎ"
d.formatWithWeekday();         // "แˆแˆ™แˆต, แŒแŠ•แ‰ฆแ‰ต 15 2016"
d.formatWithWeekday('english', true); // with Geez numerals
d.formatShort();               // "2016/09/15"
d.toISODateString();           // "2016-09-15"

// Unified format() supports lang, showWeekday, useGeez, includeTime, and calendar
d.format(lang: 'english', showWeekday: true, includeTime: true);

Calendar Preference (Ethiopian / Gregorian)

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

final date = Kenat('2016/1/1');
const userPref = 'gregorian'; // e.g. from a user setting

date.format(calendar: userPref);      // โ†’ "September 12, 2023"
date.getDate(calendar: userPref);     // โ†’ {year: 2023, month: 9, day: 12}
date.toISOString(calendar: userPref); // โ†’ "2023-09-12T06:00" (standard ISO 8601)

date.format(); // โ†’ defaults to 'ethiopian': "แˆ˜แˆตแŠจแˆจแˆ 1 2016"

Serialization

Kenat instances serialize cleanly via toJson():

final date = Kenat('2017/1/15');
print(date.toJson());
// โ†’ {
//     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 toJson(), with a type field that correctly reflects the concrete error class, for structured error handling/logging.

Date Arithmetic

d.addDays(10);      // new Kenat 10 days later
d.addMonths(2);     // 2 months later
d.addYears(1);      // 1 year later

d.add(5, 'days');      // generic add
d.subtract(3, 'months');

d.diffInDays(other);    // int
d.diffInMonths(other);  // int
d.diffInYears(other);   // int
d.diffBreakdown(other); // {years, months, days}

Distance API

Human-friendly "distance to a date or holiday" helpers built on diffBreakdown:

final a = Kenat.fromEthiopian(2015, 5, 15);
final b = Kenat.fromEthiopian(2012, 5, 15);

print(a.distanceTo(b, {'units': ['years', 'months', 'days'], 'output': 'string'}));
// โ†’ "3 years 0 months 0 days"

print(Kenat.fromEthiopian(2018, 1, 1).distanceFromToday({'output': 'string'}));

// Distance to the nearest occurrence of a holiday (past or future)
print(Kenat.distanceToHoliday('fasika', {
  'direction': 'future',
  'units': ['days'],
  'output': 'string',
}));

Date Comparison

d.isBefore(other);   // bool
d.isAfter(other);    // bool
d.isSameDay(other);  // bool
d.isSameMonth(other); // bool
d.isSameYear(other);  // bool
d == other;          // equality by date
d.startOf('month');   // first day of month
d.startOf('day');     // same day, time set to 1:00 day
d.endOf('month');     // last day of month
d.endOf('day');       // same day, time set to 12:00 night
d.startOfMonth();     // convenience alias
d.endOfMonth();       // convenience alias

Holidays

// Get a specific holiday for a year
final meskel = getHoliday('meskel', 2016);
// {key: 'meskel', name: 'แˆ˜แˆตแ‰€แˆ', ethiopian: {year: 2016, month: 1, day: 17}, ...}

// All holidays in a month
final holidays = getHolidaysInMonth(2016, 1);
// Optional language option
final holidaysEn = getHolidaysInMonth(2016, 1, {'lang': 'english'});

// Filter by tag
final publicHolidays = getHolidaysInMonth(2016, 1, {'filter': 'public'});
final christianOnly = getHolidaysInMonth(2016, 1, {'filter': 'christian'});
final islamicOnly   = getHolidaysInMonth(2016, 1, {'filter': 'muslim'});

// All holidays for a year
final yearHolidays = getHolidaysForYear(2016);

Holiday keys: enkutatash, meskel, gena, timket, adwa, labour, patriots, martyrsDay, beherbehereseb, nineveh, abiyTsome, debreZeit, hosanna, siklet, fasika, rikbeKahnat, erget, paraclete, tsomeHawaryat, tsomeDihnet, eidFitr, eidAdha, moulid

Holiday tags: public, state, religious, christian, orthodox, muslim, cultural


Bahire Hasab

The Bahire Hasab is the Ethiopian Orthodox system for computing movable feasts.

final bh = getBahireHasab(2016);

print(bh['ameteAlem']);     // Age of the world (e.g. 7516)
print(bh['evangelist']);    // {name: 'แ‹ฎแˆแŠ•แˆต', remainder: 3}
print(bh['newYear']);       // {dayName: 'แ‰…แ‹ณแˆœ', tinteQemer: 5}
print(bh['medeb']);         // Metonic cycle position
print(bh['nineveh']);       // {year: 2016, month: 5, day: 17}
print(bh['movableFeasts']); // Map of all movable feasts

// Get a single movable holiday
final fasika = getMovableHoliday('fasika', 2016);
// {year: 2016, month: 8, day: 27}

Movable feast keys: nineveh, abiyTsome, debreZeit, hosanna, siklet, fasika, rikbeKahnat, erget, paraclete, tsomeHawaryat, tsomeDihnet


Fasting Periods

import 'package:kenat/kenat.dart';

// Get start and end dates of a fasting period
final period = getFastingPeriod(FastingKeys.abiyTsome, 2016);
// {start: {year:2016, month:6, day:4}, end: {year:2016, month:8, day:26}}

// Get full fasting info with name and description
final info = getFastingInfo(FastingKeys.filseta, 2016, {'lang': 'english'});
// {key: 'filseta', name: 'Filseta', description: '...', tags: [...], period: {...}}

// Check if a date is an Orthodox weekly fast day (Wed/Fri, excluding post-Easter period)
final isfast = isTsomeDihnetFastDay({'year': 2016, 'month': 1, 'day': 5});
// true or false

Fasting keys (via FastingKeys class):

Key Description
FastingKeys.abiyTsome Lent (Great Fast)
FastingKeys.nineveh Fast of Nineveh
FastingKeys.tsomeNebiyat Fast of the Prophets
FastingKeys.filseta Fast of the Assumption (Nehase 1โ€“14)
FastingKeys.tsomeHawaryat Fast of the Apostles
FastingKeys.tsomeDihenet Weekly fasting (Wed & Fri)
FastingKeys.ramadan Islamic Ramadan

Ethiopian Time (Time)

The Ethiopian time system uses a 12-hour clock shifted by 6 hours from the Gregorian clock (6 AM = 12:00 "day", 6 PM = 12:00 "night").

// Create from Ethiopian hour/minute/period
final t = Time(6, 30, 'day');        // 6:30 AM Ethiopian = 12:30 PM Gregorian

// Create from Gregorian 24h time
final t2 = Time.fromGregorian(14, 0); // 2:00 PM = 8:00 "day" Ethiopian

// Create from string
final t3 = Time.fromString('6:30 night'); // or "แฎ:แด แˆ›แ‰ณ"

// Convert back to Gregorian
print(t.toGregorian()); // {hour: 12, minute: 30}

// Format
print(t.format());                          // "แฎ:แด แŒ แ‹‹แ‰ต"
print(t.format({'useGeez': false}));        // "06:30 day"
print(t.format({'lang': 'english'}));       // "06:30 day"

// Arithmetic
final later = t.add({'hours': 2, 'minutes': 30});
final earlier = t.subtract({'hours': 1});
final difference = t.diff(t2); // {hours: 1, minutes: 30}

// Kenat with time
final d = Kenat.fromEthiopian(2016, 9, 15);
final withTime = d.setTime(6, 30, 'day');
print(withTime.formatWithTime()); // "แŒแŠ•แ‰ฆแ‰ต 15 2016 06:30 แŒ แ‹‹แ‰ต"

Geez (Ethiopic) Numerals

toGeez(15);      // "แฒแญ"
toGeez(2016);    // "แณแปแฒแฎ"
toGeez(10000);   // "แผ"

toArabic('แฒแญ');   // 15
toArabic('แณแปแฒแฎ'); // 2016

Calendar Month Grid

Generate a structured calendar grid for UI rendering, with optional holidays, saints, and Geez numeral support.

final grid = createMonthGrid({
  'year': 2016,
  'month': 9,
  'useGeez': false,
  'weekdayLang': 'amharic', // or 'english'
  'weekStart': 1,           // 0=Sunday, 1=Monday(default), ...
  'mode': 'public',         // 'public' | 'christian' | 'orthodox' | 'muslim'
});

print(grid['monthName']); // "แŒแŠ•แ‰ฆแ‰ต"
print(grid['year']);      // "2016"
print(grid['headers']);   // ["แŠฅแˆ‘แ‹ต","แˆฐแŠž","แˆ›แŠญแˆฐแŠž",...]

// Each entry in grid['days'] is either null (padding) or:
// {
//   ethiopian: {year, month, day},
//   gregorian: {year, month, day},
//   weekday: 4,
//   weekdayName: "แˆแˆ™แˆต",
//   isToday: false,
//   holidays: [ {key, name, description, tags, ...} ]
// }

// Navigate months
final nextMonth = grid['up']();
final prevMonth = grid['down']();

Orthodox Saints (Nigs)

With mode: 'christian', each day's holidays list is enriched with Orthodox monthly commemoration ("Nigs") entries sourced from the same day-keyed nigs.json schema as the JS/TS library. A saint only appears if the requested month is one of its major-feast months (its negs list) โ€” pass showAllSaints: true to list every saint recurring on that day-of-month, every month:

final christianGrid = createMonthGrid({
  'year': 2016,
  'month': 9,
  'mode': 'christian',
});

final allSaintsGrid = createMonthGrid({
  'year': 2016,
  'month': 9,
  'mode': 'christian',
  'showAllSaints': true,
});

// Saint entries look like:
// { key, name, description, major, isNigs, tags: ['religious', 'christian', 'NIGS' | 'SAINT_DAY'] }

Static Calendar Methods

final monthCalendar = Kenat.getStaticMonthCalendar(2017, 1, {
  'useGeez': false,
  'weekdayLang': 'amharic',
});

final yearCalendar = Kenat.getYearCalendar(2017); // List of 13 month objects

final start = Kenat.fromEthiopian(2017, 1, 1);
final end = Kenat.fromEthiopian(2017, 1, 10);
final range = Kenat.generateDateRange(start, end); // List<Kenat>, inclusive

// Print the current month as an ASCII grid to the console
Kenat.now().printThisMonth();

Standalone Utility Functions

All major operations are also available as top-level functions for convenience:

// Conversions
toEC(2024, 5, 23);  // Ethiopian: {year: 2016, month: 9, day: 15}
toGC(2016, 9, 15);  // Gregorian: {year: 2024, month: 5, day: 23}

// Date arithmetic
addDays({'year': 2016, 'month': 1, 'day': 1}, 10);
addMonths({'year': 2016, 'month': 1, 'day': 1}, 2);
addYears({'year': 2016, 'month': 1, 'day': 1}, 1);
diffInDays(date1, date2);
diffInMonths(date1, date2);
diffInYears(date1, date2);

// Calendar grid
createMonthGrid({'year': 2016, 'month': 9});

// Format
formatStandard({'year': 2016, 'month': 9, 'day': 15});         // "แŒแŠ•แ‰ฆแ‰ต 15 2016"
formatInGeezAmharic({'year': 2016, 'month': 9, 'day': 15});   // "แŒแŠ•แ‰ฆแ‰ต แฒแญ แณแปแฒแฎ"
formatShort({'year': 2016, 'month': 9, 'day': 15});           // "2016/09/15"
formatWithWeekday({'year': 2016, 'month': 9, 'day': 15});

๐Ÿ—บ๏ธ Ethiopian Calendar Basics

Feature Details
Months 13 months: 12 ร— 30 days + Pagume (5 or 6 days)
Leap Year Every 4th year (year % 4 == 3), Pagume has 6 days
Year Offset Ethiopian year is ~7โ€“8 years behind Gregorian
New Year Enkutatash โ€” Meskerem 1 (โ‰ˆ Sept 11/12)
Time System Day starts at 6:00 AM (Ethiopian 12:00 day)

Ethiopian Month Names:

# Amharic English
1 แˆ˜แˆตแŠจแˆจแˆ Meskerem
2 แŒฅแ‰…แˆแ‰ต Tikimt
3 แŠ…แ‹ณแˆญ Hidar
4 แ‰ณแŠ…แˆฃแˆฅ Tahsas
5 แŒฅแˆญ Tir
6 แ‹จแŠซแ‰ฒแ‰ต Yekatit
7 แˆ˜แŒ‹แ‰ขแ‰ต Megabit
8 แˆšแ‹ซแ‹šแ‹ซ Miyazia
9 แŒแŠ•แ‰ฆแ‰ต Ginbot
10 แˆฐแŠ” Sene
11 แˆแˆแˆŒ Hamle
12 แАแˆแˆด Nehase
13 แŒณแŒ‰แˆœ Pagume

๐Ÿงช Testing

The library ships with 17 test suites covering all modules:

dart test

๐Ÿ“„ License

MIT License ยฉ Melaku Demeke


๐Ÿ™‹ Contributing

Pull requests and issues are welcome on GitHub. Please open an issue before submitting large changes.

Libraries

kenat
A comprehensive Ethiopian calendar library for Dart/Flutter