
Research
/Security News
jscrambler npm Package Compromised in Supply Chain Attack
A compromised jscrambler npm release added a malicious preinstall hook that runs hidden native binaries on Linux, macOS, and Windows.

📌 Overview
Kenat (Amharic: ቀናት) is a comprehensive JavaScript library for the Ethiopian calendar. It provides a complete toolset for developers, handling date conversions, advanced formatting, full date arithmetic, and a powerful, authentic holiday calculation system based on the traditional Bahire Hasab (ባሕረ ሃሳብ).
{ calendar } option — no if/else needed at call sites.isBefore, isAfter, isSameDay, isSameMonth, and isSameYear for clean, readable comparisons.Kenat instance, so chaining is always safe.npm install kenat
Node.js 18 or later. Kenat ships zero runtime dependencies and both module formats — import (ESM) and require() (CJS) both work, so there's no need for a dynamic import() workaround in CommonJS projects. A minified browser build (dist/index.global.js, global Kenat) is also published for plain <script> tag / CDN use via unpkg.
Kenat is written in TypeScript and ships its own .d.ts declarations — no additional type packages needed, and the types are guaranteed to match the implementation since they're generated from the same source, not hand-maintained separately.
import Kenat from 'kenat';
import type { EthiopianDate, Holiday } from 'kenat';
const today: Kenat = new Kenat();
const date: EthiopianDate = today.getEthiopian();
// Default import (Kenat class)
import Kenat from 'kenat';
// Named imports for utilities
import {
getHolidaysForYear,
getHolidaysInMonth,
getHoliday,
HolidayTags,
HolidayNames,
toGeez,
toArabic,
toEC,
toGC,
diffBreakdown,
getFastingPeriod,
getFastingInfo,
getFastingDays,
getBahireHasab,
monthNames,
MonthGrid,
Time
} from 'kenat';
Get today's Ethiopian date:
import Kenat from 'kenat';
const today = new Kenat();
console.log(today.getEthiopian());
// → { year: 2018, month: 1, day: 8 }
console.log(today.format({ lang: 'english', showWeekday: true }));
// → "Thursday, Meskerem 8 2018"
// Get Gregorian equivalent
console.log(today.getGregorian());
// → { year: 2025, month: 9, day: 19 }
// Format with time
const withTime = today.setTime(8, 30, 'day');
console.log(withTime.format({ includeTime: true }));
// → "2018/1/8 8:30 day"
Immutable & chainable — every method that "changes" a date (.add(), .startOf(), .setTime(), ...) returns a new Kenat instance rather than mutating the original. This makes chaining predictable and safe:
const future = new Kenat()
.add(2, 'years')
.add(3, 'months')
.startOf('month');
Ethiopian-first, calendar-agnostic where it counts — dates are stored internally as Ethiopian {year, month, day}, but every user-facing accessor and formatter (getDate, format, toString, toISOString) accepts a { calendar: 'ethiopian' | 'gregorian' } option, so building calendar-switchable UIs doesn't require branching logic in your own code.
No hidden state — options are always passed explicitly per call (options = {}), matching the rest of the library's style; there's no global or instance-level configuration to keep track of.
import { getHolidaysForYear } from 'kenat';
const holidaysIn2017 = getHolidaysForYear(2017);
console.log(holidaysIn2017.find(h => h.key === 'fasika'));
// Output for Fasika (Easter) in 2017
{
key: 'fasika',
tags: ['public', 'religious', 'christian'],
movable: true,
name: 'ፋሲካ',
description: 'የኢየሱስ ክርስቶስን ከሙታን መነሣት ያከብራል።...',
ethiopian: { year: 2017, month: 8, day: 12 },
gregorian: { year: 2025, month: 4, day: 20 }
}
import { getHolidaysForYear, HolidayTags } from 'kenat';
const publicHolidays = getHolidaysForYear(2017, {
filter: HolidayTags.PUBLIC
});
const religiousHolidays = getHolidaysForYear(2017, {
filter: [HolidayTags.CHRISTIAN, HolidayTags.MUSLIM]
});
const meskel = new Kenat('2017/1/17');
console.log(meskel.isHoliday()); // → Returns the Meskel holiday object
const notHoliday = new Kenat('2017/1/18');
console.log(notHoliday.isHoliday()); // → []
const bahireHasab = new Kenat('2017/1/1').getBahireHasab();
console.log(bahireHasab.evangelist);
// → { name: 'ማቴዎስ', remainder: 1 }
console.log(bahireHasab.movableFeasts.fasika.ethiopian);
// → { year: 2017, month: 8, day: 21 }
// Full output of .getBahireHasab() for 2017
{
ameteAlem: 7517,
meteneRabiet: 1879,
evangelist: { name: 'ማቴዎስ', remainder: 1 },
newYear: { dayName: 'ረቡዕ', tinteQemer: 2 },
medeb: 12,
wenber: 11,
abektie: 1,
metqi: 29,
bealeMetqi: { date: { year: 2017, month: 1, day: 29 }, weekday: 'Wednesday' },
mebajaHamer: 3,
nineveh: { year: 2017, month: 6, day: 3 },
movableFeasts: {
nineveh: { /* ... */ },
abiyTsome: { /* ... */ },
fasika: {
key: 'fasika',
tags: ['public', 'religious', 'christian'],
movable: true,
name: 'ፋሲካ',
description: 'የኢየሱስ ክርስቶስን ከሙታን መነሣት ያከብራል።...',
ethiopian: { year: 2017, month: 8, day: 12 },
gregorian: { year: 2025, month: 4, day: 20 }
},
// ... other movable holidays
}
}
The Bahire Hasab engine correctly handles edge-case years where the traditional Metqi calculation lands on 0/30 (e.g. 2006 E.C.), so movable feast dates stay accurate every year.
const today = new Kenat();
const nextWeek = today.addDays(7);
const lastMonth = today.addMonths(-1);
// Fluent API
const future = today.add(2, 'years').add(3, 'months').add(15, 'days');
const a = new Kenat('2016/1/1');
const b = new Kenat('2016/2/5');
a.isBefore(b); // → true
b.isAfter(a); // → true
a.isSameDay(a); // → true
a.isSameMonth(b); // → false (different months)
a.isSameYear(b); // → true (both 2016)
startOf/endOf snap a date to the boundary of its day, month, or year — endOf('year') correctly returns Pagume 6 in leap years and Pagume 5 otherwise:
const date = new Kenat('2016/5/15');
date.startOfMonth().getEthiopian(); // → { year: 2016, month: 5, day: 1 }
date.endOfMonth().getEthiopian(); // → { year: 2016, month: 5, day: 30 }
date.startOf('year').getEthiopian(); // → { year: 2016, month: 1, day: 1 }
new Kenat('2015/5/1').endOf('year').getEthiopian();
// → { year: 2015, month: 13, day: 6 } (2015 is a leap year, so Pagume has 6 days)
const a = new Kenat('2015/5/15');
const b = new Kenat('2012/5/15');
// Precise differences
console.log(a.diffInDays(b)); // → 1095
console.log(a.diffInYears(b)); // → 3
// Human-friendly distance
console.log(a.distanceTo(b, { units: ['years', 'months', 'days'], output: 'string' }));
// → "2 years 9 months 3 days"
// Distance from today to a given date
console.log(new Kenat('2018/1/1').distanceFromToday({ output: 'string' }));
// Monthly calendar
const calendar = Kenat.getMonthCalendar(2017, 1, {
useGeez: false,
weekdayLang: 'amharic'
});
// Yearly calendar
const yearCalendar = Kenat.getYearCalendar(2017);
// Date range generation
const start = new Kenat('2017/1/1');
const end = new Kenat('2017/1/10');
const range = Kenat.generateDateRange(start, end);
// Print the current month as a grid to the console
new Kenat().printThisMonth();
import { getFastingPeriod, getFastingInfo } from 'kenat';
// Get Lent fasting period
const lent = getFastingPeriod('ABIY_TSOME', 2017);
console.log(lent);
// → { start: { year: 2017, month: 7, day: 16 }, end: { year: 2017, month: 8, day: 12 } }
// Get fasting information
const fastingInfo = getFastingInfo('NINEVEH', 2017);
import { HolidayNames } from 'kenat';
// Distance to next holiday
console.log(Kenat.distanceToHoliday(HolidayNames.fasika, {
direction: 'future',
units: ['days'],
output: 'string'
}));
// → "358 days"
import { toGeez } from 'kenat';
console.log(toGeez(2017)); // → "፳፻፲፯"
// Format in Geez
const date = new Kenat('2017/1/1');
console.log(date.formatInGeezAmharic()); // → "መስከረም ፩ ፳፻፲፯"
If your app lets end users choose between the Ethiopian and Gregorian calendar, pass calendar: 'gregorian' to format(), toString(), toISOString(), or getDate() instead of branching with if/else at every call site:
const date = new 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)
// Defaults to 'ethiopian' when omitted
date.format(); // → "መስከረም 1 2016"
toISOString({ calendar: 'gregorian' })converts Kenat's Ethiopian 12-hour time to Gregorian 24-hour time (12:00 day → 06:00) and never appends the non-standard+12hsuffix that the default Ethiopian-calendar ISO string uses for night times — the result is a genuine, parser-safe ISO 8601 string.
// Set Ethiopian time (12-hour system)
const withTime = new Kenat('2017/1/1').setTime(8, 30, 'day');
console.log(withTime.format({ includeTime: true }));
// → "2017/1/1 8:30 day"
// Convert to ISO format
console.log(withTime.toISOString());
// → "2017-01-01T08:30"
Kenat instances serialize cleanly with JSON.stringify via a built-in toJSON():
const date = new Kenat('2017/1/15');
console.log(date.toJSON());
// → {
// ethiopian: { year: 2017, month: 1, day: 15 },
// gregorian: { year: 2024, month: 9, day: 25 },
// time: { hour: 12, minute: 0, period: 'day' }
// }
console.log(JSON.stringify({ createdAt: date }));
// → '{"createdAt":{"ethiopian":{...},"gregorian":{...},"time":{...}}}'
Kenat (Default Export)Main class for Ethiopian date operations.
Constructor:
new Kenat() // Current date
new Kenat('2017/1/1') // Ethiopian date string
new Kenat({year: 2017, month: 1, day: 1}) // Date object
new Kenat(new Date()) // Gregorian Date object
Access & Conversion
getEthiopian() - Returns {year, month, day}getGregorian() - Returns Gregorian equivalentgetDate({ calendar }) - Returns the date in 'ethiopian' (default) or 'gregorian', no if/else neededgetCurrentTime() - Returns the instance's Time objectsetTime(hour, minute, period) - Returns a new instance with the given Ethiopian timeFormatting
format(options) - Format with various options, including lang, showWeekday, useGeez, includeTime, and { calendar: 'ethiopian' | 'gregorian' }toString(options) - String representation of date + time; also accepts { calendar }formatInGeezAmharic() - Amharic month name with Geez numeralsformatWithWeekday(lang, useGeez) - Formatted string including the weekday nameformatShort() - Compact "yyyy/mm/dd" formattoISOString(options) - ISO-style date string; { calendar: 'gregorian' } returns a standard ISO 8601 datetoJSON() - Plain-object representation for JSON.stringifyComparison
isBefore(other) / isAfter(other) - Chronological comparisonisSameDay(other) / isSameMonth(other) / isSameYear(other) - Granular equality checksisLeapYear() - Whether the instance's Ethiopian year is a leap yearweekday() - Day-of-week index (0 = Sunday)Arithmetic & Period Boundaries
add(amount, unit) / subtract(amount, unit) - Chainable arithmetic (unit: 'days'|'months'|'years')addDays(n) / addMonths(n) / addYears(n) - Direct arithmetic shortcutsstartOf(unit) / endOf(unit) - Snap to the start/end of 'day', 'month', or 'year' (leap-year aware)startOfMonth() / endOfMonth() - Shortcuts for startOf('month') / endOf('month')diffInDays(other) / diffInMonths(other) / diffInYears(other) - Precise numeric differencesdistanceTo(other, options) - Human-friendly breakdown or string ({ units, output, lang })distanceFromToday(options) - Distance from today to this instanceHolidays & Liturgical Calendar
isHoliday(options) - Check if the date is a holidaygetBahireHasab() - Get liturgical calculations for the instance's yearCalendar Grids
getMonthCalendar(year, month, useGeez) - Instance-level month grid (defaults to the instance's own month)printThisMonth(useGeez) - Pretty-print the current month's calendar grid to the consoleStatic Helpers
Kenat.now() - Same as new Kenat()Kenat.getMonthCalendar(year, month, options) - Generate a month calendarKenat.getYearCalendar(year, options) - Generate a full year's calendarKenat.generateDateRange(start, end) - Array of Kenat instances between two datesKenat.distanceToHoliday(holidayKey, options) - Distance to a holiday occurrenceKenat.formatDistance(breakdown, options) - Format a distance breakdown as a human stringtoEC(year, month, day) - Gregorian to EthiopiantoGC(year, month, day) - Ethiopian to GregoriantoGeez(number) - Convert to Geez numeralstoArabic(geezString) - Convert from Geez numeralsgetHolidaysForYear(year, options) - Get all holidays for a yeargetHolidaysInMonth(year, month, lang) - Get holidays for a monthgetHoliday(key, year) - Get a specific holidayHolidayTags - Constants for filtering holidaysHolidayNames - Constants for holiday keysgetFastingPeriod(fastKey, year) - Get fasting period datesgetFastingInfo(fastKey, year) - Get fasting informationgetFastingDays(fastKey, year) - Get the list of individual fasting daysgetBahireHasab(year) - Get Bahire Hasab calculationsKenat.getMonthCalendar(year, month, options) - Generate month calendarKenat.getYearCalendar(year, options) - Generate year calendarKenat.generateDateRange(start, end) - Generate date rangeMonthGrid.create(options) - Create calendar griddiffBreakdown(dateA, dateB, options) - Precise date differenceKenat.distanceToHoliday(holidayKey, options) - Distance to holidayHolidayTags - Holiday category tagsHolidayNames - Holiday name constantsmonthNames - Month name arrays in multiple languages (includes an English gregorian set)FastingKeys - Fasting period keysFor detailed method signatures and advanced usage, refer to the full documentation.
Fork the repo & clone it.
Create a new branch:
git checkout -b feature/your-feature
Write your changes (in src/*.ts) and add tests in the /tests directory.
Run npm run typecheck and npm test to ensure everything passes.
If you're changing anything in examples/, run npm run build first — the examples import from dist/, not src/.
Open a Pull Request with your improvements or bug fix.
Melaku Demeke
Special thanks to the Ethiopian Orthodox Tewahedo Church for preserving the traditional Bahire Hasab calculations and to the open-source community for their contributions and feedback.
Thanks to all the amazing people who have contributed to this project!
MIT — see LICENSE for details.
FAQs
A JavaScript library for the Ethiopian calendar with date and time support.
The npm package kenat receives a total of 418 weekly downloads. As such, kenat popularity was classified as not popular.
We found that kenat demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Research
/Security News
A compromised jscrambler npm release added a malicious preinstall hook that runs hidden native binaries on Linux, macOS, and Windows.

Research
/Security News
A malicious .NET package is typosquatting the Braintree SDK to steal live payment card data, merchant API keys, and host secrets from production apps.

Security News
/Research
Compromised Injective SDK npm version 1.20.21 exfiltrates wallet private keys and mnemonics through fake telemetry functionality.