dayjs-hijri-plus/.github/wiki/Architecture.md

4.1 KiB

Architecture

Design Philosophy

dayjs-hijri-plus contains no Hijri calendar arithmetic. Every conversion delegates to hijri-core, which provides a pluggable engine registry with UAQ and FCNA built in.

This separation is deliberate. Calendar algorithms are complex, have known edge cases, and require dedicated testing. Keeping them in hijri-core means both this plugin and future adapters (for Temporal, date-fns, etc.) share a single, well-tested core.

Plugin Structure

src/
  index.ts   Plugin entry: registers methods on dayjsClass and dayjsFactory
  types.ts   Type definitions and module augmentation for dayjs

The plugin follows the standard Day.js PluginFunc signature:

const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => { ... };
  • dayjsClass.prototype.*: instance methods (.toHijri, .formatHijri, etc.)
  • (dayjsFactory as any).fromHijri: static method added to the factory function

Peer Dependencies

Both dayjs and hijri-core are peer dependencies. This means:

  1. The host application controls which version of dayjs is used. No version conflict possible.
  2. The host application controls which version of hijri-core is used. If hijri-core ships updated tables covering new years, the plugin benefits automatically.
  3. The plugin itself has zero runtime dependencies in node_modules, only peer resolutions.

Format Token Resolution

formatHijri works in two passes:

Pass 1: Replace Hijri tokens using a single regex sweep over the format string.

const HIJRI_TOKEN_RE = /iYYYY|iYY|iMMMM|iMMM|iMM|iM|iDD|iD|iEEEE|iEEE|iE|ioooo|iooo/g;

Tokens are listed longest-first in the alternation. This prevents iYY from matching before iYYYY, and iMM from matching before iMMMM. The regex engine tries alternatives left-to-right at each position, so ordering is the only safeguard needed.

Pass 2: The modified string is passed to this.format(result). Day.js resolves all remaining tokens (YYYY, MM, DD, HH, mm, ss, etc.) and square-bracket escapes ([literal]).

This means Hijri tokens and Gregorian tokens can coexist in the same format string. For example, 'iYYYY YYYY' produces '1444 2023'.

Weekday Alignment

Day.js .day() returns 0 for Sunday through 6 for Saturday, the same convention as Date.prototype.getDay().

The weekday arrays exported by hijri-core (hwLong, hwShort, hwNumeric) use the same index layout: index 0 = Sunday, index 6 = Saturday. So hwLong[this.day()] always yields the correct weekday name with no offset arithmetic.

fromHijri Error Handling

dayjs.fromHijri calls toGregorian from hijri-core. If the Hijri date is invalid or outside the table range, toGregorian returns null. The plugin converts that into a thrown Error with the specific Hijri components included in the message, so callers get a useful diagnostic rather than a null-dereference downstream.

Calendar Extension

The registry is global within a process. Registering a custom calendar once makes it available to all plugin method calls:

import { registerCalendar } from 'dayjs-hijri-plus';

registerCalendar('tabular', tabularEngine);

dayjs('2023-03-23').toHijri({ calendar: 'tabular' });

Custom engines must implement the CalendarEngine interface from hijri-core:

interface CalendarEngine {
  readonly id: string;
  toHijri(date: Date): HijriDate | null;
  toGregorian(hy: number, hm: number, hd: number): Date | null;
  isValid(hy: number, hm: number, hd: number): boolean;
  daysInMonth(hy: number, hm: number): number;
}

Build

The package ships a dual CJS/ESM build via tsup. Both dayjs and hijri-core are marked as external, so they are never bundled. Consumers provide them via peer dependency resolution.

Output:

File Format
dist/index.cjs CommonJS (Node require)
dist/index.mjs ESM (import)
dist/index.d.ts TypeScript declarations for CJS
dist/index.d.mts TypeScript declarations for ESM

Home | API Reference