ethio_greg_dual_calendar 0.1.1 copy "ethio_greg_dual_calendar: ^0.1.1" to clipboard
ethio_greg_dual_calendar: ^0.1.1 copied to clipboard

Dual-calendar dates and calendar UIs for Flutter in both the Gregorian and Ethiopian (Amete Mihret) calendars, localized in English, Amharic, and Afaan Oromoo.

ethio_greg_dual_calendar #

A Flutter dual-calendar package: render dates and full calendar UIs in either the Gregorian or the Ethiopian (Amete Mihret) calendar, with month and weekday names localized in multiple languages (English, Amharic, Afaan Oromoo — easily extended).

This project is an extraction of the calendar system originally built for a church app, where users can switch both their language and their calendar system at runtime and every rendered date in the app follows along automatically.

Why #

Most Flutter calendar widgets assume the Gregorian calendar. In Ethiopia the Ethiopian calendar is the everyday calendar: 13 months — twelve of 30 days plus Pagumē, a short 13th month of 5 days (6 in a leap year) — with the year running ~7–8 years behind the Gregorian year and starting on Meskerem 1 (≈ September 11). Wrapping a Gregorian grid in Ethiopian labels is not enough: the month boundaries, day numbers, year arithmetic, and month count are all different. This package makes the calendar system a first-class, switchable setting.

Features #

1. Accurate Gregorian ↔ Ethiopian conversion (EthiopianDate) #

  • Pure-Dart conversion through the Julian Day Number (JDN) using the Amete Mihret epoch — no platform channels, no dependencies.
  • EthiopianDate.fromGregorian(DateTime) and EthiopianDate.toGregorian(year, month, day) are exact inverses; verified with zero mismatches across 1900–2100 against the reference abushakir package, including leap-year boundaries.
  • Leap-year rule built in (year % 4 == 3 → Pagumē has 6 days).
  • Sample anchors: 2026-07-12 → 2018/11/05 (Hamle 5), 2024-09-11 → 2017/01/01 (Meskerem 1, Ethiopian New Year), 2000-01-01 → 1992/04/22 (Tahsas 22).

2. Calendar- and locale-aware formatting (AppDate) #

One static API the whole app uses for dates. It reads the active calendar system and the active locale, so callers never branch on either:

AppDate.calendar = CalendarSystem.ethiopian; // kept in sync by settings

AppDate.mediumDate(date);        // "Aug 9, 2024"  /  "ነሐሴ 3, 2016"
AppDate.weekdayMonthDay(date);   // "Friday, Aug 9" (weekday is the same physical day)
AppDate.monthYear(date);         // "August 2024"   /  "ነሐሴ 2016"
AppDate.dayOfMonth(date);        // 9 (Gregorian)   /  3 (Ethiopian)
AppDate.relativeOrWeekdayMonthDay(date); // "Today" / "Yesterday" / "Tomorrow" / fallback

Plus calendar-aware month arithmetic, which is where the two systems really diverge (12 vs 13 months per year):

AppDate.monthDays(date);          // all days of the month containing `date`,
                                  // as Gregorian DateTimes — 30 cells for an
                                  // Ethiopian month, 5–6 for Pagumē, 28–31 Gregorian
AppDate.monthStart(date, -1);     // first day of the previous month, crossing
                                  // year boundaries (Pagumē → Meskerem)
AppDate.previousMonthStart(date); // null at the calendar-year edge
AppDate.nextMonthStart(date);     // null in December / Pagumē
AppDate.isSameMonth(a, b);        // same month in the *active* calendar

Key design decision: DateTime (Gregorian) stays the single storage and transport type everywhere — the Ethiopian calendar exists only at the formatting/layout layer. This keeps persistence, APIs, and comparisons simple and makes switching calendars a pure re-render.

3. Calendar widgets #

  • MonthCalendar — a month grid that is genuinely native to the active calendar: an Ethiopian month renders as 30 cells numbered 1–30 (or Pagumē's 5–6), with Ethiopian month/year in the header — not a Gregorian month wearing Ethiopian labels. Weekday columns are shared since a given day falls on the same weekday in both calendars. Supports:
    • full-month and two-week (twoWeeks) formats, as swipeable pages,
    • firstDate/lastDate range clamping of swipes and the ◀ ▶ navigation,
    • per-day event markers (eventCountForDay, capped dots under each cell),
    • today highlight + selected-day outline,
    • table_calendar-style customization: CalendarStyle, HeaderStyle, DaysOfWeekStyle restyle the built-in pieces (per-state text styles and decorations, marker dots, chevrons, title/label formatters), and CalendarBuilders replaces any of them outright (todayBuilder, selectedBuilder, markerBuilder, dowBuilder, headerTitleBuilder, …). Style fields default to null = "derive from the app's ThemeData", so an unstyled calendar keeps the original theme-adaptive look. Plus startingDayOfWeek (any weekday, Sunday default) and weekendDays.
  • WeekLongCalendar / DayButton — a horizontal, scrollable day strip over the active calendar's month, with auto-scroll to the selected day.
  • Month/year selector widgets that step by calendar months (13 in an Ethiopian year).

4. Multi-language month & weekday names #

All names live in CalendarL10n, a plain const value object — no codegen, and hosts can supply their own strings by constructing one instance:

  • monthsLong / monthsShort — Gregorian month names per locale
  • ethMonths — the 13 Ethiopian month names per locale (Meskerem, Tikimt, Hidar, Tahsas, Tir, Yekatit, Megabit, Miyazia, Ginbot, Sene, Hamle, Nehase, Pagumē)
  • weekdaysLong / weekdaysShort, plus today / yesterday / tomorrow and the calendar-setting labels used by CalendarSelector

Built-in locales: English (CalendarL10n.en), Amharic (CalendarL10n.am), Afaan Oromoo (CalendarL10n.om), and Tigrinya (CalendarL10n.ti — pending native-speaker review**)**, also reachable by code via CalendarL10n.forLocale('am'). Assign the active instance to AppDate.l10n alongside the host's locale switching. Adding a language is constructing one CalendarL10n.

Day numbers can optionally render in Ge'ez numerals (፩ ፲ ፴) via EthioCalendarConfig(geezDayNumbers: true); GeezNumerals.format(2016) (→ ፳፻፲፮) is also exported on its own.

5. Runtime switching #

The calendar system is a user setting (CalendarSystem.gregorian | CalendarSystem.ethiopian), persisted alongside language in the host app's settings and exposed through a simple dropdown (CalendarSelector — a dumb value/onChanged widget, so any state management works). Keep AppDate.calendar / AppDate.l10n in sync with the settings and rebuild the widget tree when they change, and every rendered date in the app re-resolves automatically — no manual refresh plumbing.

6. Injectable configuration (EthioCalendarConfig / EthioCalendarScope) #

All formatting and month arithmetic lives on an immutable EthioCalendarConfig (calendar + strings + options); the static AppDate API delegates to a global instance. For subtrees that need a different calendar or language than the rest of the app — e.g. both systems side by side — wrap them in an EthioCalendarScope; this package's widgets resolve the nearest scope and fall back to the global config:

EthioCalendarScope(
  config: const EthioCalendarConfig(
    calendar: CalendarSystem.ethiopian,
    l10n: CalendarL10n.am,
  ),
  child: MonthCalendar(...),
)

Architecture #

lib/
├── ethio_calendar.dart        # public exports
└── src/
    ├── ethiopian_date.dart    # pure conversion math (JDN ↔ Ethiopian)
    ├── config.dart            # EthioCalendarConfig: formatting & month arithmetic
    ├── app_date.dart          # static facade over the global config
    ├── scope.dart             # EthioCalendarScope (per-subtree override)
    ├── geez_numerals.dart     # ፩ ፲ ፴ … formatting
    ├── types.dart             # CalendarSystem enum
    ├── l10n/
    │   └── calendar_l10n.dart # months, ethMonths, weekdays, relative words (en/am/om/ti)
    └── widgets/
        ├── month_calendar.dart     # month grid (full / two-week)
        ├── week_long_calendar.dart # horizontal day strip
        ├── day_button.dart         # one day in the strip
        ├── month_selector.dart     # ◀ month ▶ stepper (13 months/yr in Ethiopian)
        └── calendar_selector.dart  # settings dropdown

Layering rule: conversion math → formatting API → widgets, each layer usable on its own. You can use EthiopianDate alone in a backend, AppDate without the widgets, or the widgets as a drop-in UI.

Quick start #

// 1. Pick the calendar and language (e.g. from user settings)
AppDate.calendar = CalendarSystem.ethiopian;
AppDate.l10n = CalendarL10n.am;

// 2. Format any DateTime
Text(AppDate.mediumDate(DateTime.now())); // "ሐምሌ 6, 2018"

// 3. Show a month grid
MonthCalendar(
  focusedDate: focused,
  selectedDate: selected,
  firstDate: DateTime(2020),
  lastDate: DateTime(2030),
  onDaySelected: (day) => setState(() => selected = day),
  onFocusedDateChanged: (month) => setState(() => focused = month),
  eventCountForDay: (day) => eventsOn(day).length,
)

License #

MIT — see LICENSE.

3
likes
160
points
93
downloads
screenshot

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Dual-calendar dates and calendar UIs for Flutter in both the Gregorian and Ethiopian (Amete Mihret) calendars, localized in English, Amharic, and Afaan Oromoo.

Repository (GitHub)
View/report issues

License

MIT (license)

Dependencies

flutter

More

Packages that depend on ethio_greg_dual_calendar