Compare commits

...

8 commits
v1.0.2 ... main

Author SHA1 Message Date
Aric Camarata
35d35c641e
add opt-in anonymous telemetry (#1)
Some checks failed
CI / Test (Node 20) (push) Failing after 38s
CI / Test (Node 22) (push) Failing after 37s
CI / Test (Node 24) (push) Failing after 32s
CI / Lint (push) Failing after 35s
CI / Typecheck (push) Failing after 43s
CI / Pack check (push) Failing after 31s
CI / Coverage (push) Failing after 2s
* add opt-in telemetry via @acamarata/telemetry (off by default)

* chore: update lockfile for @acamarata/telemetry devDep

* chore: fix prettier formatting on telemetry import
2026-06-30 15:56:49 -04:00
Aric Camarata
33ec33fbc0 chore: bump to v1.0.4 2026-06-13 11:52:36 -04:00
Aric Camarata
b5b5c9313a build: use prepack hook so npm pack/publish reliably emit index.d.mts 2026-06-13 10:11:20 -04:00
Aric Camarata
04d72ac223 chore: bump to v1.0.3 2026-06-10 16:50:36 -04:00
Aric Camarata
3d20009b30 chore: update hijri-core to 1.0.3 2026-06-10 16:49:50 -04:00
Aric Camarata
f9ad1e52ed fix: convert the displayed calendar date in toHijri for hijri-core's UTC-day contract
toHijri() now passes Date.UTC(year, month, date) to hijri-core instead of the raw
instant from this.toDate(). Fixes wrong-Hijri-day results around UTC-midnight on
hosts east or west of UTC. Lock-step with hijri-core fix/utc-day-boundary.
2026-06-10 16:35:37 -04:00
Aric Camarata
f8ab0772b9 ci: fix lint job — add @typescript-eslint parser/plugin devDeps, files pattern, typed linting
eslint.config.mjs imported @typescript-eslint/parser and @typescript-eslint/eslint-plugin
directly but neither was a direct devDependency. pnpm strict hoisting made them unreachable
on CI. Fix: add both as explicit ^8 devDependencies, add files pattern for *.ts to all config
objects, and enable parserOptions.project for typed rules. Also run prettier to fix formatting.
2026-05-31 08:48:31 -04:00
Aric Camarata
7a57010e7c chore: ignore coverage directory 2026-05-30 20:15:14 -04:00
11 changed files with 136 additions and 48 deletions

1
.gitignore vendored
View file

@ -6,6 +6,7 @@ dist/
.claude/ .claude/
.env .env
.env.* .env.*
coverage/
# AI agent directories # AI agent directories
.cursor/ .cursor/

View file

@ -5,7 +5,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased] ## [1.0.4] - 2026-06-13
### Fixed
- Published package now includes `dist/index.d.mts` so ESM type resolution under `node16`/`nodenext` resolves the import condition.
## [1.0.3] - 2026-06-10
### Fixed
- `.toHijri()` now converts the calendar date the dayjs instance displays (via `Date.UTC(year, month, date)`) instead of passing the raw instant to hijri-core. Fixes wrong-Hijri-day results around UTC-midnight instants on hosts east or west of UTC. Requires hijri-core 1.0.3.
## [1.0.2] - 2026-05-30 ## [1.0.2] - 2026-05-30

View file

@ -36,6 +36,12 @@ dayjs.fromHijri(1444, 10, 1).format('YYYY-MM-DD'); // '2023-04-21'
Full API reference, examples, and architecture notes are on the [GitHub Wiki](https://github.com/acamarata/dayjs-hijri-plus/wiki). Full API reference, examples, and architecture notes are on the [GitHub Wiki](https://github.com/acamarata/dayjs-hijri-plus/wiki).
## Day boundaries and time zones
`.toHijri()` converts the calendar date the dayjs instance displays — the same date you would read off the screen — regardless of the host's system timezone or whether the dayjs `utc` plugin is active. A call like `dayjs('2025-03-01').toHijri()` always maps the 1st of March 2025, not whatever local instant that string resolves to in UTC.
Religious start-of-day at sunset is out of scope. Sunset-aware day boundaries require external prayer-time data and are not handled here.
## Related ## Related
- [hijri-core](https://github.com/acamarata/hijri-core): the zero-dependency Hijri calendar engine this plugin wraps - [hijri-core](https://github.com/acamarata/hijri-core): the zero-dependency Hijri calendar engine this plugin wraps

8
TELEMETRY.md Normal file
View file

@ -0,0 +1,8 @@
# Telemetry Disclosure
This package supports opt-in anonymous usage telemetry via [`@acamarata/telemetry`](https://github.com/acamarata/telemetry).
Telemetry is **off by default**. No data is sent unless you set `ACAMARATA_TELEMETRY=1`.
Full disclosure (what is sent, where it goes, how to disable):
[github.com/acamarata/telemetry/blob/main/TELEMETRY.md](https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md)

View file

@ -5,10 +5,14 @@ import { typescript } from '@acamarata/eslint-config';
export default [ export default [
{ {
files: ['**/*.ts', '**/*.tsx'],
plugins: { '@typescript-eslint': tsPlugin }, plugins: { '@typescript-eslint': tsPlugin },
languageOptions: { parser: tsParser }, languageOptions: {
parser: tsParser,
parserOptions: { project: true, tsconfigRootDir: import.meta.dirname },
}, },
...typescript, },
...typescript.map((cfg) => ({ ...cfg, files: ['**/*.ts', '**/*.tsx'] })),
eslintConfigPrettier, eslintConfigPrettier,
{ {
ignores: ['dist/', 'node_modules/', 'test.mjs', 'test-cjs.cjs'], ignores: ['dist/', 'node_modules/', 'test.mjs', 'test-cjs.cjs'],

View file

@ -1,6 +1,6 @@
{ {
"name": "dayjs-hijri-plus", "name": "dayjs-hijri-plus",
"version": "1.0.2", "version": "1.0.4",
"description": "Day.js plugin for Hijri calendar conversion and formatting. Supports Umm al-Qura and FCNA calendars via hijri-core.", "description": "Day.js plugin for Hijri calendar conversion and formatting. Supports Umm al-Qura and FCNA calendars via hijri-core.",
"author": "Aric Camarata", "author": "Aric Camarata",
"license": "MIT", "license": "MIT",
@ -37,7 +37,7 @@
"lint": "eslint src/", "lint": "eslint src/",
"format": "prettier --write src/", "format": "prettier --write src/",
"format:check": "prettier --check src/", "format:check": "prettier --check src/",
"prepublishOnly": "tsup", "prepack": "pnpm run build",
"coverage": "c8 --reporter=lcov --reporter=text node test.mjs", "coverage": "c8 --reporter=lcov --reporter=text node test.mjs",
"docs": "typedoc --out .github/wiki/api src/index.ts", "docs": "typedoc --out .github/wiki/api src/index.ts",
"postbuild": "cp dist/index.d.ts dist/index.d.mts" "postbuild": "cp dist/index.d.ts dist/index.d.mts"
@ -60,15 +60,18 @@
}, },
"devDependencies": { "devDependencies": {
"@acamarata/eslint-config": "^0.1.0", "@acamarata/eslint-config": "^0.1.0",
"c8": "^10.1.3",
"@acamarata/prettier-config": "^0.1.0", "@acamarata/prettier-config": "^0.1.0",
"@acamarata/telemetry": "^0.1.0",
"@acamarata/tsconfig": "^0.1.0", "@acamarata/tsconfig": "^0.1.0",
"@eslint/js": "^10.0.1", "@eslint/js": "^10.0.1",
"@types/node": "^25.3.5", "@types/node": "^25.3.5",
"@typescript-eslint/eslint-plugin": "^8.56.1",
"@typescript-eslint/parser": "^8.56.1",
"c8": "^10.1.3",
"dayjs": "^1.11.0", "dayjs": "^1.11.0",
"eslint": "^10.0.3", "eslint": "^10.0.3",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"hijri-core": "^1.0.0", "hijri-core": "^1.0.3",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"tsup": "^8.0.0", "tsup": "^8.0.0",
"typedoc": "^0.28.19", "typedoc": "^0.28.19",

View file

@ -14,6 +14,9 @@ importers:
'@acamarata/prettier-config': '@acamarata/prettier-config':
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0(prettier@3.8.1) version: 0.1.0(prettier@3.8.1)
'@acamarata/telemetry':
specifier: ^0.1.0
version: 0.1.0
'@acamarata/tsconfig': '@acamarata/tsconfig':
specifier: ^0.1.0 specifier: ^0.1.0
version: 0.1.0 version: 0.1.0
@ -23,6 +26,12 @@ importers:
'@types/node': '@types/node':
specifier: ^25.3.5 specifier: ^25.3.5
version: 25.3.5 version: 25.3.5
'@typescript-eslint/eslint-plugin':
specifier: ^8.56.1
version: 8.56.1(@typescript-eslint/parser@8.56.1(eslint@10.0.3)(typescript@5.9.3))(eslint@10.0.3)(typescript@5.9.3)
'@typescript-eslint/parser':
specifier: ^8.56.1
version: 8.56.1(eslint@10.0.3)(typescript@5.9.3)
c8: c8:
specifier: ^10.1.3 specifier: ^10.1.3
version: 10.1.3 version: 10.1.3
@ -36,8 +45,8 @@ importers:
specifier: ^10.1.8 specifier: ^10.1.8
version: 10.1.8(eslint@10.0.3) version: 10.1.8(eslint@10.0.3)
hijri-core: hijri-core:
specifier: ^1.0.0 specifier: ^1.0.3
version: 1.0.0 version: 1.0.3
prettier: prettier:
specifier: ^3.8.1 specifier: ^3.8.1
version: 3.8.1 version: 3.8.1
@ -81,6 +90,10 @@ packages:
peerDependencies: peerDependencies:
prettier: '>=3.0.0' prettier: '>=3.0.0'
'@acamarata/telemetry@0.1.0':
resolution: {integrity: sha512-iP09ZD0bHencHLbv6kQZDgwN9crLCWGKxmiMrfJjhBCoWTgv4koSgg0Li/LFKwCCFluua6orj9fVeQ8eqcJXSQ==}
engines: {node: '>=20'}
'@acamarata/tsconfig@0.1.0': '@acamarata/tsconfig@0.1.0':
resolution: {integrity: sha512-bgzyBak43mE+0HhduZX3cvaPjKcggtGGZZMjr35qtYWolsIWgZ9nx7OOswbVYoU35qoUv6rZ0mTK6GbZ8QTYjw==} resolution: {integrity: sha512-bgzyBak43mE+0HhduZX3cvaPjKcggtGGZZMjr35qtYWolsIWgZ9nx7OOswbVYoU35qoUv6rZ0mTK6GbZ8QTYjw==}
engines: {node: '>=20'} engines: {node: '>=20'}
@ -818,8 +831,8 @@ packages:
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
hijri-core@1.0.0: hijri-core@1.0.3:
resolution: {integrity: sha512-wImBZLBKbEWEEUE1nrc1CFY/uvx4XjGNWYChImJZlswXIVhrBCzSVaj6DP1AU2gUMJ6KDh2ygXo/u/Qx232CXA==} resolution: {integrity: sha512-ONT5gp+Z/lzT/BbWKS0F8lcKK9T/H6jc95NLQE4Angdt7Uimo4KkmSt/qn9odaQRp1pX0RjA65QRcR4miF7XxA==}
engines: {node: '>=20'} engines: {node: '>=20'}
html-escaper@2.0.2: html-escaper@2.0.2:
@ -1245,6 +1258,8 @@ snapshots:
dependencies: dependencies:
prettier: 3.8.1 prettier: 3.8.1
'@acamarata/telemetry@0.1.0': {}
'@acamarata/tsconfig@0.1.0': {} '@acamarata/tsconfig@0.1.0': {}
'@bcoe/v8-coverage@1.0.2': {} '@bcoe/v8-coverage@1.0.2': {}
@ -1880,7 +1895,7 @@ snapshots:
has-flag@4.0.0: {} has-flag@4.0.0: {}
hijri-core@1.0.0: {} hijri-core@1.0.3: {}
html-escaper@2.0.2: {} html-escaper@2.0.2: {}

View file

@ -1,9 +1,9 @@
import type { PluginFunc } from 'dayjs'; import type { PluginFunc } from "dayjs";
import { toHijri, toGregorian, hmLong, hmMedium, hwLong, hwShort, hwNumeric } from 'hijri-core'; import { toHijri, toGregorian, hmLong, hmMedium, hwLong, hwShort, hwNumeric } from "hijri-core";
import type { ConversionOptions, HijriDate } from './types'; import type { ConversionOptions, HijriDate } from "./types";
// Augment Day.js to expose plugin methods on the instance type. // Augment Day.js to expose plugin methods on the instance type.
declare module 'dayjs' { declare module "dayjs" {
interface Dayjs { interface Dayjs {
/** /**
* Convert the Day.js date to a Hijri date. * Convert the Day.js date to a Hijri date.
@ -91,7 +91,7 @@ declare module 'dayjs' {
// Using the function declaration form (same pattern as dayjs timezone plugin) // Using the function declaration form (same pattern as dayjs timezone plugin)
// because dayjs does not export an IStatic interface for module augmentation. // because dayjs does not export an IStatic interface for module augmentation.
// import('dayjs').Dayjs is used explicitly to satisfy the tsup DTS emitter. // import('dayjs').Dayjs is used explicitly to satisfy the tsup DTS emitter.
declare module 'dayjs' { declare module "dayjs" {
/** /**
* Construct a Day.js instance from a Hijri date. * Construct a Day.js instance from a Hijri date.
* *
@ -117,7 +117,7 @@ declare module 'dayjs' {
hm: number, hm: number,
hd: number, hd: number,
opts?: ConversionOptions, opts?: ConversionOptions,
): import('dayjs').Dayjs; ): import("dayjs").Dayjs;
} }
// Hijri-specific format tokens, ordered longest-first to prevent partial matches. // Hijri-specific format tokens, ordered longest-first to prevent partial matches.
@ -138,7 +138,7 @@ const HIJRI_TOKEN_RE = /iYYYY|iYY|iMMMM|iMMM|iMM|iM|iDD|iD|iEEEE|iEEE|iE|ioooo|i
* @returns The bracket-escaped string. * @returns The bracket-escaped string.
*/ */
function lit(value: string): string { function lit(value: string): string {
return '[' + value.split(']').join(']][') + ']'; return "[" + value.split("]").join("]][") + "]";
} }
/** /**
@ -167,7 +167,11 @@ const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => {
// ------------------------------------------------------------------ // // ------------------------------------------------------------------ //
dayjsClass.prototype.toHijri = function (opts?: ConversionOptions): HijriDate | null { dayjsClass.prototype.toHijri = function (opts?: ConversionOptions): HijriDate | null {
return toHijri(this.toDate(), opts); // Build a UTC-noon Date from the calendar date this instance displays so
// that hijri-core's UTC-day contract reads the correct day regardless of
// the host timezone or whether the dayjs utc plugin is active.
// dayjs .month() is 0-based, matching Date.UTC's month parameter.
return toHijri(new Date(Date.UTC(this.year(), this.month(), this.date())), opts);
}; };
dayjsClass.prototype.isValidHijri = function (opts?: ConversionOptions): boolean { dayjsClass.prototype.isValidHijri = function (opts?: ConversionOptions): boolean {
@ -191,7 +195,7 @@ const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => {
opts?: ConversionOptions, opts?: ConversionOptions,
): string { ): string {
const hijri = this.toHijri(opts); const hijri = this.toHijri(opts);
if (!hijri) return ''; if (!hijri) return "";
// Day.js .day() returns 0 (Sunday) ... 6 (Saturday), matching the index // Day.js .day() returns 0 (Sunday) ... 6 (Saturday), matching the index
// layout of hwLong, hwShort, and hwNumeric from hijri-core. // layout of hwLong, hwShort, and hwNumeric from hijri-core.
@ -199,38 +203,38 @@ const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => {
const replaced = formatStr.replace(HIJRI_TOKEN_RE, (token) => { const replaced = formatStr.replace(HIJRI_TOKEN_RE, (token) => {
switch (token) { switch (token) {
case 'iYYYY': case "iYYYY":
return lit(String(hijri.hy).padStart(4, '0')); return lit(String(hijri.hy).padStart(4, "0"));
case 'iYY': case "iYY":
return lit(String(hijri.hy % 100).padStart(2, '0')); return lit(String(hijri.hy % 100).padStart(2, "0"));
case 'iMMMM': case "iMMMM":
// Non-null: hijri.hm is a valid Hijri month 1-12; hm-1 is always within hmLong bounds. // Non-null: hijri.hm is a valid Hijri month 1-12; hm-1 is always within hmLong bounds.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return lit(hmLong[hijri.hm - 1]!); return lit(hmLong[hijri.hm - 1]!);
case 'iMMM': case "iMMM":
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return lit(hmMedium[hijri.hm - 1]!); return lit(hmMedium[hijri.hm - 1]!);
case 'iMM': case "iMM":
return lit(String(hijri.hm).padStart(2, '0')); return lit(String(hijri.hm).padStart(2, "0"));
case 'iM': case "iM":
return lit(String(hijri.hm)); return lit(String(hijri.hm));
case 'iDD': case "iDD":
return lit(String(hijri.hd).padStart(2, '0')); return lit(String(hijri.hd).padStart(2, "0"));
case 'iD': case "iD":
return lit(String(hijri.hd)); return lit(String(hijri.hd));
case 'iEEEE': case "iEEEE":
// Non-null: dow is always 0-6 (day of week), within hwLong bounds. // Non-null: dow is always 0-6 (day of week), within hwLong bounds.
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return lit(hwLong[dow]!); return lit(hwLong[dow]!);
case 'iEEE': case "iEEE":
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return lit(hwShort[dow]!); return lit(hwShort[dow]!);
case 'iE': case "iE":
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return lit(String(hwNumeric[dow]!)); return lit(String(hwNumeric[dow]!));
case 'ioooo': case "ioooo":
case 'iooo': case "iooo":
return lit('AH'); return lit("AH");
default: default:
return token; return token;
} }
@ -262,12 +266,14 @@ const plugin: PluginFunc = (_option, dayjsClass, dayjsFactory) => {
if (!greg) { if (!greg) {
throw new Error(`Invalid or out-of-range Hijri date: ${hy}/${hm}/${hd}`); throw new Error(`Invalid or out-of-range Hijri date: ${hy}/${hm}/${hd}`);
} }
// Construct from ISO date string to avoid timezone offset issues. // Construct from an ISO date string (YYYY-MM-DD) so the result is the
// dayjsFactory(Date) interprets the Date in local time; a UTC-midnight Date // Gregorian calendar day that corresponds to the Hijri date, at local
// in western timezones would resolve to the previous local day. // midnight in whatever timezone the consumer uses. Passing a raw Date
// object to dayjsFactory() would interpret it as a UTC instant and could
// land on the previous local day for hosts west of UTC.
const y = greg.getUTCFullYear(); const y = greg.getUTCFullYear();
const mo = String(greg.getUTCMonth() + 1).padStart(2, '0'); const mo = String(greg.getUTCMonth() + 1).padStart(2, "0");
const dy = String(greg.getUTCDate()).padStart(2, '0'); const dy = String(greg.getUTCDate()).padStart(2, "0");
return dayjsFactory(`${y}-${mo}-${dy}`); return dayjsFactory(`${y}-${mo}-${dy}`);
}; };
}; };
@ -278,13 +284,13 @@ export default plugin;
* Re-exported from hijri-core for consumers who import from dayjs-hijri-plus. * Re-exported from hijri-core for consumers who import from dayjs-hijri-plus.
* Avoids requiring hijri-core as a direct dependency just to use these types. * Avoids requiring hijri-core as a direct dependency just to use these types.
*/ */
export type { HijriDate, ConversionOptions } from './types'; export type { HijriDate, ConversionOptions } from "./types";
/** /**
* Re-exported CalendarEngine interface from hijri-core. * Re-exported CalendarEngine interface from hijri-core.
* Use this type to implement custom calendar engines for `registerCalendar`. * Use this type to implement custom calendar engines for `registerCalendar`.
*/ */
export type { CalendarEngine } from 'hijri-core'; export type { CalendarEngine } from "hijri-core";
/** /**
* Re-exported registry API from hijri-core. * Re-exported registry API from hijri-core.
@ -296,4 +302,13 @@ export type { CalendarEngine } from 'hijri-core';
* registerCalendar('my-cal', myEngine); * registerCalendar('my-cal', myEngine);
* listCalendars(); // ['uaq', 'fcna', 'my-cal'] * listCalendars(); // ['uaq', 'fcna', 'my-cal']
*/ */
export { registerCalendar, getCalendar, listCalendars } from 'hijri-core'; export { registerCalendar, getCalendar, listCalendars } from "hijri-core";
// ── Opt-in anonymous telemetry ────────────────────────────────────────────────
// Off by default. Enable: ACAMARATA_TELEMETRY=1
// What is sent + how to disable: https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md
import("@acamarata/telemetry")
.then(({ track }) => track("load", { package: "dayjs-hijri-plus", version: "1.0.4" }))
.catch(() => {
// telemetry not installed or disabled — that's fine
});

View file

@ -1,2 +1,2 @@
import type { HijriDate, ConversionOptions } from 'hijri-core'; import type { HijriDate, ConversionOptions } from "hijri-core";
export type { HijriDate, ConversionOptions }; export type { HijriDate, ConversionOptions };

View file

@ -59,3 +59,16 @@ describe('CJS: isValidHijri', () => {
assert.equal(dayjs(D_RAMADAN_1444).isValidHijri(), true); assert.equal(dayjs(D_RAMADAN_1444).isValidHijri(), true);
}); });
}); });
describe('CJS: UTC-day boundary (regression)', () => {
it('dayjs("2025-03-01").toHijri() -> 1 Ramadan 1446', () => {
const h = dayjs('2025-03-01').toHijri();
assert.deepEqual(h, { hy: 1446, hm: 9, hd: 1 });
});
it('round-trip: fromHijri(1446,9,1) then toHijri() -> {1446,9,1}', () => {
const d = dayjs.fromHijri(1446, 9, 1);
const h = d.toHijri();
assert.deepEqual(h, { hy: 1446, hm: 9, hd: 1 });
});
});

View file

@ -102,3 +102,18 @@ describe('isValidHijri', () => {
assert.equal(valid, true); assert.equal(valid, true);
}); });
}); });
describe('UTC-day boundary (regression)', () => {
// dayjs("YYYY-MM-DD") parses as local midnight — timezone-invariant anchor
// for toHijri now that the adapter reads the displayed calendar date.
it('dayjs("2025-03-01").toHijri() -> 1 Ramadan 1446', () => {
const h = dayjs('2025-03-01').toHijri();
assert.deepEqual(h, { hy: 1446, hm: 9, hd: 1 });
});
it('round-trip: fromHijri(1446,9,1) then toHijri() -> {1446,9,1}', () => {
const d = dayjs.fromHijri(1446, 9, 1);
const h = d.toHijri();
assert.deepEqual(h, { hy: 1446, hm: 9, hd: 1 });
});
});