回曆日期(烏姆‧庫拉曆,Umm al-Qura)
沙烏地阿拉伯的官方回曆為烏姆‧庫拉曆(أم القرى),這是一種透過計算得出的曆法。其他「伊斯蘭」曆法使用不同的規則,往往會相差一天,因此在程式碼與使用者介面中務必明確標註所使用的曆法。
規則
- 儲存 ISO 公曆日期(如
2026-11-03、DATE欄位或 UTC 時間戳記)。僅在顯示時轉換為回曆。當使用者輸入回曆日期時,請在輸入時將其轉換為公曆並儲存。 - 始終標註顯示日期所使用的曆法:如「23 جمادى الأولى 1448هـ」和「3 نوفمبر 2026م」。
- 回曆月份有 29 或 30 天,一年則有 354 或 355 天。切勿使用固定的月份天數,或為了「下個月」而「加 30 天」。
- 形如
1448-05-23的回曆字串並非 ISO 8601 格式。切勿將其傳遞給new Date();它會被誤解為西元 1448 年。 - 宗教日期(齋戒月開始、開齋節)由新月觀測宣告,可能與印刷的烏姆‧庫拉曆日期不同。為這些計算出的日期標註「حسب تقويم أم القرى」或「متوقع」,切勿標註為確定無誤。
JavaScript:使用 Intl 進行格式化
const d = new Date(Date.UTC(2026, 10, 3)); // 2026年11月3日
new Intl.DateTimeFormat('ar-SA-u-ca-islamic-umalqura', { dateStyle: 'full', timeZone: 'UTC' }).format(d);
// "الثلاثاء، ٢٣ جمادى الأولى ١٤٤٨ هـ"
new Intl.DateTimeFormat('ar-SA-u-ca-islamic-umalqura-nu-latn',
{ day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' }).format(d);
// "23 جمادى الأولى 1448 هـ"
new Intl.DateTimeFormat('en-u-ca-islamic-umalqura-nu-latn',
{ day: 'numeric', month: 'long', year: 'numeric', timeZone: 'UTC' }).format(d);
// "Jumada I 23, 1448 AH"
- 務必明確寫出
ca-islamic-umalqura。不要依賴於ar-SA就代表回曆:在最新的 ICU/CLDR(Node 24、當前 Chrome)中,ar-SA的預設曆法是公曆。 - 使用
nu-latn(0-9)或nu-arab(٠-٩)明確設定數字格式。預設值因地區與版本而異:ar-SA給出 ٠-٩,而普通的ar現在給出 0-9。 - 確切的字串(「هـ」前的空格、「AH」、是「Jumada I」還是「Jumada al-Ula」)會因引擎而異。切勿解析格式化後的文字,也切勿在不同環境中進行快照測試。請改用
formatToParts。 - 時區決定是哪一天。
new Date('2026-11-03')是 UTC 午夜,因此請使用timeZone: 'UTC'進行格式化。若想表示「沙烏地阿拉伯的今天」,請使用timeZone: 'Asia/Riyadh'。時間點2026-11-03T22:30Z在利雅德已經是 24 جمادى الأولى。
公曆轉回曆數值
const hijriParts = new Intl.DateTimeFormat('en-u-ca-islamic-umalqura-nu-latn', {
day: 'numeric', month: 'numeric', year: 'numeric', timeZone: 'UTC',
});
export function toHijri(date) {
const p = {};
for (const { type, value } of hijriParts.formatToParts(date)) p[type] = value;
return { year: parseInt(p.year, 10), month: parseInt(p.month, 10), day: parseInt(p.day, 10) };
}
toHijri(new Date(Date.UTC(2026, 10, 3))); // { year: 1448, month: 5, day: 23 }
回曆轉公曆:逐日掃描
Intl 無法解析回曆日期,因此請估算公曆日期,然後掃描附近幾天,直到 toHijri 相符為止。
const DAY = 86400000;
export function hijriToGregorian(y, m, d) {
// 回曆元年1月1日約為西元622年7月19日(預測格里曆);平均月長29.530589天
const est = Date.UTC(622, 6, 19) + Math.round((y - 1) * 354.36667 + (m - 1) * 29.530589 + (d - 1)) * DAY;
for (let off = -20; off <= 20; off++) {
const t = new Date(est + off * DAY);
const h = toHijri(t);
if (h.year === y && h.month === m && h.day === d) return t.toISOString().slice(0, 10);
}
return null; // 該日期不存在,例如29天的月份中第30天
}
hijriToGregorian(1447, 9, 1); // "2026-02-18"
hijriToGregorian(1448, 1, 1); // "2026-06-16"
hijriToGregorian(1448, 1, 30); // null(回曆1448年穆哈蘭姆月有29天)
null結果可用於驗證使用者輸入;顯示「هذا اليوم غير موجود في هذا الشهر」。- 月份長度:
hijriToGregorian(y, m, 30) ? 30 : 29。 - ICU 的烏姆‧庫拉曆表涵蓋回曆1300年至1600年。超出此範圍時,引擎會回退到表格曆(tabular calendar),因此其結果並非烏姆‧庫拉曆。
- 接受使用者輸入的阿拉伯-印度數字:在解析前將 ٠-٩ 轉換為 0-9(請參閱
arabic-rtl-web)。
Python:hijridate
# pip install hijridate (hijri-converter 的後繼者,API 相同)
from hijridate import Hijri, Gregorian
h = Gregorian(2026, 11, 3).to_hijri() # Hijri(1448, 5, 23)
h.isoformat() # '1448-05-23'
h.dmyformat() # '23/05/1448'
h.month_name() # 'Jumada al-Ula'
h.month_name('ar') # 'جمادى الأولى'
h.day_name('ar') # 'الثلاثاء'
h.notation('ar') # 'هـ'
h.month_length() # 30
g = Hijri(1447, 9, 1).to_gregorian() # Gregorian(2026, 2, 18),一個 datetime.date 的子類別
Hijri.today()
- 較舊的程式碼使用
pip install hijri-converter和from hijri_converter import Hijri, Gregorian。它仍然有效,但現在會發出 DeprecationWarning;請將導入切換為hijridate。 - 支援範圍:回曆1343年1月1日至1500年12月30日(西元1924年8月1日至2077年11月16日)。超出範圍會引發
OverflowError: date out of range。 - 不存在的日期會引發
ValueError,例如Hijri(1448, 1, 30):「day must be in 1..29 for month」。 - 在 .NET 中,請使用
UmAlQuraCalendar,而不是HijriCalendar(HijriCalendar 是科威特表格算法)。
月份名稱
1 محرم، 2 صفر, 3 ربيع الأول, 4 ربيع الآخر, 5 جمادى الأولى, 6 جمادى الآخرة, 7 رجب, 8 شعبان, 9 رمضان, 10 شوال, 11 ذو القعدة, 12 ذو الحجة.
不同函式庫的拼寫有所不同:ICU 輸出「ربيع الآخر」,而 hijridate 輸出「ربيع الثاني」;英文也同樣有差異(「Rabiʻ II」、「Rabi' al-Thani」)。為了保持 UI 一致,請將月份號碼映射到您自己的列表,而不是混合使用不同函式庫的字串。
常見陷阱
- 錯誤的曆法。 對於2026年11月3日,Node 24 使用
islamic-umalqura給出 23 日,使用islamic-civil給出 22 日,使用islamic給出 24 日。Excel 和 Windows 的「Hijri」曆法使用科威特表格算法。對於沙烏地日期,請使用烏姆‧庫拉曆。 - 時區導致的一天偏移:請在原始日期儲存所處的時區進行格式化。
- 回曆中的日期算術。 若要計算「下個月」,請增加月份號碼,將天數限制在該月的實際長度內,然後進行轉換。年齡和持續時間應在公曆中計算。
- 未標註曆法的日期。 單純的「1/5/1448」或「5/1」具有歧義性;請務必加上「هـ」或「م」。
- 混合數字。 每個頁面請保持一致的一套數字系統。
使用已知配對進行測試(烏姆‧庫拉曆)
| 回曆 | 公曆 |
|---|---|
| 回曆1447年9月1日 | 2026-02-18 |
| 回曆1448年1月1日 | 2026-06-16 |
| 回曆1448年5月23日 | 2026-11-03 |
| 回曆1446年12月10日 | 2025-06-06 |