Corteksa
GuidesOther

HIJRI CALENDAR

HIJRI CALENDAR

Present a date or datetime field in the Hijri (Umm al-Qura) calendar. Pure presentation — no new field type, no new column, no DDL, no migration. Gated behind HIJRI_CALENDAR_ENABLED; when off, no field can opt in and every existing field behaves exactly as before.

The invariant

The store is always Gregorian. date stays DATE, datetime stays TIMESTAMPTZ, and the stored bytes for a Hijri field are identical to those of a Gregorian field holding the same day. Conversion happens only at the read boundary.

That is not a stylistic choice — storing a Hijri 1447-09-12 as if it were Gregorian is a ~579-year silent corruption. Keeping the store Gregorian is what makes filters, BETWEEN, chronological sort, DATEDIFF/DATEADD, serial year_format, scheduled workflows, dashboard group-by and audit diffs all keep working untouched — they never see a Hijri value.

Flipping a field between calendars is therefore a config change with zero data migration: the rows were always Gregorian, so they simply re-render.

Configuration

The option lives on the field's existing options JSON:

{
  "calendar_system": "hijri",   // 'gregorian' (default) | 'hijri'
  "min_date": "2000-01-01",     // bounds stay Gregorian ISO
  "max_date": "2077-12-31"
}

Absent means gregorian. Any other value is rejected at config time with a 400 — it must not be ignored, because the read path treats anything that is not 'hijri' as Gregorian, so a typo'd "islamic" would save cleanly and then silently render the wrong calendar.

What each surface returns

SurfaceBehavior
Record read (detail + list)Adds a read-only companion key <slug>_hijri beside the untouched ISO value
Excel/CSV exportAdds an extra "<Label> (Hijri)" text column beside the real date column
Document templates (PDF)Renders {{field}} in place as Hijri
// GET /object/{slug}/data/{record}
{
  "contract_date-a1b2": "2026-03-01",         // canonical, unchanged
  "contract_date-a1b2_hijri": "1447-09-12"    // derived, read-only
}

Why a companion key and not a replacement

A client that reads a record and echoes it back on write must never be able to send a Hijri year into a Gregorian column. Keeping the canonical ISO value under its own slug makes that structurally impossible.

The same reasoning drives the export: the real column keeps its Gregorian ISO date cell so a re-import reads the original day, and the suffixed header matches no field so import ignores it.

Document templates are the deliberate exception — a rendered PDF is terminal output that never round-trips, and {{contract_date}} is the placeholder authors actually write, so there is no round-trip hazard to protect against.

Format

Numeric YYYY-MM-DD in Hijri (1447-09-12). Arabic month names, RTL layout, era marks and the Hijri picker grid are the frontend's concern — the backend emits a parseable string and does not decide presentation.

Supported range

≈1937-01-01 – 2077-12-31 CE (≈1356–1500 AH) — the Umm al-Qura table window. Outside it, conversion returns null rather than a value.

This guard is not optional: ICU does not fail past the table, it silently extrapolates with tabular arithmetic (2078-06-011501-07-20). A plausible-looking wrong date is worse than an absent one. Since these are reads, null is correct — there is no request to reject.

Authority

Umm al-Qura (the Saudi civil calendar) is the single declared authority. Tabular and sighting-based Hijri variants disagree with it by ±1–2 days, so every consumer goes through toHijriDateString — never a local Intl call or a date library.

Conversion uses Node's built-in ICU (Intl.DateTimeFormat with the islamic-umalqura calendar). No third-party dependency, but it does require a full-ICU Node build.

Where it lives

ConcernFile
Conversionsrc/common/utils/hijri-calendar.util.ts
"Is this field Hijri?"src/api/v1/object/shared/utils/hijri-field.util.ts
Option type.../field/options/interfaces/calendar-system.type.ts
Option validation.../field/options/helpers/assert-calendar-system.ts
Feature flagsrc/api/v1/object/fields/services/hijri-feature.ts

Not implemented

Deliberately out of scope for this slice — the write path is untouched, which is what keeps the corruption class closed:

  • Hijri input / convert_input — values are still entered as Gregorian.
  • allow_display_toggle — no per-viewer calendar switch.
  • Arabic month-name rendering server-side.
  • start_date / due_date field types.
  • Messaging variable rendering — still Gregorian.

Flag behavior

HIJRI_CALENDAR_ENABLED gates config time only: it decides whether calendar_system='hijri' can be saved onto a field, not whether an already configured field renders. Turning it off stops new Hijri fields but leaves existing ones rendering. Since the companion key is purely additive over an unchanged Gregorian value, that leaves no way to corrupt data — only an extra key a client may ignore.

On this page