et_date_picker

A Flutter package that lets users pick dates on the Ethiopian (Ge'ez) calendar. Developers can use either the Ethiopian date or the auto-converted Gregorian date, whichever fits their app.

Screenshots

Day picker Dark theme Year picker
Day picker — light theme Day picker — dark theme with ET/GC preview Year grid picker

Features

  • Full Ethiopian calendar UI with Amharic month names and day-of-week labels
  • Bidirectional date conversion — Ethiopian ↔ Gregorian
  • Ethiopian time conversion (የኢትዮጵያ ሰዓት) — sunrise-based clock with ቀን / ሌሊት periods
  • Correct 13-month calendar including Pagume (5 or 6 days)
  • Optional Ethiopic numeral rendering (፩ ፪ ፫ …) with bundled Noto Sans Ethiopic font
  • Swipeable month navigation with smooth page transitions
  • Year and month grid picker — tap the header to jump to any year or month
  • Dialog presentation via showEthiopianDatePickerDialog
  • initialDate, firstDate, and lastDate accept Gregorian DateTime — converted internally for the Ethiopian calendar UI
  • Responsive dialog sizing — max 360dp width, clamped to fit smaller phones (Material 3 aligned)
  • Localized Cancel / OK button labels via MaterialLocalizations (overridable)
  • Optional ET/GC selected-date preview strip in the dialog
  • Fully themeable — respects your app's ColorScheme
  • Unit tests covering date conversion edge cases including leap years

Getting started

Add the package to your pubspec.yaml:

dependencies:
  et_date_picker: ^0.0.4

Then run:

flutter pub get

Import in your Dart file:

import 'package:et_date_picker/et_date_picker.dart';

Usage

Dialog

final today = DateTime.now();
final result = await showEthiopianDatePickerDialog(
  context: context,
  initialDate: today,
);

if (result != null) {
  print(result.ethiopianDate.toAmharicString());
  print(result.gregorianDate);
}

The dialog returns both calendar representations on confirm. Tap the header to open the year grid, pick a year, then pick a month — or swipe left/right to move one month at a time. Button labels follow your app locale (Cancel / OK in English).

Selected date preview

By default the ET/GC preview strip above the buttons is hidden. Enable it when you want users to see both calendars before confirming:

final result = await showEthiopianDatePickerDialog(
  context: context,
  showSelectedDatePreview: true,
);

Ethiopic numerals

final result = await showEthiopianDatePickerDialog(
  context: context,
  useEthiopicNumerals: true, // renders ፩ ፪ ፫ instead of 1 2 3
);

When useEthiopicNumerals is true, day numbers use the bundled Noto Sans Ethiopic font.

Setting a date range

final result = await showEthiopianDatePickerDialog(
  context: context,
  firstDate: DateTime(2007, 9, 12),
  lastDate: DateTime(2024, 9, 11),
);

initialDate, firstDate, and lastDate accept Gregorian DateTime values. Only the calendar date (year, month, day) is used; the picker converts them internally for the Ethiopian calendar UI.

Custom theme

final result = await showEthiopianDatePickerDialog(
  context: context,
  theme: EthiopianDatePickerTheme(
    dayTextStyle: TextStyle(color: Colors.black87),
    todayTextStyle: TextStyle(color: Colors.green.shade400),
    selectedDayTextStyle: TextStyle(
      color: Colors.white,
      backgroundColor: Colors.green.shade800,
      fontWeight: FontWeight.bold,
    ),
    confirmButtonStyle: ButtonStyle(
      backgroundColor: WidgetStatePropertyAll(Colors.green.shade800),
      foregroundColor: WidgetStatePropertyAll(Colors.white),
    ),
  ),
);

Unset theme properties fall back to the ambient ThemeData / ColorScheme.

Date conversion only (no UI)

// Gregorian → Ethiopian
final etDate = EtDateConverter.toEthiopian(DateTime(2024, 10, 19));
print(etDate.toAmharicString()); // 9 ጥቅምት 2017
print(etDate.monthName);         // ጥቅምት
print(etDate.year);              // 2017
print(etDate.month);             // 2
print(etDate.day);               // 9

// Ethiopian → Gregorian
final gcDate = EtDateConverter.toGregorian(
  EthiopianDate(year: 2017, month: 2, day: 9),
);
print(gcDate); // 2024-10-19

// Today in Ethiopian calendar
final today = EtDateConverter.today();

Time conversion

Ethiopian time counts from sunrise — standard 6:00 AM equals ET 12:00 ቀን.

// Standard DateTime → Ethiopian time
final etTime = EtDateConverter.toEthiopianTime(DateTime.now());
print(etTime.format()); // e.g. "3:30 ቀን"

// From explicit hour/minute/second (24-hour standard clock)
final etTime2 = EtDateConverter.toEthiopianTimeFromParts(
  hour: 15,
  minute: 30,
);

// Combined date + time
final etDateTime = EtDateConverter.toEthiopianDateTime(DateTime.now());
print(etDateTime.format()); // e.g. "9 ጥቅምት 2017 3:30 ቀን"

// Back to standard 24-hour clock
final standardHour = EtDateConverter.toStandardHour(etTime);

Ethiopian calendar basics

The Ethiopian calendar (Ge'ez calendar) differs from the Gregorian calendar in a few key ways:

Ethiopian Gregorian
Months 13 12
Days per month 30 (fixed) 28–31
13th month (Pagume) 5 days (6 in leap year)
New Year ~11 Sep (12 Sep after leap year) 1 Jan
Year difference ~7–8 years behind

Month names

# Amharic Gregorian equivalent
1 መስከረም (Meskerem) Sep – Oct
2 ጥቅምት (Tikimt) Oct – Nov
3 ኅዳር (Hidar) Nov – Dec
4 ታኅሣሥ (Tahsas) Dec – Jan
5 ጥር (Tir) Jan – Feb
6 የካቲት (Yekatit) Feb – Mar
7 መጋቢት (Megabit) Mar – Apr
8 ሚያዝያ (Miazia) Apr – May
9 ግንቦት (Ginbot) May – Jun
10 ሰኔ (Sene) Jun – Jul
11 ሐምሌ (Hamle) Jul – Aug
12 ነሐሴ (Nehase) Aug – Sep
13 ጳጉሜ (Pagume) Sep (5–6 days)

API reference

EthiopianDate

EthiopianDate({
  required int year,
  required int month,  // 1–13
  required int day,    // 1–30 (1–6 for Pagume)
})
Property / Method Description
monthName Amharic name of the month
daysInMonth 30, or 5/6 for Pagume
isPagume Whether this is the 13th month
firstDayOfMonth Same year/month, day 1
lastDayOfMonth Same year/month, last valid day
copyWith(...) Returns a copy with updated fields
addMonths(int n) Navigate forward/backward by months
toAmharicString() e.g. "9 ጥቅምት 2017"
isEthiopianLeapYear(int year) Static — true when year % 4 == 3

EtDateConverter

Method Description
toEthiopian(DateTime) Gregorian → Ethiopian date
toGregorian(EthiopianDate) Ethiopian → Gregorian date
today() Current date as EthiopianDate
isSameDay(DateTime, DateTime) Day equality check
isLeapYear(int year) Whether an Ethiopian year is a leap year
toEthiopianTime(DateTime) Standard time → EthiopianTime
toEthiopianTimeFromParts({hour, minute, second}) Build EthiopianTime from clock parts
toStandardHour(EthiopianTime) Ethiopian time → standard 24-hour hour
toEthiopianDateTime(DateTime) Combined EthiopianDateTime

EthiopianTime

Property / Method Description
hour 12-hour dial value (1–12)
minute, second Same as standard clock
isDay, isNight Whether the period is ቀን or ሌሊት
periodLabel "ቀን" or "ሌሊት"
format() e.g. "3:30 ቀን"
formatWithSeconds() e.g. "3:30:45 ቀን"

showEthiopianDatePickerDialog

Future<EthiopianPickerResult?> showEthiopianDatePickerDialog({
  required BuildContext context,
  DateTime? initialDate,
  DateTime? firstDate,
  DateTime? lastDate,
  bool useEthiopicNumerals = false,
  bool showSelectedDatePreview = false,
  EthiopianDatePickerTheme? theme,
  String? cancelText,
  String? confirmText,
  double width = 360, // max width; clamped to available screen space
  EdgeInsets insetPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 24),
  ShapeBorder? shape,
})

width defaults to 360 (Flutter Material 3 date picker size) but is automatically clamped to fit smaller screens. insetPadding matches Flutter's Material date picker defaults.

cancelText and confirmText are optional. When omitted, they follow the app locale via MaterialLocalizations — the same behavior as Flutter's showDatePicker (Cancel / OK in English). For Amharic buttons:

await showEthiopianDatePickerDialog(
  context: context,
  cancelText: 'ይቅር',
  confirmText: 'ምረጥ',
);

Returns null if the user cancels. On confirm, returns both calendar representations:

result.ethiopianDate  // EthiopianDate
result.gregorianDate  // DateTime

EthiopianCalendarController

EthiopianCalendarController({
  DateTime? initialDate,
  EthiopianDate? initialEthiopianDate,
})

Provide either initialDate (Gregorian) or initialEthiopianDate, not both. Use the controller with EthiopianDatePicker for programmatic navigation and selection.

EthiopianDatePicker

Embed the picker directly in your UI (without the dialog wrapper):

EthiopianDatePicker(
  initialDate: DateTime(2024, 10, 19),
  firstDate: DateTime(2000, 1, 1),
  lastDate: DateTime(2030, 12, 31),
  onDateSelected: (ethiopianDate, gregorianDate) {
    // ...
  },
)

initialDate, firstDate, and lastDate are Gregorian DateTime values, same as the dialog API.

Dialog sizing constants

Name Value Description
kEthiopianDatePickerMaxDialogWidth 360 Max dialog width (Material 3)
kEthiopianDatePickerInsetPadding 16×24 Default screen inset padding
resolveEthiopianDatePickerDialogWidth() Clamps width to available screen space

EthiopianDatePickerTheme

Key styling hooks: backgroundColor, headerTextStyle, dowTextStyle, dayTextStyle, todayTextStyle, selectedDayTextStyle, disabledDayTextStyle, outsideDayTextStyle, selectedPreviewLabelStyle, selectedPreviewValueStyle, confirmButtonStyle, cancelButtonStyle, dayCellHeight, dayCellMargin.

Use selectedDayTextStyle.backgroundColor for the selected-day circle fill and todayTextStyle.color for today's border ring.


Upgrading from 0.0.2

initialDate, firstDate, and lastDate now take Gregorian DateTime instead of EthiopianDate.

// Before (0.0.2)
initialDate: EthiopianDate(year: 2017, month: 2, day: 9),
firstDate: EthiopianDate(year: 2015, month: 1, day: 1),

// After (0.0.3)
initialDate: DateTime(2024, 10, 19),
firstDate: DateTime(2007, 9, 12),

If you already have an EthiopianDate, convert it with EtDateConverter.toGregorian(etDate). If you have a previous picker result, use result.gregorianDate directly.

Upgrading from 0.0.3

  • cancelLabel / confirmLabel were renamed to cancelText / confirmText and are now optional. Omit them to use localized Cancel / OK labels.
// Before (0.0.3)
cancelLabel: 'Cancel',
confirmLabel: 'OK',

// After (0.0.4) — localized by default
// or override explicitly:
cancelText: 'ይቅር',
confirmText: 'ምረጥ',
  • The ET/GC preview strip is now opt-in via showSelectedDatePreview: true (default false).
  • Dialog insetPadding default changed from 24×40 to 16×24 (Material aligned). Dialog width is clamped on narrow screens.

Contributing

Contributions are welcome. Please open an issue before submitting a pull request for significant changes.

When adding features, ensure:

  • Conversion logic is covered by unit tests in test/et_date_picker_test.dart
  • The example app demonstrates the new feature

License

MIT License — see LICENSE for details.

Libraries

et_date_picker