Get a gregorian date from Hijri date strings

╄→尐↘猪︶ㄣ 提交于 2019-11-29 08:01:44

I regret that you were ill advised. Many people blindly recommend well-known libraries like Joda-Time without even checking if this can solve your problem and fit your requirements.

Which requirement? You have stated:

value tuple (1436, "SHawwal", 18) => gregorian date as String ("3rd August 2015")

Let's first demonstrate that Joda-Time is not capable of doing this conversion. Shawwal ("شَوّال") is the tenth month of islamic lunar calendar. So we can try this Joda-Time code:

Chronology hijri = 
  IslamicChronology.getInstance(DateTimeZone.UTC, IslamicChronology.LEAP_YEAR_INDIAN);
LocalDate dtHijri = new LocalDate(1436, 10, 18, hijri);

Chronology iso = ISOChronology.getInstanceUTC();
LocalDate dtIso = // Joda does not offer a direct conversion, sorry
  dtHijri.toDateTimeAtStartOfDay(DateTimeZone.UTC)
  .withChronology(iso).toLocalDate(); 
System.out.println("hijri-to-gregorian: " + dtHijri + "=>" + dtIso); 
// hijri-to-gregorian: 1436-10-18=>2015-08-04

All leap year patterns offered by Joda-Time (LEAP_YEAR_15_BASED, LEAP_YEAR_16_BASED, LEAP_YEAR_HABASH_AL_HASIB or LEAP_YEAR_INDIAN) yield the same result "2015-08-04" which is one day later than your expectation. And I have explicitly tested all four leap year patterns.

Is Java-8 an alternative?

HijrahDate hd = HijrahChronology.INSTANCE.date(HijrahEra.AH, 1436, 10, 18);
LocalDate ld = LocalDate.from(hd);
System.out.println("java-8: " + ld); // java-8: 2015-08-03

That is fine, but I assume you need to make this work on Android. And there you would fail because the Java-8-classes are simply not available.

By the way: Why does Java-8 yield the right result here? It uses the data of the Umalqura-calendar in Saudi-Arabia while Joda-Time uses four half-academic approximations of islamic calendar. In fact, the islamic calendar exists in many local variants (and my comment above had targeted this point to ask you which variant you want to use).

So will ThreetenABP do it since it is supposed to be a backport of java.time (JSR-310) on Android?

HijrahDate hd = HijrahChronology.INSTANCE.date(HijrahEra.AH, 1436, 10, 18);
org.threeten.bp.LocalDate ld = org.threeten.bp.LocalDate.from(hd);
System.out.println("threeten-bp: " + ld); // threeten-bp: 2015-08-04

Surprisingly the backport fails. If you watch out the documentations of ThreetenBP and Java-8 regarding the Hijri calendar then you will see a big difference. ThreetenBP does NOT use the Umalqura calendar! It is de facto equivalent to the leap year pattern LEAP_YEAR_16_BASED of Joda-Time. Well, as (awkward) workaround you could configure ThreetenBP such that you tell the deviations by defining a configuration file described here. But I strongly suspect this would only work on Java-platforms because ThreetenABP (the Android migration of ThreetenBP) does not care about defining such a file in the assets-directory of a given Android app.

What about ICU4J?

ICU4J (International components for Unicode) seems to work, at least with three variants: ISLAMIC, ISLAMIC_TBLA and ISLAMIC_UMALQURA. All 3 variants yield "2015-08-03". So you have to carefully decide which one to use because these variants will differ on other days! About the TBLA-variant, that is a half-academic approximation similar to LEAP_YEAR_16_BASED of Joda-Time (but using Thursday epoch - one day difference all the time). The variant called ISLAMIC is a simplified astronomical simulation of moon cycles. My suspicion you rather want the Umalqura-calendar of Saudi-Arabia, right???

Anyway, ICU4J has one big disadvantage. It is not well designed for the Android platform and very big (10.7 MByte). Some people have reported to have not been able to run that software on Android.

My recommendation: Use Time4A

The result of my analysis of current support for Hijri calendar on Android platform has been to develop an alternative library called Time4A to help people using the Hijri calendar on that platform, too. This library (version v3.6-2015f) supports 8 half-academic leap year patterns including the tbla-variant (HijriAlgorithm.WEST_ISLAMIC_ASTRO) , the Umalqura-calendar of Saudi-Arabia and the astronomical simulation of ICU4J. And important: Time4A will also understand Arabic month names (using new Locale("ar")). Example:

HijriCalendar hijri = 
  HijriCalendar.of(HijriCalendar.VARIANT_UMALQURA, 1436, HijriMonth.SHAWWAL, 18);
PlainDate iso = hijri.transform(PlainDate.class);
System.out.println("Time4A: " + iso); // Time4A: 2015-08-03

Now the final code solution handling some oddities in your requirements:

int hijriYear = 1436;
String hijriMonth = "SHawwal";
int hijriDayOfMonth = 18;

ChronoFormatter<HijriCalendar> inputFormat =
        ChronoFormatter.setUp(HijriCalendar.class, Locale.ENGLISH)
        .addPattern("yyyyMMMMd", PatternType.NON_ISO_DATE).build();
HijriCalendar hijriDate = 
        inputFormat
        .with(Attributes.PARSE_CASE_INSENSITIVE, true)
        .withCalendarVariant(HijriCalendar.VARIANT_UMALQURA)
        .parse(hijriYear + hijriMonth + hijriDayOfMonth);

PlainDate iso = hijriDate.transform(PlainDate.class);

ChronoFormatter<PlainDate> outputFormat =
    ChronoFormatter.setUp(PlainDate.class, Locale.ENGLISH)
        .addEnglishOrdinal(PlainDate.DAY_OF_MONTH)
        .addPattern(" MMMM uuuu", PatternType.CLDR).build();
String s = outputFormat.format(iso);
System.out.println(s); // 3rd August 2015

But you still need to think about which variant of Hijri calendar you really want. No intelligent software can decide this for you.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!