Jalali Calendar Implementation
Relevant source files
The following files were used as context for generating this wiki page:
- app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt
- app/src/main/java/io/github/mojri/hesabyar/ui/screens/JalaliDatePickerDialog.kt
- app/src/main/java/io/github/mojri/hesabyar/rust/RustBridge.kt
- app/src/test/java/io/github/mojri/hesabyar/JalaliCalendarTest.kt
- app/src/test/java/io/github/mojri/hesabyar/JalaliMonthBoundariesTest.kt
- rust/hesabyar-core/README.md
Introduction
The Jalali Calendar Implementation in Hesabyar provides comprehensive support for the Persian (Solar Hijri) calendar system across the entire application. It ensures accurate date conversions, month boundary calculations, and user interface components for date selection. This module is critical for a Persian-first financial application, as all transactions, reports, and reminders rely on Jalali dates for user relevance.
The system utilizes a hybrid architecture: a high-performance native core written in Rust handles primary logic, while a pure-Kotlin fallback ensures the application remains fully functional if the native bridge is unavailable. This dual-path approach guarantees reliability in various environments, from standard Android execution to JVM-only benchmarks.
Sources: README.md:16-24, app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:13-18
Architecture and Core Logic
Native vs. Local Implementation
The system is designed with a decoupled architecture. The JalaliCalendarHelper serves as the primary entry point for the UI and business layers. It attempts to delegate complex calculations to the Rust-based HesabyarCore via a JalaliNativeBridge. If the bridge is not initialized or returns a failure sentinel (e.g., -1), the helper switches to local Kotlin implementations of the algorithms.
The following diagram illustrates the decision flow when a conversion is requested:
The logic prioritizes the Rust core for performance and consistency with potential cross-platform targets, while the Kotlin fallback ensures offline and test-environment resilience.
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:39-44, app/src/main/java/io/github/mojri/hesabyar/rust/RustBridge.kt:18-35
Core Data Structures
The implementation uses specific structures to represent and transfer dates between layers:
| Structure | Type | Description |
|---|---|---|
JalaliDate | Data Class | Stores year, month, and day as integers. Used for UI and local logic. |
PackedDate | Long (i64) | A bit-packed integer format: (year << 16) | (month << 8) | day. Used for FFI. |
Timestamp | Long | UTC Epoch milliseconds. The base for Gregorian comparisons. |
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:47-51, app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:80-86
Date Conversion Algorithms
Gregorian to Jalali
The conversion from Gregorian to Jalali involves calculating the number of days since a fixed epoch (1600-01-01). The Kotlin fallback mirrors the logic found in the Rust core to ensure identical results regardless of the execution path.
- Normalization: Gregorian inputs are validated to prevent silent normalization (e.g., converting February 30th to March 1st).
- Day Calculation: The total number of days elapsed since the epoch is determined, accounting for leap years in the Gregorian cycle.
- Jalali Decomposition: The total day count is divided into Jalali cycles (Birashk cycle) and years to extract the Jalali Y/M/D.
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:100-141, app/src/test/java/io/github/mojri/hesabyar/JalaliCalendarTest.kt:156-163
Leap Year Logic
Leap years in the Jalali calendar follow a 33-year Birashk cycle. A year is a leap year if year % 33 matches one of the following remainders: 1, 5, 9, 13, 17, 22, 26, 30.
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:34-36
Month Lengths
The Jalali calendar has fixed month lengths except for the last month (Esfand):
- Months 1-6 (Farvardin to Shahrivar): 31 days.
- Months 7-11 (Mehr to Bahman): 30 days.
- Month 12 (Esfand): 29 days in common years, 30 days in leap years.
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:246-253
Month Boundary Calculations
The application frequently requires the start and end timestamps for the current Jalali month (e.g., for filtering dashboard data or reports).
This process ensures that transactions are assigned to the correct Jalali month in UTC, preventing discrepancies caused by timezone offsets (such as Iran's UTC+3:30).
Sources: app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:208-233, app/src/test/java/io/github/mojri/hesabyar/JalaliMonthBoundariesTest.kt:113-134
UI Components: Jalali Date Picker
The JalaliDatePickerDialog provides a Material 3 interface for selecting Persian dates.
Key UI Features
- Animated Grid: Transitions between a standard 7x6 day grid and a quick month/year selector.
- Swipe Navigation: Supports horizontal drag gestures to change months.
- Birashk Awareness: Correctly displays 29, 30, or 31 days based on the specific year and month selected.
- Padding Logic: Displays leading and trailing days from adjacent months to maintain a consistent grid layout.
Date Picker Flow
Summary
The Jalali Calendar Implementation is a robust, cross-platform system designed for accuracy and high availability. By combining a Rust-based native core with a Kotlin fallback, it provides reliable financial date management. The system extends beyond simple conversion to include advanced month-boundary logic for analytics and localized UI components for a seamless user experience.
Sources: rust/hesabyar-core/README.md:1-12, app/src/main/java/io/github/mojri/hesabyar/ui/JalaliCalendarHelper.kt:10-22