Menu

Home Converter About Holidays

Today's Date

Gregorian Calendar

Ethiopian Calendar

Implementation Details: How the Main Calendar Works

This section documents the user-interface logic used by the Ethiopian Calendar app: how state is stored, how month views are rendered, how holidays are mapped, and how navigation/keyboard controls work.

Author: Anit Kumar Tarafdar

Last reviewed:

1) Overview Flow

On DOMContentLoaded the app calls init(), which:

  1. Computes todayJdn from the device date via gregorianToJdn().
  2. Displays “today” in both GC and ET panels (#gregorian-today, #ethiopian-today).
  3. Initializes calendar state (viewState.etYear, viewState.etMonth) and populates the month/year dropdowns.
  4. Calls renderCalendar() to build the month grid.
  5. Initializes the converter (setupConverter()) and the holiday table (populateHolidayTable()), if those elements exist.
  6. Wires mobile menu open/close and keyboard handlers.

2) State & Constants

3) Rendering Pipeline (renderCalendar())

  1. Sync UI controls: Clears the grid, updates the month/year selects to the current viewState.
  2. Determine GC year span for this ET year: Compute the GC date of Meskerem 1 via jdnToGregorian(ethiopianToJdn(etYear, 1, 1)).yeargregYearStart.
  3. Populate holidays for this ET year window: For each h in HOLIDAYS:
    • If h.type === 'ethiopian', map with ethiopianToJdn(etYear, h.month, h.day).
    • Else (Gregorian-based): use holidayGregYear = (h.month < 9) ? gregYearStart + 1 : gregYearStart.
    • For gregorian-leap, adjust day to h.leap_day when isGregorianLeap(holidayGregYear).
    • Get JDN via gregorianToJdn(holidayGregYear, h.month, holidayGregDay) and store in holidayInfoMap.
  4. Header & month length: Title uses ET month names (Amharic + English). Days in month = 30 (months 1–12); for month 13 (Pagume) it’s 5 or 6 depending on (etYear % 4) === 3.
  5. GC range text: Compute firstDayJdn and lastDayJdn and show the GC month name(s) spanned (#gregorian-span).
  6. Monday-first grid offset:
    const firstDayOfWeek = (firstDayJdn + 1) % 7;  // Sunday=0 indexing
    const startOffset    = (firstDayOfWeek === 0) ? 6 : firstDayOfWeek - 1;
    const startJdn       = firstDayJdn - startOffset;  // backfill previous month cells
  7. Build 42 cells: for i = 0…41, compute:
    const cellJdn      = startJdn + i;
    const cellEtDate   = jdnToEthiopian(cellJdn);
    const cellGregDate = jdnToGregorian(cellJdn);
    
    const isCurrentMonth = (cellEtDate.month === viewState.etMonth);
    const isToday        = (cellJdn === todayJdn);
    const isHoliday      = holidayInfoMap.has(cellJdn);
    Then mount DOM:
    • Container .calendar-cell.calendar-cell-content (rounded/bordered).
    • Style by state: holiday-cell (if holiday + current month), bg-white border-slate-200 (current month), bg-slate-50 border-transparent (adjacent months), and today-cell for today highlight.
    • Date block shows ET day in Ge’ez numerals via toGeez() and GC day as a sub-label.
    • If holiday + current month, append .holiday-names (Amharic + English) and bind click → showToast().

4) Navigation & Static Pages

5) Converter (setupConverter())

6) Holiday Table (populateHolidayTable())

Builds a dual-date table for the current ET year (derived from today’s JDN). It uses the same GC start year rule (Sep–Dec → start year, Jan–Aug → start year + 1) and leap-aware shifts for gregorian-leap entries, outputting ET and GC labels side by side.

7) Toast & Mobile Menu

8) Accessibility & UX Notes

9) Performance Considerations

10) Known Limitations

11) Suggested Tests (Manual QA)