@@ -116,6 +116,163 @@ pub fn get_localized_day_name(year: i32, month: u8, day: u8, full: bool) -> Stri
116116 formatted. trim ( ) . to_string ( )
117117}
118118
119+ /// Determine the appropriate calendar system for a given locale
120+ pub fn get_locale_calendar_type ( locale : & Locale ) -> CalendarType {
121+ let locale_str = locale. to_string ( ) ;
122+
123+ match locale_str. as_str ( ) {
124+ // Thai locales use Buddhist calendar
125+ s if s. starts_with ( "th" ) => CalendarType :: Buddhist ,
126+ // Persian/Farsi locales use Persian calendar (Solar Hijri)
127+ s if s. starts_with ( "fa" ) => CalendarType :: Persian ,
128+ // Amharic (Ethiopian) locales use Ethiopian calendar
129+ s if s. starts_with ( "am" ) => CalendarType :: Ethiopian ,
130+ // Default to Gregorian for all other locales
131+ _ => CalendarType :: Gregorian ,
132+ }
133+ }
134+
135+ /// Calendar types supported for locale-aware formatting
136+ #[ derive( Debug , Clone , PartialEq ) ]
137+ pub enum CalendarType {
138+ /// Gregorian calendar (used by most locales)
139+ Gregorian ,
140+ /// Buddhist calendar (Thai locales) - adds 543 years to Gregorian year
141+ Buddhist ,
142+ /// Persian Solar Hijri calendar (Persian/Farsi locales) - subtracts 621/622 years
143+ Persian ,
144+ /// Ethiopian calendar (Amharic locales) - subtracts 7/8 years
145+ Ethiopian ,
146+ }
147+
148+ /// Convert a Gregorian date to the appropriate calendar system for a locale
149+ ///
150+ /// # Arguments
151+ /// * `year` - Gregorian year
152+ /// * `month` - Month (1-12)
153+ /// * `day` - Day (1-31)
154+ /// * `calendar_type` - Target calendar system
155+ ///
156+ /// # Returns
157+ /// * `Some((era_year, month, day))` - Date in target calendar system
158+ /// * `None` - If conversion fails
159+ pub fn convert_date_to_locale_calendar (
160+ year : i32 ,
161+ month : u8 ,
162+ day : u8 ,
163+ calendar_type : & CalendarType ,
164+ ) -> Option < ( i32 , u8 , u8 ) > {
165+ match calendar_type {
166+ CalendarType :: Gregorian => Some ( ( year, month, day) ) ,
167+ CalendarType :: Buddhist => {
168+ // Buddhist calendar: Gregorian year + 543
169+ Some ( ( year + 543 , month, day) )
170+ }
171+ CalendarType :: Persian => {
172+ // Persian calendar conversion (Solar Hijri)
173+ // This is complex - for now, approximate conversion
174+ // March 21 (Nowruz) is roughly the start of the Persian year
175+ let persian_year = if month >= 3 && day >= 21 {
176+ year - 621 // After March 21
177+ } else {
178+ year - 622 // Before March 21
179+ } ;
180+ Some ( ( persian_year, month, day) )
181+ }
182+ CalendarType :: Ethiopian => {
183+ // Ethiopian calendar conversion
184+ // September 11/12 is roughly the start of the Ethiopian year
185+ let ethiopian_year = if month >= 9 && day >= 11 {
186+ year - 7 // After September 11
187+ } else {
188+ year - 8 // Before September 11
189+ } ;
190+ Some ( ( ethiopian_year, month, day) )
191+ }
192+ }
193+ }
194+
195+ /// Get the era year for a given date and locale
196+ pub fn get_era_year ( year : i32 , month : u8 , day : u8 , locale : & Locale ) -> Option < i32 > {
197+ // Validate input date
198+ if !( 1 ..=12 ) . contains ( & month) || !( 1 ..=31 ) . contains ( & day) {
199+ return None ;
200+ }
201+
202+ let calendar_type = get_locale_calendar_type ( locale) ;
203+ match calendar_type {
204+ CalendarType :: Gregorian => None ,
205+ _ => convert_date_to_locale_calendar ( year, month, day, & calendar_type)
206+ . map ( |( era_year, _, _) | era_year) ,
207+ }
208+ }
209+
210+ /// Get era-specific month names for a given calendar type and month
211+ pub fn get_era_month_name (
212+ month : u8 ,
213+ full : bool ,
214+ calendar_type : & CalendarType ,
215+ locale : & Locale ,
216+ ) -> Option < String > {
217+ match calendar_type {
218+ CalendarType :: Buddhist => {
219+ // Get Thai Buddhist month names
220+ get_localized_month_name_for_calendar ( month, full, locale, calendar_type)
221+ }
222+ CalendarType :: Persian => {
223+ // Get Persian Solar Hijri month names
224+ get_localized_month_name_for_calendar ( month, full, locale, calendar_type)
225+ }
226+ CalendarType :: Ethiopian => {
227+ // Get Ethiopian month names
228+ get_localized_month_name_for_calendar ( month, full, locale, calendar_type)
229+ }
230+ CalendarType :: Gregorian => {
231+ // Use standard Gregorian month names
232+ None
233+ }
234+ }
235+ }
236+
237+ /// Get localized month names for specific calendar systems
238+ fn get_localized_month_name_for_calendar (
239+ month : u8 ,
240+ full : bool ,
241+ _locale : & Locale ,
242+ calendar_type : & CalendarType ,
243+ ) -> Option < String > {
244+ // For Thai Buddhist calendar - check if months ending in 31 days have "คม" suffix
245+ if calendar_type == & CalendarType :: Buddhist && matches ! ( month, 1 | 3 | 5 | 7 | 8 | 10 | 12 ) {
246+ let month_name = get_localized_month_name ( month, full) ;
247+ if !month_name. is_empty ( ) {
248+ // Verify the month name ends with "คม" for 31-day months in Thai
249+ let suffix = "\u{0E04} \u{0E21} " ; // "คม"
250+ if month_name. ends_with ( suffix) {
251+ return Some ( month_name) ;
252+ }
253+ }
254+ }
255+
256+ // For other calendar systems, try to get standard localized names
257+ let month_name = get_localized_month_name ( month, full) ;
258+ if month_name. is_empty ( ) {
259+ None
260+ } else {
261+ Some ( month_name)
262+ }
263+ }
264+
265+ // Fix the match arm - Buddhist should be CalendarType::Buddhist
266+ impl CalendarType {
267+ /// Check if this calendar uses era-based years that differ from Gregorian
268+ pub fn uses_era ( & self ) -> bool {
269+ match self {
270+ Self :: Gregorian => false ,
271+ Self :: Buddhist | Self :: Persian | Self :: Ethiopian => true ,
272+ }
273+ }
274+ }
275+
119276#[ cfg( test) ]
120277mod tests {
121278 use super :: * ;
@@ -128,4 +285,47 @@ mod tests {
128285 // The caller (date.rs) will handle this by falling back to jiff
129286 assert ! ( name. is_empty( ) || name. len( ) >= 3 ) ;
130287 }
288+
289+ #[ test]
290+ fn test_calendar_type_detection ( ) {
291+ let thai_locale = icu_locale:: locale!( "th-TH" ) ;
292+ let persian_locale = icu_locale:: locale!( "fa-IR" ) ;
293+ let amharic_locale = icu_locale:: locale!( "am-ET" ) ;
294+ let english_locale = icu_locale:: locale!( "en-US" ) ;
295+
296+ assert_eq ! (
297+ get_locale_calendar_type( & thai_locale) ,
298+ CalendarType :: Buddhist
299+ ) ;
300+ assert_eq ! (
301+ get_locale_calendar_type( & persian_locale) ,
302+ CalendarType :: Persian
303+ ) ;
304+ assert_eq ! (
305+ get_locale_calendar_type( & amharic_locale) ,
306+ CalendarType :: Ethiopian
307+ ) ;
308+ assert_eq ! (
309+ get_locale_calendar_type( & english_locale) ,
310+ CalendarType :: Gregorian
311+ ) ;
312+ }
313+
314+ #[ test]
315+ fn test_era_year_conversion ( ) {
316+ let thai_locale = icu_locale:: locale!( "th-TH" ) ;
317+ let persian_locale = icu_locale:: locale!( "fa-IR" ) ;
318+ let amharic_locale = icu_locale:: locale!( "am-ET" ) ;
319+
320+ // Test Thai Buddhist calendar (2026 + 543 = 2569)
321+ assert_eq ! ( get_era_year( 2026 , 6 , 15 , & thai_locale) , Some ( 2569 ) ) ;
322+
323+ // Test Persian calendar (rough approximation)
324+ assert_eq ! ( get_era_year( 2026 , 3 , 22 , & persian_locale) , Some ( 1405 ) ) ;
325+ assert_eq ! ( get_era_year( 2026 , 3 , 19 , & persian_locale) , Some ( 1404 ) ) ;
326+
327+ // Test Ethiopian calendar (rough approximation)
328+ assert_eq ! ( get_era_year( 2026 , 9 , 12 , & amharic_locale) , Some ( 2019 ) ) ;
329+ assert_eq ! ( get_era_year( 2026 , 9 , 10 , & amharic_locale) , Some ( 2018 ) ) ;
330+ }
131331}
0 commit comments