diff --git a/.github/workflows/wiki-sync.yml b/.github/workflows/wiki-sync.yml index 02d1508..61c205b 100644 --- a/.github/workflows/wiki-sync.yml +++ b/.github/workflows/wiki-sync.yml @@ -6,6 +6,9 @@ on: paths: - '.wiki/**' +permissions: + contents: write + jobs: sync: name: Sync wiki to GitHub Wiki diff --git a/.wiki/Architecture.md b/.wiki/Architecture.md index ee9f0de..c5a952b 100644 --- a/.wiki/Architecture.md +++ b/.wiki/Architecture.md @@ -4,7 +4,7 @@ The TC39 Temporal proposal (Stage 3, actively shipping in browsers) replaces the legacy `Date` object with a family of types: `PlainDate`, `PlainTime`, `PlainDateTime`, `ZonedDateTime`, `Instant`, and more. Central to its design is an extensible calendar system. -A `Temporal.PlainDate` always stores its fields (year, month, day) in terms of a specific calendar. The ISO 8601 (Gregorian) calendar is the default. Custom calendars implement an interface — informally called the Temporal Calendar Protocol — that lets the `PlainDate` compute calendar-specific values from those fields, perform arithmetic, and convert between calendar systems. +A `Temporal.PlainDate` always stores its fields (year, month, day) in terms of a specific calendar. The ISO 8601 (Gregorian) calendar is the default. Custom calendars implement an interface (informally called the Temporal Calendar Protocol) that lets the `PlainDate` compute calendar-specific values from those fields, perform arithmetic, and convert between calendar systems. `temporal-hijri` implements this protocol for the Hijri (Islamic) calendar. @@ -26,7 +26,7 @@ mergeFields(fields, additionalFields) toString() ``` -`HijriCalendar` implements all of these. The key challenge is that `Temporal.PlainDate` stores ISO coordinates (Gregorian year/month/day), while the methods must return Hijri coordinates — and `dateFromFields` must do the reverse. +`HijriCalendar` implements all of these. The key challenge is that `Temporal.PlainDate` stores ISO coordinates (Gregorian year/month/day), while the methods must return Hijri coordinates. `dateFromFields` does the reverse. ## Coordinate Bridging @@ -49,7 +49,7 @@ The inverse path: ### Date object construction -The UAQ engine reads local date components (`getFullYear`, `getMonth`, `getDate`). To ensure the local date always matches the intended calendar date — regardless of the host's timezone — `toHijri()` uses the local `Date` constructor: `new Date(year, month - 1, day)`. This avoids the UTC-to-local shift that would occur with `Date.UTC`. +The UAQ engine reads local date components (`getFullYear`, `getMonth`, `getDate`). To ensure the local date always matches the intended calendar date regardless of the host's timezone, `toHijri()` uses the local `Date` constructor: `new Date(year, month - 1, day)`. This avoids the UTC-to-local shift that would occur with `Date.UTC`. The FCNA engine reads UTC components for its astronomical calculations. The UTC-local discrepancy is at most one day, which falls within the tolerance of FCNA's calculation window. @@ -57,7 +57,7 @@ The FCNA engine reads UTC components for its astronomical calculations. The UTC- Adding a duration to a Hijri date requires different handling for different duration components: -- **Years and months** must be applied in Hijri space. Adding "1 month" to 1 Ramadan should yield 1 Shawwal — not a fixed 30-day offset. The Hijri calendar has months of 29 and 30 days in no fixed pattern, so month arithmetic must account for actual month lengths. +- **Years and months** must be applied in Hijri space. Adding "1 month" to 1 Ramadan should yield 1 Shawwal, not a fixed 30-day offset. The Hijri calendar has months of 29 and 30 days in no fixed pattern, so month arithmetic must account for actual month lengths. - **Days and weeks** can be applied in ISO (Gregorian) space after the Hijri-space year/month addition. Adding 7 days means exactly 7 days, and ISO arithmetic handles that correctly. @@ -72,7 +72,7 @@ The implementation: 6. Apply the day and week delta with ISO PlainDate.add(). ``` -Clamping (step 4) follows the Temporal specification's "constrain" overflow behavior. Adding 1 month to 30 Rajab (a 30-day month) where Shaban is 29 days would yield 30 Shaban — instead, the result is clamped to 29 Shaban. +Clamping (step 4) follows the Temporal specification's "constrain" overflow behavior. Adding 1 month to 30 Rajab (a 30-day month) where Shaban is 29 days would yield 30 Shaban. The result is clamped to 29 Shaban. ## dateUntil: Difference Strategy @@ -107,7 +107,7 @@ hijri-core provides: - Built-in UAQ engine (table-driven, Hijri years 1318-1500) - Built-in FCNA engine (Meeus Chapter 49 astronomical calculations) -`temporal-hijri` is a pure adapter layer. It does not implement any calendar arithmetic itself — it translates between the Temporal protocol and hijri-core's engine interface. +`temporal-hijri` is a pure adapter layer. It does not implement any calendar arithmetic itself. It translates between the Temporal protocol and hijri-core's engine interface. ## Build Output diff --git a/.wiki/Home.md b/.wiki/Home.md index 9954267..ecfc046 100644 --- a/.wiki/Home.md +++ b/.wiki/Home.md @@ -6,15 +6,15 @@ This package provides `UaqCalendar` and `FcnaCalendar` as plug-in calendar objec ## Pages -- [Home](Home) — you are here -- [API Reference](API-Reference) — full method signatures and return types -- [Architecture](Architecture) — design decisions, protocol internals, arithmetic strategy +- [Home](Home): you are here +- [API Reference](API-Reference): full method signatures and return types +- [Architecture](Architecture): design decisions, protocol internals, arithmetic strategy ## Quick links - [GitHub repository](https://github.com/acamarata/temporal-hijri) - [npm package](https://www.npmjs.com/package/temporal-hijri) -- [hijri-core](https://github.com/acamarata/hijri-core) — the underlying calendar engine +- [hijri-core](https://github.com/acamarata/hijri-core): the underlying calendar engine ## Calendar systems @@ -27,7 +27,7 @@ This package provides `UaqCalendar` and `FcnaCalendar` as plug-in calendar objec - Node.js 20+ - `hijri-core ^1.0.0` (peer dependency) -- `@js-temporal/polyfill ^0.4.0` (optional peer dependency — needed if native `Temporal` is unavailable) +- `@js-temporal/polyfill ^0.4.0` (optional peer dependency, needed if native `Temporal` is unavailable) --- diff --git a/CHANGELOG.md b/CHANGELOG.md index c0d477a..b173c03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,8 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - `HijriCalendar` base class implementing the TC39 Temporal Calendar Protocol -- `UaqCalendar` — Umm al-Qura calendar (table-driven, 1318-1500 AH coverage) -- `FcnaCalendar` — FCNA/ISNA calendar (astronomical new moon calculation via Meeus) +- `UaqCalendar`: Umm al-Qura calendar (table-driven, 1318-1500 AH coverage) +- `FcnaCalendar`: FCNA/ISNA calendar (astronomical new moon calculation via Meeus) - `uaqCalendar` and `fcnaCalendar` convenience singletons - Full Temporal protocol: `year`, `month`, `monthCode`, `day`, `daysInMonth`, `daysInYear`, `monthsInYear`, `inLeapYear`, `dayOfWeek`, `dayOfYear`, `weekOfYear`, `daysInWeek`, `dateFromFields`, `yearMonthFromFields`, `monthDayFromFields`, `dateAdd`, `dateUntil`, `mergeFields`, `toString` - Dual CJS and ESM builds with TypeScript declarations diff --git a/README.md b/README.md index 10aadbe..ec87265 100644 --- a/README.md +++ b/README.md @@ -175,7 +175,7 @@ The package ships dual CJS/ESM builds with full `.d.ts` and `.d.mts` declaration - Node.js 20, 22, 24 - Any bundler supporting `exports` field (`Vite`, `Webpack 5`, `Rollup`, `esbuild`) -- ESM (`import`) and CommonJS (`require`) — both provided +- ESM (`import`) and CommonJS (`require`): both provided - No native `Temporal` required: works entirely with `@js-temporal/polyfill` --- @@ -188,9 +188,9 @@ Full reference, architecture notes, and algorithmic detail in the [wiki](https:/ ## Related -- [hijri-core](https://github.com/acamarata/hijri-core) — the zero-dependency Hijri engine powering this package -- [luxon-hijri](https://github.com/acamarata/luxon-hijri) — Hijri/Gregorian conversion for Luxon -- [pray-calc](https://github.com/acamarata/pray-calc) — Islamic prayer times +- [hijri-core](https://github.com/acamarata/hijri-core): zero-dependency Hijri engine powering this package +- [luxon-hijri](https://github.com/acamarata/luxon-hijri): Hijri/Gregorian conversion for Luxon +- [pray-calc](https://github.com/acamarata/pray-calc): Islamic prayer times --- diff --git a/package.json b/package.json index 7f61ce9..5dd9398 100644 --- a/package.json +++ b/package.json @@ -56,7 +56,7 @@ "devDependencies": { "@js-temporal/polyfill": "^0.4.4", "@types/node": "^22.0.0", - "hijri-core": "file:../hijri-core", + "hijri-core": "^1.0.0", "tsup": "^8.0.0", "typescript": "^5.5.0" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7cc7590..676a6e8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,8 +15,8 @@ importers: specifier: ^22.0.0 version: 22.19.11 hijri-core: - specifier: file:../hijri-core - version: file:../hijri-core + specifier: ^1.0.0 + version: 1.0.0 tsup: specifier: ^8.0.0 version: 8.5.1(typescript@5.9.3) @@ -407,8 +407,8 @@ packages: engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} os: [darwin] - hijri-core@file:../hijri-core: - resolution: {directory: ../hijri-core, type: directory} + hijri-core@1.0.0: + resolution: {integrity: sha512-wImBZLBKbEWEEUE1nrc1CFY/uvx4XjGNWYChImJZlswXIVhrBCzSVaj6DP1AU2gUMJ6KDh2ygXo/u/Qx232CXA==} engines: {node: '>=20'} joycon@3.1.1: @@ -803,7 +803,7 @@ snapshots: fsevents@2.3.3: optional: true - hijri-core@file:../hijri-core: {} + hijri-core@1.0.0: {} joycon@3.1.1: {} diff --git a/src/calendars/HijriCalendar.ts b/src/calendars/HijriCalendar.ts index 6fab0d2..9a9f946 100644 --- a/src/calendars/HijriCalendar.ts +++ b/src/calendars/HijriCalendar.ts @@ -1,7 +1,7 @@ import { Temporal } from '@js-temporal/polyfill'; import type { CalendarEngine } from 'hijri-core'; -type DateUnit = 'year' | 'years' | 'month' | 'months' | 'week' | 'weeks' | 'day' | 'days' | 'auto'; +type DateUnit = 'year' | 'years' | 'month' | 'months' | 'week' | 'weeks' | 'day' | 'days'; /** * Base class implementing the TC39 Temporal Calendar Protocol for Hijri calendars. @@ -146,6 +146,7 @@ export class HijriCalendar { return total; } + /** Hijri week number counted from day 1 of Muharram (day 1-7 = week 1). No ISO week alignment. */ weekOfYear(date: Temporal.PlainDate): number { return Math.ceil(this.dayOfYear(date) / 7); } @@ -163,6 +164,11 @@ export class HijriCalendar { return this.fromHijri(fields.year, fields.month, fields.day); } + /** + * ISO-anchored PlainYearMonth per the Temporal Calendar Protocol. + * The resulting PlainYearMonth stores ISO coordinates internally, representing + * the Hijri month that starts on that ISO year/month. + */ yearMonthFromFields( fields: { year: number; month: number }, _options?: { overflow?: 'constrain' | 'reject' } @@ -174,6 +180,11 @@ export class HijriCalendar { }); } + /** + * ISO-anchored PlainMonthDay per the Temporal Calendar Protocol. + * Reference year 1444 is intentional: it is a recent, well-covered UAQ year + * used to anchor the ISO coordinates when no year is supplied. + */ monthDayFromFields( fields: { month: number; day: number; year?: number }, _options?: { overflow?: 'constrain' | 'reject' } diff --git a/src/index.ts b/src/index.ts index 359a2c5..52d3e11 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,9 +3,8 @@ export { UaqCalendar } from './calendars/UaqCalendar'; export { FcnaCalendar } from './calendars/FcnaCalendar'; export type { HijriDate, CalendarEngine, ConversionOptions } from 'hijri-core'; -export type { HijriCalendarOptions } from './types'; -// Convenience singletons — ready to use without instantiation. +// Pre-built singletons. Import and use directly; no need to instantiate. import { UaqCalendar } from './calendars/UaqCalendar'; import { FcnaCalendar } from './calendars/FcnaCalendar'; diff --git a/src/types.ts b/src/types.ts index 0188add..f47c7ee 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1 @@ export type { HijriDate, CalendarEngine, ConversionOptions } from 'hijri-core'; - -export interface HijriCalendarOptions { - calendar?: string; -}