mirror of
https://github.com/acamarata/temporal-hijri.git
synced 2026-06-30 19:04:29 +00:00
feat: initial release of temporal-hijri v1.0.0
Temporal Calendar Protocol implementation for Hijri calendar via hijri-core. Provides HijriCalendar base class, UaqCalendar (UAQ engine), FcnaCalendar (FCNA engine), and convenience singletons uaqCalendar/fcnaCalendar. Calendar protocol: year, month, monthCode, day, daysInMonth, daysInYear, monthsInYear, inLeapYear, dayOfWeek, dayOfYear, weekOfYear, daysInWeek, dateFromFields, yearMonthFromFields, monthDayFromFields, dateAdd, dateUntil, mergeFields. dateAdd applies year/month increments in Hijri coordinate space (correct semantics for variable-length lunar months), then applies days/weeks in ISO space. Day-of-month clamped on month boundary changes. 18 ESM + 8 CJS tests passing. Dual CJS/ESM build.
This commit is contained in:
commit
51e07a65b3
24 changed files with 2356 additions and 0 deletions
18
.editorconfig
Normal file
18
.editorconfig
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = true
|
||||
insert_final_newline = true
|
||||
|
||||
[*.{js,mjs,cjs,ts,json,yaml,yml,md}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{c,h}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[Makefile]
|
||||
indent_style = tab
|
||||
62
.github/workflows/ci.yml
vendored
Normal file
62
.github/workflows/ci.yml
vendored
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
name: Test (Node ${{ matrix.node }})
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: [20, 22, 24]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run build
|
||||
- run: node test.mjs
|
||||
- run: node test-cjs.cjs
|
||||
|
||||
typecheck:
|
||||
name: Typecheck
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run typecheck
|
||||
|
||||
pack-check:
|
||||
name: Pack check
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 24
|
||||
cache: pnpm
|
||||
- run: pnpm install --frozen-lockfile
|
||||
- run: pnpm run build
|
||||
- name: Verify pack contents
|
||||
run: |
|
||||
npm pack --dry-run 2>&1 | tee pack-output.txt
|
||||
grep "dist/index.cjs" pack-output.txt
|
||||
grep "dist/index.mjs" pack-output.txt
|
||||
grep "dist/index.d.ts" pack-output.txt
|
||||
grep "dist/index.d.mts" pack-output.txt
|
||||
grep "README.md" pack-output.txt
|
||||
grep "CHANGELOG.md" pack-output.txt
|
||||
grep "LICENSE" pack-output.txt
|
||||
22
.github/workflows/wiki-sync.yml
vendored
Normal file
22
.github/workflows/wiki-sync.yml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
name: Wiki Sync
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [main]
|
||||
paths:
|
||||
- '.wiki/**'
|
||||
|
||||
jobs:
|
||||
sync:
|
||||
name: Sync wiki to GitHub Wiki
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Sync .wiki/ to GitHub Wiki
|
||||
uses: Andrew-Chen-Wang/github-wiki-action@v4
|
||||
with:
|
||||
path: .wiki/
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GITHUB_ACTOR: ${{ github.actor }}
|
||||
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
node_modules/
|
||||
dist/
|
||||
*.tgz
|
||||
*.log
|
||||
.DS_Store
|
||||
.claude/
|
||||
.env
|
||||
.env.*
|
||||
|
||||
# AI agent directories
|
||||
.aider*
|
||||
.cursor/
|
||||
.continue/
|
||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
package-import-method=hardlink
|
||||
1
.nvmrc
Normal file
1
.nvmrc
Normal file
|
|
@ -0,0 +1 @@
|
|||
24
|
||||
164
.wiki/API-Reference.md
Normal file
164
.wiki/API-Reference.md
Normal file
|
|
@ -0,0 +1,164 @@
|
|||
# API Reference
|
||||
|
||||
## Exports
|
||||
|
||||
```typescript
|
||||
// Classes
|
||||
export { HijriCalendar } from 'temporal-hijri';
|
||||
export { UaqCalendar } from 'temporal-hijri';
|
||||
export { FcnaCalendar } from 'temporal-hijri';
|
||||
|
||||
// Singletons
|
||||
export { uaqCalendar } from 'temporal-hijri'; // new UaqCalendar()
|
||||
export { fcnaCalendar } from 'temporal-hijri'; // new FcnaCalendar()
|
||||
|
||||
// Types (re-exported from hijri-core)
|
||||
export type { HijriDate, ConversionOptions } from 'temporal-hijri';
|
||||
export type { HijriCalendarOptions } from 'temporal-hijri';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## `HijriCalendar`
|
||||
|
||||
Base class. Implements the Temporal Calendar Protocol using a `CalendarEngine` from hijri-core.
|
||||
|
||||
### Constructor
|
||||
|
||||
```typescript
|
||||
new HijriCalendar(engine: CalendarEngine)
|
||||
```
|
||||
|
||||
Accepts any engine registered via hijri-core's `registerCalendar()`. The calendar's `id` is `hijri-${engine.id}`.
|
||||
|
||||
---
|
||||
|
||||
## `UaqCalendar`
|
||||
|
||||
```typescript
|
||||
new UaqCalendar()
|
||||
```
|
||||
|
||||
Wraps the `uaq` engine from hijri-core. Calendar ID: `"hijri-uaq"`.
|
||||
|
||||
---
|
||||
|
||||
## `FcnaCalendar`
|
||||
|
||||
```typescript
|
||||
new FcnaCalendar()
|
||||
```
|
||||
|
||||
Wraps the `fcna` engine from hijri-core. Calendar ID: `"hijri-fcna"`.
|
||||
|
||||
---
|
||||
|
||||
## Calendar Protocol Methods
|
||||
|
||||
All methods are available on `HijriCalendar`, `UaqCalendar`, and `FcnaCalendar`. Each method receives a `Temporal.PlainDate` with the ISO (Gregorian) calendar.
|
||||
|
||||
### Field accessors
|
||||
|
||||
| Method | Signature | Returns | Notes |
|
||||
|---|---|---|---|
|
||||
| `year` | `(date: PlainDate) => number` | Hijri year | |
|
||||
| `month` | `(date: PlainDate) => number` | Hijri month (1-12) | |
|
||||
| `monthCode` | `(date: PlainDate) => string` | `"M01"` – `"M12"` | Zero-padded |
|
||||
| `day` | `(date: PlainDate) => number` | Day of month (1-29/30) | |
|
||||
|
||||
### Year and month metrics
|
||||
|
||||
| Method | Signature | Returns | Notes |
|
||||
|---|---|---|---|
|
||||
| `daysInMonth` | `(date: PlainDate) => number` | 29 or 30 | Varies by month and calendar |
|
||||
| `daysInYear` | `(date: PlainDate) => number` | 354 or 355 | Sum of all 12 months |
|
||||
| `monthsInYear` | `(date: PlainDate) => number` | Always 12 | |
|
||||
| `inLeapYear` | `(date: PlainDate) => boolean` | `true` if 355-day year | |
|
||||
|
||||
### Week and day position
|
||||
|
||||
| Method | Signature | Returns | Notes |
|
||||
|---|---|---|---|
|
||||
| `dayOfWeek` | `(date: PlainDate) => number` | 1-7 (Mon=1, Sun=7) | ISO weekday |
|
||||
| `dayOfYear` | `(date: PlainDate) => number` | 1-354 or 1-355 | Within the Hijri year |
|
||||
| `weekOfYear` | `(date: PlainDate) => number` | 1-51 | `ceil(dayOfYear / 7)` |
|
||||
| `daysInWeek` | `(date: PlainDate) => number` | Always 7 | |
|
||||
|
||||
### Construction from fields
|
||||
|
||||
| Method | Signature | Returns |
|
||||
|---|---|---|
|
||||
| `dateFromFields` | `(fields: {year, month, day}, options?) => PlainDate` | ISO `PlainDate` |
|
||||
| `yearMonthFromFields` | `(fields: {year, month}, options?) => PlainYearMonth` | ISO `PlainYearMonth` |
|
||||
| `monthDayFromFields` | `(fields: {month, day, year?}, options?) => PlainMonthDay` | ISO `PlainMonthDay` |
|
||||
|
||||
`monthDayFromFields` uses year 1444 AH as a default reference if no year is supplied.
|
||||
|
||||
### Arithmetic
|
||||
|
||||
#### `dateAdd`
|
||||
|
||||
```typescript
|
||||
dateAdd(
|
||||
date: Temporal.PlainDate,
|
||||
duration: Temporal.Duration,
|
||||
options?: { overflow?: 'constrain' | 'reject' }
|
||||
): Temporal.PlainDate
|
||||
```
|
||||
|
||||
Adds a duration to a Hijri date. Year and month components are applied in Hijri space (preserving calendar semantics); day and week components are applied in ISO space afterward.
|
||||
|
||||
When a month addition causes the day-of-month to exceed the target month's length, the day is clamped to the last valid day of that month.
|
||||
|
||||
#### `dateUntil`
|
||||
|
||||
```typescript
|
||||
dateUntil(
|
||||
one: Temporal.PlainDate,
|
||||
two: Temporal.PlainDate,
|
||||
options?: { largestUnit?: 'years' | 'months' | 'weeks' | 'days' }
|
||||
): Temporal.Duration
|
||||
```
|
||||
|
||||
Computes the difference between two dates. When `largestUnit` is `'years'` or `'months'`, the difference is calculated in Hijri space. For `'days'` and `'weeks'` it delegates to ISO arithmetic, which is exact.
|
||||
|
||||
### Other
|
||||
|
||||
| Method | Signature | Returns |
|
||||
|---|---|---|
|
||||
| `mergeFields` | `(fields, additionalFields) => Record` | Merged field object |
|
||||
| `toString` | `() => string` | Calendar identifier |
|
||||
|
||||
---
|
||||
|
||||
## Types
|
||||
|
||||
### `HijriDate` (from hijri-core)
|
||||
|
||||
```typescript
|
||||
interface HijriDate {
|
||||
hy: number; // Hijri year
|
||||
hm: number; // Hijri month (1-12)
|
||||
hd: number; // Hijri day (1-30)
|
||||
}
|
||||
```
|
||||
|
||||
### `ConversionOptions` (from hijri-core)
|
||||
|
||||
```typescript
|
||||
interface ConversionOptions {
|
||||
calendar?: string; // defaults to 'uaq'
|
||||
}
|
||||
```
|
||||
|
||||
### `HijriCalendarOptions`
|
||||
|
||||
```typescript
|
||||
interface HijriCalendarOptions {
|
||||
calendar?: string;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
[Home](Home) · [API Reference](API-Reference) · [Architecture](Architecture)
|
||||
134
.wiki/Architecture.md
Normal file
134
.wiki/Architecture.md
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
# Architecture
|
||||
|
||||
## The Temporal Calendar Protocol
|
||||
|
||||
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.
|
||||
|
||||
`temporal-hijri` implements this protocol for the Hijri (Islamic) calendar.
|
||||
|
||||
## Protocol Implementation
|
||||
|
||||
The protocol requires these methods on a calendar object:
|
||||
|
||||
```
|
||||
year(date) month(date) monthCode(date)
|
||||
day(date) daysInMonth(date) daysInYear(date)
|
||||
monthsInYear(date) inLeapYear(date) dayOfWeek(date)
|
||||
dayOfYear(date) weekOfYear(date) daysInWeek(date)
|
||||
dateFromFields(fields, options)
|
||||
yearMonthFromFields(fields, options)
|
||||
monthDayFromFields(fields, options)
|
||||
dateAdd(date, duration, options)
|
||||
dateUntil(one, two, options)
|
||||
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.
|
||||
|
||||
## Coordinate Bridging
|
||||
|
||||
Every calendar method follows the same two-step pattern:
|
||||
|
||||
1. Receive a `Temporal.PlainDate` with ISO coordinates.
|
||||
2. Convert to Hijri coordinates using `toHijri()`, which constructs a JavaScript `Date` from the ISO fields and passes it to the hijri-core engine.
|
||||
|
||||
```
|
||||
Temporal.PlainDate (ISO) → toHijri() → {hy, hm, hd}
|
||||
```
|
||||
|
||||
The inverse path:
|
||||
|
||||
```
|
||||
{hy, hm, hd} → fromHijri() → Temporal.PlainDate (ISO)
|
||||
```
|
||||
|
||||
`fromHijri()` calls the engine's `toGregorian()`, reads UTC components from the returned `Date`, and constructs a `PlainDate`.
|
||||
|
||||
### 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 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.
|
||||
|
||||
## dateAdd: Arithmetic Strategy
|
||||
|
||||
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.
|
||||
|
||||
- **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.
|
||||
|
||||
The implementation:
|
||||
|
||||
```
|
||||
1. Extract Hijri coordinates from the input PlainDate.
|
||||
2. Add years and months to the Hijri coordinates.
|
||||
3. Normalize: roll months > 12 or < 1 into years.
|
||||
4. Clamp the day to the target month's actual length.
|
||||
5. Convert back to ISO PlainDate.
|
||||
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.
|
||||
|
||||
## dateUntil: Difference Strategy
|
||||
|
||||
For `largestUnit: 'days'` or `'weeks'`, the difference between two ISO dates is always an exact number of days. ISO arithmetic handles this directly and correctly.
|
||||
|
||||
For `largestUnit: 'years'` or `'months'`, the difference is computed in Hijri space:
|
||||
|
||||
```
|
||||
years = h2.hy - h1.hy
|
||||
months = h2.hm - h1.hm
|
||||
days = h2.hd - h1.hd
|
||||
```
|
||||
|
||||
Borrow operations normalize negative days (by borrowing from months, adding the actual length of the preceding Hijri month) and negative months (by borrowing from years). This matches the behavior expected by the Temporal specification for calendar-aware subtraction.
|
||||
|
||||
## Class Hierarchy
|
||||
|
||||
```
|
||||
HijriCalendar ← base, accepts any CalendarEngine
|
||||
UaqCalendar ← wraps hijri-core 'uaq' engine
|
||||
FcnaCalendar ← wraps hijri-core 'fcna' engine
|
||||
```
|
||||
|
||||
The subclasses exist for naming and documentation clarity. All logic is in `HijriCalendar`. Extending with a custom engine requires only instantiating `HijriCalendar` directly with a registered engine.
|
||||
|
||||
## Dependency on hijri-core
|
||||
|
||||
hijri-core provides:
|
||||
|
||||
- `CalendarEngine` interface (the contract this package relies on)
|
||||
- `getCalendar(id)` registry function
|
||||
- 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.
|
||||
|
||||
## Build Output
|
||||
|
||||
The package ships two formats from a single TypeScript source:
|
||||
|
||||
| File | Format | Usage |
|
||||
|---|---|---|
|
||||
| `dist/index.mjs` | ESM | `import { uaqCalendar } from 'temporal-hijri'` |
|
||||
| `dist/index.cjs` | CommonJS | `const { uaqCalendar } = require('temporal-hijri')` |
|
||||
| `dist/index.d.ts` | Type declarations (CJS) | TypeScript + CJS |
|
||||
| `dist/index.d.mts` | Type declarations (ESM) | TypeScript + ESM |
|
||||
|
||||
Both `hijri-core` and `@js-temporal/polyfill` are declared `external` in the build config and listed as peer dependencies. They are not bundled.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **Temporal is still a proposal.** Native `Temporal` is available in Chrome 127+, Firefox 139+, and Safari 18.2+. Node.js ships Temporal behind a flag. The package works with `@js-temporal/polyfill` for full compatibility today.
|
||||
- **UAQ coverage is bounded.** Dates before 1318 AH (1900 CE) or after 1500 AH (2076 CE) throw `RangeError` from the UAQ calendar. Use FCNA for dates outside this range.
|
||||
- **`monthDayFromFields` requires a reference year.** Without a year, the function defaults to 1444 AH. The resulting `PlainMonthDay` is a structural type in ISO space and does not preserve the Hijri year.
|
||||
- **`weekOfYear` is approximate.** The Hijri calendar has no standardized week numbering system. The implementation uses `ceil(dayOfYear / 7)`, which gives a consistent ordering but does not align with any official standard.
|
||||
|
||||
---
|
||||
|
||||
[Home](Home) · [API Reference](API-Reference) · [Architecture](Architecture)
|
||||
34
.wiki/Home.md
Normal file
34
.wiki/Home.md
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# temporal-hijri
|
||||
|
||||
Temporal Calendar Protocol implementation for the Hijri calendar system.
|
||||
|
||||
This package provides `UaqCalendar` and `FcnaCalendar` as plug-in calendar objects for the TC39 `Temporal` proposal. It bridges the ISO-centric `Temporal.PlainDate` API with Hijri date arithmetic via [hijri-core](https://github.com/acamarata/hijri-core).
|
||||
|
||||
## Pages
|
||||
|
||||
- [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
|
||||
|
||||
## Calendar systems
|
||||
|
||||
| Calendar | ID | Description |
|
||||
|---|---|---|
|
||||
| Umm al-Qura | `hijri-uaq` | Official Saudi calendar, table-driven, covers 1318-1500 AH |
|
||||
| FCNA/ISNA | `hijri-fcna` | North American standard, astronomical new moon calculation |
|
||||
|
||||
## Requirements
|
||||
|
||||
- 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)
|
||||
|
||||
---
|
||||
|
||||
[Home](Home) · [API Reference](API-Reference) · [Architecture](Architecture)
|
||||
19
CHANGELOG.md
Normal file
19
CHANGELOG.md
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
# Changelog
|
||||
|
||||
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.0.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [1.0.0] - 2026-02-25
|
||||
|
||||
### 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` 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
|
||||
- Peer dependency on `hijri-core ^1.0.0` for conversion logic
|
||||
- Optional peer dependency on `@js-temporal/polyfill ^0.4.0`
|
||||
21
LICENSE
Normal file
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2026 Aric Camarata
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
199
README.md
Normal file
199
README.md
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
[](https://www.npmjs.com/package/temporal-hijri)
|
||||
[](https://github.com/acamarata/temporal-hijri/actions/workflows/ci.yml)
|
||||
[](LICENSE)
|
||||
|
||||
# temporal-hijri
|
||||
|
||||
Temporal Calendar Protocol implementation for the Hijri calendar. Works with the TC39 Temporal proposal and `@js-temporal/polyfill`.
|
||||
|
||||
Provides `UaqCalendar` (Umm al-Qura) and `FcnaCalendar` (FCNA/ISNA) as plug-in calendars for `Temporal.PlainDate` and related types. The underlying conversion logic comes from [hijri-core](https://github.com/acamarata/hijri-core), a zero-dependency Hijri engine with table-driven UAQ data and astronomical FCNA calculations.
|
||||
|
||||
---
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
pnpm add temporal-hijri hijri-core
|
||||
```
|
||||
|
||||
If you are using the polyfill instead of the native `Temporal` API:
|
||||
|
||||
```bash
|
||||
pnpm add temporal-hijri hijri-core @js-temporal/polyfill
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
|
||||
```typescript
|
||||
import { Temporal } from '@js-temporal/polyfill'; // or use native Temporal
|
||||
import { uaqCalendar } from 'temporal-hijri';
|
||||
|
||||
// Convert an ISO date to Hijri coordinates
|
||||
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||
|
||||
console.log(uaqCalendar.year(isoDate)); // 1444
|
||||
console.log(uaqCalendar.month(isoDate)); // 9 (Ramadan)
|
||||
console.log(uaqCalendar.day(isoDate)); // 1
|
||||
console.log(uaqCalendar.monthCode(isoDate)); // "M09"
|
||||
console.log(uaqCalendar.inLeapYear(isoDate)); // false (1444 is 354 days)
|
||||
|
||||
// Convert Hijri coordinates back to ISO
|
||||
const ramadan = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||
console.log(ramadan.toString()); // "2023-03-23"
|
||||
|
||||
// Arithmetic in Hijri space
|
||||
const { Duration } = Temporal;
|
||||
const nextMonth = uaqCalendar.dateAdd(isoDate, new Duration(0, 1));
|
||||
console.log(uaqCalendar.month(nextMonth)); // 10 (Shawwal)
|
||||
console.log(nextMonth.toString()); // "2023-04-21"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Calendar Classes
|
||||
|
||||
### `UaqCalendar`
|
||||
|
||||
Implements the Umm al-Qura calendar, the official calendar of Saudi Arabia. Month boundaries come from pre-calculated tables covering 1318-1500 AH (Gregorian 1900-2076). The most widely used Hijri calendar standard for civil and religious purposes.
|
||||
|
||||
```typescript
|
||||
import { UaqCalendar } from 'temporal-hijri';
|
||||
const cal = new UaqCalendar(); // cal.id === 'hijri-uaq'
|
||||
```
|
||||
|
||||
### `FcnaCalendar`
|
||||
|
||||
Implements the FCNA/ISNA calendar used by the Fiqh Council of North America and the Islamic Society of North America. Month starts are determined by astronomical new moon calculation (Meeus Chapter 49): if conjunction occurs before 12:00 UTC, the month begins the next day; if at or after noon, it begins the day after that.
|
||||
|
||||
```typescript
|
||||
import { FcnaCalendar } from 'temporal-hijri';
|
||||
const cal = new FcnaCalendar(); // cal.id === 'hijri-fcna'
|
||||
```
|
||||
|
||||
### `HijriCalendar` (base class)
|
||||
|
||||
The base implementation. Accepts any `CalendarEngine` from hijri-core. Use this to build a Temporal calendar from a custom engine registered via `hijri-core`'s `registerCalendar()`.
|
||||
|
||||
```typescript
|
||||
import { HijriCalendar } from 'temporal-hijri';
|
||||
import { getCalendar, registerCalendar } from 'hijri-core';
|
||||
|
||||
// Register a custom engine first
|
||||
registerCalendar('my-calendar', myEngine);
|
||||
const cal = new HijriCalendar(getCalendar('my-calendar'));
|
||||
// cal.id === 'hijri-my-calendar'
|
||||
```
|
||||
|
||||
### Convenience singletons
|
||||
|
||||
`uaqCalendar` and `fcnaCalendar` are pre-constructed instances. They are shared objects and safe to reuse across calls.
|
||||
|
||||
```typescript
|
||||
import { uaqCalendar, fcnaCalendar } from 'temporal-hijri';
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API
|
||||
|
||||
All methods receive a `Temporal.PlainDate` with an ISO (Gregorian) calendar. The PlainDate carries the ISO year/month/day; the calendar object interprets those coordinates.
|
||||
|
||||
| Method | Returns | Description |
|
||||
|---|---|---|
|
||||
| `year(date)` | `number` | Hijri year |
|
||||
| `month(date)` | `number` | Hijri month (1-12) |
|
||||
| `monthCode(date)` | `string` | Month code: `"M01"` through `"M12"` |
|
||||
| `day(date)` | `number` | Day of the Hijri month (1-29 or 1-30) |
|
||||
| `daysInMonth(date)` | `number` | Length of the Hijri month (29 or 30) |
|
||||
| `daysInYear(date)` | `number` | Days in the Hijri year (354 or 355) |
|
||||
| `monthsInYear(date)` | `number` | Always `12` |
|
||||
| `inLeapYear(date)` | `boolean` | `true` if the year has 355 days |
|
||||
| `dayOfWeek(date)` | `number` | ISO weekday: 1=Monday, 7=Sunday |
|
||||
| `dayOfYear(date)` | `number` | Day position within the Hijri year |
|
||||
| `weekOfYear(date)` | `number` | Week position within the Hijri year |
|
||||
| `daysInWeek(date)` | `number` | Always `7` |
|
||||
| `dateFromFields(fields)` | `Temporal.PlainDate` | Construct ISO PlainDate from `{year, month, day}` in Hijri |
|
||||
| `yearMonthFromFields(fields)` | `Temporal.PlainYearMonth` | Construct from `{year, month}` in Hijri |
|
||||
| `monthDayFromFields(fields)` | `Temporal.PlainMonthDay` | Construct from `{month, day}` in Hijri |
|
||||
| `dateAdd(date, duration)` | `Temporal.PlainDate` | Add a duration; years/months applied in Hijri space, days in ISO space |
|
||||
| `dateUntil(one, two, options)` | `Temporal.Duration` | Difference between two dates; supports `largestUnit: 'years'|'months'|'days'|'weeks'` |
|
||||
| `mergeFields(fields, additional)` | `Record` | Merge field objects (Temporal protocol requirement) |
|
||||
| `toString()` | `string` | Calendar identifier (`"hijri-uaq"` or `"hijri-fcna"`) |
|
||||
|
||||
---
|
||||
|
||||
## Calendar Systems
|
||||
|
||||
| System | ID | Authority | Method | Coverage |
|
||||
|---|---|---|---|---|
|
||||
| Umm al-Qura | `hijri-uaq` | KACST / Saudi Arabia | Pre-calculated tables | 1318-1500 AH (1900-2076 CE) |
|
||||
| FCNA/ISNA | `hijri-fcna` | Fiqh Council of North America | Astronomical new moon (Meeus) | Unlimited (calculated) |
|
||||
|
||||
UAQ dates outside 1318-1500 AH throw `RangeError`. FCNA is unbounded but loses precision for very early dates.
|
||||
|
||||
---
|
||||
|
||||
## Custom Calendars
|
||||
|
||||
Any engine registered in hijri-core can be wrapped in a Temporal calendar:
|
||||
|
||||
```typescript
|
||||
import { HijriCalendar } from 'temporal-hijri';
|
||||
import { registerCalendar, getCalendar } from 'hijri-core';
|
||||
import type { CalendarEngine } from 'hijri-core';
|
||||
|
||||
const myEngine: CalendarEngine = {
|
||||
id: 'local-sighting',
|
||||
toHijri(date) { /* ... */ return { hy, hm, hd }; },
|
||||
toGregorian(hy, hm, hd) { /* ... */ return new Date(...); },
|
||||
isValid(hy, hm, hd) { /* ... */ return true; },
|
||||
daysInMonth(hy, hm) { /* ... */ return 29; },
|
||||
};
|
||||
|
||||
registerCalendar('local-sighting', myEngine);
|
||||
const cal = new HijriCalendar(getCalendar('local-sighting'));
|
||||
// cal.id === 'hijri-local-sighting'
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## TypeScript
|
||||
|
||||
All types are exported:
|
||||
|
||||
```typescript
|
||||
import type { HijriDate, ConversionOptions, HijriCalendarOptions } from 'temporal-hijri';
|
||||
```
|
||||
|
||||
The package ships dual CJS/ESM builds with full `.d.ts` and `.d.mts` declarations.
|
||||
|
||||
---
|
||||
|
||||
## Compatibility
|
||||
|
||||
- Node.js 20, 22, 24
|
||||
- Any bundler supporting `exports` field (`Vite`, `Webpack 5`, `Rollup`, `esbuild`)
|
||||
- ESM (`import`) and CommonJS (`require`) — both provided
|
||||
- No native `Temporal` required: works entirely with `@js-temporal/polyfill`
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
Full reference, architecture notes, and algorithmic detail in the [wiki](https://github.com/acamarata/temporal-hijri/wiki).
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
---
|
||||
|
||||
## License
|
||||
|
||||
MIT. Copyright (c) 2026 Aric Camarata. See [LICENSE](LICENSE).
|
||||
67
package.json
Normal file
67
package.json
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
{
|
||||
"name": "temporal-hijri",
|
||||
"version": "1.0.0",
|
||||
"description": "Temporal Calendar Protocol implementation for the Hijri calendar system. Supports Umm al-Qura and FCNA calendars via hijri-core.",
|
||||
"author": "Aric Camarata",
|
||||
"license": "MIT",
|
||||
"main": "./dist/index.cjs",
|
||||
"module": "./dist/index.mjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
|
||||
"require": { "types": "./dist/index.d.ts", "default": "./dist/index.cjs" }
|
||||
}
|
||||
},
|
||||
"sideEffects": false,
|
||||
"files": [
|
||||
"dist/index.cjs",
|
||||
"dist/index.mjs",
|
||||
"dist/index.d.ts",
|
||||
"dist/index.d.mts",
|
||||
"README.md",
|
||||
"CHANGELOG.md",
|
||||
"LICENSE"
|
||||
],
|
||||
"engines": { "node": ">=20" },
|
||||
"packageManager": "pnpm@10.30.1",
|
||||
"scripts": {
|
||||
"build": "tsup",
|
||||
"typecheck": "tsc --noEmit",
|
||||
"pretest": "tsup",
|
||||
"test": "node test.mjs && node test-cjs.cjs",
|
||||
"prepublishOnly": "tsup"
|
||||
},
|
||||
"keywords": [
|
||||
"temporal",
|
||||
"tc39",
|
||||
"hijri",
|
||||
"islamic",
|
||||
"calendar",
|
||||
"umm-al-qura",
|
||||
"fcna",
|
||||
"gregorian",
|
||||
"converter",
|
||||
"typescript"
|
||||
],
|
||||
"peerDependencies": {
|
||||
"hijri-core": "^1.0.0",
|
||||
"@js-temporal/polyfill": "^0.4.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@js-temporal/polyfill": {
|
||||
"optional": true
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@js-temporal/polyfill": "^0.4.4",
|
||||
"@types/node": "^22.0.0",
|
||||
"hijri-core": "file:../hijri-core",
|
||||
"tsup": "^8.0.0",
|
||||
"typescript": "^5.5.0"
|
||||
},
|
||||
"publishConfig": { "access": "public", "registry": "https://registry.npmjs.org/" },
|
||||
"repository": { "type": "git", "url": "git+https://github.com/acamarata/temporal-hijri.git" },
|
||||
"homepage": "https://github.com/acamarata/temporal-hijri#readme",
|
||||
"bugs": { "url": "https://github.com/acamarata/temporal-hijri/issues" }
|
||||
}
|
||||
956
pnpm-lock.yaml
Normal file
956
pnpm-lock.yaml
Normal file
|
|
@ -0,0 +1,956 @@
|
|||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@js-temporal/polyfill':
|
||||
specifier: ^0.4.4
|
||||
version: 0.4.4
|
||||
'@types/node':
|
||||
specifier: ^22.0.0
|
||||
version: 22.19.11
|
||||
hijri-core:
|
||||
specifier: file:../hijri-core
|
||||
version: file:../hijri-core
|
||||
tsup:
|
||||
specifier: ^8.0.0
|
||||
version: 8.5.1(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.5.0
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.3':
|
||||
resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.3':
|
||||
resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.3':
|
||||
resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.3':
|
||||
resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.3':
|
||||
resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.3':
|
||||
resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.3':
|
||||
resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.3':
|
||||
resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.3':
|
||||
resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.3':
|
||||
resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.3':
|
||||
resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.3':
|
||||
resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.3':
|
||||
resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.3':
|
||||
resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.3':
|
||||
resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.3':
|
||||
resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.3':
|
||||
resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.3':
|
||||
resolution: {integrity: sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.3':
|
||||
resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||
|
||||
'@js-temporal/polyfill@0.4.4':
|
||||
resolution: {integrity: sha512-2X6bvghJ/JAoZO52lbgyAPFj8uCflhTo2g7nkFzEQdXd/D8rEeD4HtmTEpmtGCva260fcd66YNXBOYdnmHqSOg==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.59.0':
|
||||
resolution: {integrity: sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||
resolution: {integrity: sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||
resolution: {integrity: sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||
resolution: {integrity: sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [glibc]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||
resolution: {integrity: sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
libc: [musl]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||
resolution: {integrity: sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||
resolution: {integrity: sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||
resolution: {integrity: sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||
resolution: {integrity: sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/estree@1.0.8':
|
||||
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
|
||||
|
||||
'@types/node@22.19.11':
|
||||
resolution: {integrity: sha512-BH7YwL6rA93ReqeQS1c4bsPpcfOmJasG+Fkr6Y59q83f9M1WcBRHR2vM+P9eOisYRcN3ujQoiZY8uk5W+1WL8w==}
|
||||
|
||||
acorn@8.16.0:
|
||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
any-promise@1.3.0:
|
||||
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
|
||||
|
||||
bundle-require@5.1.0:
|
||||
resolution: {integrity: sha512-3WrrOuZiyaaZPWiEt4G3+IffISVC9HYlWueJEBWED4ZH4aIAC2PnkdnuRrR94M+w6yGWn4AglWtJtBI8YqvgoA==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
peerDependencies:
|
||||
esbuild: '>=0.18'
|
||||
|
||||
cac@6.7.14:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
chokidar@4.0.3:
|
||||
resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==}
|
||||
engines: {node: '>= 14.16.0'}
|
||||
|
||||
commander@4.1.1:
|
||||
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
confbox@0.1.8:
|
||||
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
|
||||
|
||||
consola@3.4.2:
|
||||
resolution: {integrity: sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==}
|
||||
engines: {node: ^14.18.0 || >=16.10.0}
|
||||
|
||||
debug@4.4.3:
|
||||
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||
engines: {node: '>=6.0'}
|
||||
peerDependencies:
|
||||
supports-color: '*'
|
||||
peerDependenciesMeta:
|
||||
supports-color:
|
||||
optional: true
|
||||
|
||||
esbuild@0.27.3:
|
||||
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
resolution: {integrity: sha512-pVIECanWFC61Hzl2+oOCtoJ3F17kglZC/6N94eRWycFgBH35hHx0Li604ZIzhseh97mf2p0cv7vVrOZGoqhlEg==}
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
hijri-core@file:../hijri-core:
|
||||
resolution: {directory: ../hijri-core, type: directory}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
joycon@3.1.1:
|
||||
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
jsbi@4.3.2:
|
||||
resolution: {integrity: sha512-9fqMSQbhJykSeii05nxKl4m6Eqn2P6rOlYiS+C5Dr/HPIU/7yZxu5qzbs40tgaFORiw2Amd0mirjxatXYMkIew==}
|
||||
|
||||
lilconfig@3.1.3:
|
||||
resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
|
||||
engines: {node: '>=14'}
|
||||
|
||||
lines-and-columns@1.2.4:
|
||||
resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==}
|
||||
|
||||
load-tsconfig@0.2.5:
|
||||
resolution: {integrity: sha512-IXO6OCs9yg8tMKzfPZ1YmheJbZCiEsnBdcB03l0OcfK9prKnJb96siuHCr5Fl37/yo9DnKU+TLpxzTUspw9shg==}
|
||||
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
|
||||
|
||||
magic-string@0.30.21:
|
||||
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||
|
||||
mlly@1.8.0:
|
||||
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
|
||||
|
||||
ms@2.1.3:
|
||||
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||
|
||||
mz@2.7.0:
|
||||
resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==}
|
||||
|
||||
object-assign@4.1.1:
|
||||
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
pathe@2.0.3:
|
||||
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.3:
|
||||
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
pirates@4.0.7:
|
||||
resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==}
|
||||
engines: {node: '>= 6'}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==}
|
||||
|
||||
postcss-load-config@6.0.1:
|
||||
resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==}
|
||||
engines: {node: '>= 18'}
|
||||
peerDependencies:
|
||||
jiti: '>=1.21.0'
|
||||
postcss: '>=8.0.9'
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
jiti:
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
readdirp@4.1.2:
|
||||
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
|
||||
engines: {node: '>= 14.18.0'}
|
||||
|
||||
resolve-from@5.0.0:
|
||||
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
rollup@4.59.0:
|
||||
resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
source-map@0.7.6:
|
||||
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
|
||||
engines: {node: '>= 12'}
|
||||
|
||||
sucrase@3.35.1:
|
||||
resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==}
|
||||
engines: {node: '>=16 || 14 >=14.17'}
|
||||
hasBin: true
|
||||
|
||||
thenify-all@1.6.0:
|
||||
resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==}
|
||||
engines: {node: '>=0.8'}
|
||||
|
||||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
tinyexec@0.3.2:
|
||||
resolution: {integrity: sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
tree-kill@1.2.2:
|
||||
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
|
||||
hasBin: true
|
||||
|
||||
ts-interface-checker@0.1.13:
|
||||
resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==}
|
||||
|
||||
tslib@2.8.1:
|
||||
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||
|
||||
tsup@8.5.1:
|
||||
resolution: {integrity: sha512-xtgkqwdhpKWr3tKPmCkvYmS9xnQK3m3XgxZHwSUjvfTjp7YfXe5tT3GgWi0F2N+ZSMsOeWeZFh7ZZFg5iPhing==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@microsoft/api-extractor': ^7.36.0
|
||||
'@swc/core': ^1
|
||||
postcss: ^8.4.12
|
||||
typescript: '>=4.5.0'
|
||||
peerDependenciesMeta:
|
||||
'@microsoft/api-extractor':
|
||||
optional: true
|
||||
'@swc/core':
|
||||
optional: true
|
||||
postcss:
|
||||
optional: true
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
ufo@1.6.3:
|
||||
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
|
||||
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.3':
|
||||
optional: true
|
||||
|
||||
'@jridgewell/gen-mapping@0.3.13':
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.31':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@js-temporal/polyfill@0.4.4':
|
||||
dependencies:
|
||||
jsbi: 4.3.2
|
||||
tslib: 2.8.1
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.59.0':
|
||||
optional: true
|
||||
|
||||
'@types/estree@1.0.8': {}
|
||||
|
||||
'@types/node@22.19.11':
|
||||
dependencies:
|
||||
undici-types: 6.21.0
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
any-promise@1.3.0: {}
|
||||
|
||||
bundle-require@5.1.0(esbuild@0.27.3):
|
||||
dependencies:
|
||||
esbuild: 0.27.3
|
||||
load-tsconfig: 0.2.5
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
chokidar@4.0.3:
|
||||
dependencies:
|
||||
readdirp: 4.1.2
|
||||
|
||||
commander@4.1.1: {}
|
||||
|
||||
confbox@0.1.8: {}
|
||||
|
||||
consola@3.4.2: {}
|
||||
|
||||
debug@4.4.3:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
esbuild@0.27.3:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.3
|
||||
'@esbuild/android-arm': 0.27.3
|
||||
'@esbuild/android-arm64': 0.27.3
|
||||
'@esbuild/android-x64': 0.27.3
|
||||
'@esbuild/darwin-arm64': 0.27.3
|
||||
'@esbuild/darwin-x64': 0.27.3
|
||||
'@esbuild/freebsd-arm64': 0.27.3
|
||||
'@esbuild/freebsd-x64': 0.27.3
|
||||
'@esbuild/linux-arm': 0.27.3
|
||||
'@esbuild/linux-arm64': 0.27.3
|
||||
'@esbuild/linux-ia32': 0.27.3
|
||||
'@esbuild/linux-loong64': 0.27.3
|
||||
'@esbuild/linux-mips64el': 0.27.3
|
||||
'@esbuild/linux-ppc64': 0.27.3
|
||||
'@esbuild/linux-riscv64': 0.27.3
|
||||
'@esbuild/linux-s390x': 0.27.3
|
||||
'@esbuild/linux-x64': 0.27.3
|
||||
'@esbuild/netbsd-arm64': 0.27.3
|
||||
'@esbuild/netbsd-x64': 0.27.3
|
||||
'@esbuild/openbsd-arm64': 0.27.3
|
||||
'@esbuild/openbsd-x64': 0.27.3
|
||||
'@esbuild/openharmony-arm64': 0.27.3
|
||||
'@esbuild/sunos-x64': 0.27.3
|
||||
'@esbuild/win32-arm64': 0.27.3
|
||||
'@esbuild/win32-ia32': 0.27.3
|
||||
'@esbuild/win32-x64': 0.27.3
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.3):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.3
|
||||
|
||||
fix-dts-default-cjs-exports@1.0.1:
|
||||
dependencies:
|
||||
magic-string: 0.30.21
|
||||
mlly: 1.8.0
|
||||
rollup: 4.59.0
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
hijri-core@file:../hijri-core: {}
|
||||
|
||||
joycon@3.1.1: {}
|
||||
|
||||
jsbi@4.3.2: {}
|
||||
|
||||
lilconfig@3.1.3: {}
|
||||
|
||||
lines-and-columns@1.2.4: {}
|
||||
|
||||
load-tsconfig@0.2.5: {}
|
||||
|
||||
magic-string@0.30.21:
|
||||
dependencies:
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
mlly@1.8.0:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
pathe: 2.0.3
|
||||
pkg-types: 1.3.1
|
||||
ufo: 1.6.3
|
||||
|
||||
ms@2.1.3: {}
|
||||
|
||||
mz@2.7.0:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
object-assign: 4.1.1
|
||||
thenify-all: 1.6.0
|
||||
|
||||
object-assign@4.1.1: {}
|
||||
|
||||
pathe@2.0.3: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.3: {}
|
||||
|
||||
pirates@4.0.7: {}
|
||||
|
||||
pkg-types@1.3.1:
|
||||
dependencies:
|
||||
confbox: 0.1.8
|
||||
mlly: 1.8.0
|
||||
pathe: 2.0.3
|
||||
|
||||
postcss-load-config@6.0.1:
|
||||
dependencies:
|
||||
lilconfig: 3.1.3
|
||||
|
||||
readdirp@4.1.2: {}
|
||||
|
||||
resolve-from@5.0.0: {}
|
||||
|
||||
rollup@4.59.0:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.8
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.59.0
|
||||
'@rollup/rollup-android-arm64': 4.59.0
|
||||
'@rollup/rollup-darwin-arm64': 4.59.0
|
||||
'@rollup/rollup-darwin-x64': 4.59.0
|
||||
'@rollup/rollup-freebsd-arm64': 4.59.0
|
||||
'@rollup/rollup-freebsd-x64': 4.59.0
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.59.0
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.59.0
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-arm64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-loong64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-loong64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-ppc64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-ppc64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.59.0
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-x64-gnu': 4.59.0
|
||||
'@rollup/rollup-linux-x64-musl': 4.59.0
|
||||
'@rollup/rollup-openbsd-x64': 4.59.0
|
||||
'@rollup/rollup-openharmony-arm64': 4.59.0
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.59.0
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.59.0
|
||||
'@rollup/rollup-win32-x64-gnu': 4.59.0
|
||||
'@rollup/rollup-win32-x64-msvc': 4.59.0
|
||||
fsevents: 2.3.3
|
||||
|
||||
source-map@0.7.6: {}
|
||||
|
||||
sucrase@3.35.1:
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
commander: 4.1.1
|
||||
lines-and-columns: 1.2.4
|
||||
mz: 2.7.0
|
||||
pirates: 4.0.7
|
||||
tinyglobby: 0.2.15
|
||||
ts-interface-checker: 0.1.13
|
||||
|
||||
thenify-all@1.6.0:
|
||||
dependencies:
|
||||
thenify: 3.3.1
|
||||
|
||||
thenify@3.3.1:
|
||||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
tinyexec@0.3.2: {}
|
||||
|
||||
tinyglobby@0.2.15:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
|
||||
tree-kill@1.2.2: {}
|
||||
|
||||
ts-interface-checker@0.1.13: {}
|
||||
|
||||
tslib@2.8.1: {}
|
||||
|
||||
tsup@8.5.1(typescript@5.9.3):
|
||||
dependencies:
|
||||
bundle-require: 5.1.0(esbuild@0.27.3)
|
||||
cac: 6.7.14
|
||||
chokidar: 4.0.3
|
||||
consola: 3.4.2
|
||||
debug: 4.4.3
|
||||
esbuild: 0.27.3
|
||||
fix-dts-default-cjs-exports: 1.0.1
|
||||
joycon: 3.1.1
|
||||
picocolors: 1.1.1
|
||||
postcss-load-config: 6.0.1
|
||||
resolve-from: 5.0.0
|
||||
rollup: 4.59.0
|
||||
source-map: 0.7.6
|
||||
sucrase: 3.35.1
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.15
|
||||
tree-kill: 1.2.2
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
transitivePeerDependencies:
|
||||
- jiti
|
||||
- supports-color
|
||||
- tsx
|
||||
- yaml
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
ufo@1.6.3: {}
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
2
pnpm-workspace.yaml
Normal file
2
pnpm-workspace.yaml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
onlyBuiltDependencies:
|
||||
- esbuild
|
||||
22
src/calendars/FcnaCalendar.ts
Normal file
22
src/calendars/FcnaCalendar.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { getCalendar } from 'hijri-core';
|
||||
import { HijriCalendar } from './HijriCalendar';
|
||||
|
||||
/**
|
||||
* Temporal calendar implementation for the FCNA/ISNA calendar.
|
||||
*
|
||||
* The Fiqh Council of North America (FCNA) calendar, also used by the Islamic
|
||||
* Society of North America (ISNA), determines month starts through astronomical
|
||||
* calculation: a new month begins the day after the conjunction (new moon) if
|
||||
* that conjunction occurs before 12:00 noon UTC, or two days after if at or
|
||||
* after noon. This criterion enables global date-setting without local moon
|
||||
* sighting, making it popular for diaspora Muslim communities in North America
|
||||
* and Europe.
|
||||
*
|
||||
* Calendar engine: hijri-core FCNA (Meeus Chapter 49 calculations).
|
||||
* Calendar ID: "hijri-fcna"
|
||||
*/
|
||||
export class FcnaCalendar extends HijriCalendar {
|
||||
constructor() {
|
||||
super(getCalendar('fcna'));
|
||||
}
|
||||
}
|
||||
320
src/calendars/HijriCalendar.ts
Normal file
320
src/calendars/HijriCalendar.ts
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
import { Temporal } from '@js-temporal/polyfill';
|
||||
import type { CalendarEngine } from 'hijri-core';
|
||||
|
||||
type DateUnit = 'year' | 'years' | 'month' | 'months' | 'week' | 'weeks' | 'day' | 'days' | 'auto';
|
||||
|
||||
/**
|
||||
* Base class implementing the TC39 Temporal Calendar Protocol for Hijri calendars.
|
||||
*
|
||||
* Coordinate bridging: Temporal.PlainDate operates in the ISO (Gregorian) calendar.
|
||||
* Every calendar method receives a PlainDate with ISO year/month/day, and must
|
||||
* return results in the Hijri calendar's coordinate system. The bridge is
|
||||
* toHijri() and fromHijri(), which delegate to the injected CalendarEngine.
|
||||
*
|
||||
* Arithmetic strategy for dateAdd():
|
||||
* - Year and month deltas are applied in Hijri space (correct handling of
|
||||
* variable month lengths).
|
||||
* - Day and week deltas are applied in ISO space after the Hijri addition,
|
||||
* so that "add 30 days" always means exactly 30 days.
|
||||
*/
|
||||
export class HijriCalendar {
|
||||
protected readonly engine: CalendarEngine;
|
||||
readonly id: string;
|
||||
|
||||
constructor(engine: CalendarEngine) {
|
||||
this.engine = engine;
|
||||
this.id = `hijri-${engine.id}`;
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a Temporal.PlainDate (ISO calendar) to Hijri coordinates.
|
||||
*
|
||||
* Uses the local-time Date constructor so that the date components passed to
|
||||
* the engine match the calendar date exactly, regardless of host timezone.
|
||||
* The UAQ engine reads local components; the FCNA engine reads UTC components.
|
||||
* Because we construct with new Date(y, m, d) the local date always matches
|
||||
* the intended calendar date.
|
||||
*/
|
||||
protected toHijri(date: Temporal.PlainDate): { hy: number; hm: number; hd: number } {
|
||||
const jsDate = new Date(date.year, date.month - 1, date.day);
|
||||
const hijri = this.engine.toHijri(jsDate);
|
||||
if (!hijri) {
|
||||
throw new RangeError(
|
||||
`Date ${date.toString()} is out of range for the ${this.id} calendar`
|
||||
);
|
||||
}
|
||||
return hijri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Hijri coordinates to a Temporal.PlainDate (ISO calendar).
|
||||
*
|
||||
* The engine returns a Date whose UTC components represent the Gregorian date.
|
||||
* We extract those UTC components to construct the PlainDate.
|
||||
*/
|
||||
protected fromHijri(hy: number, hm: number, hd: number): Temporal.PlainDate {
|
||||
const greg = this.engine.toGregorian(hy, hm, hd);
|
||||
if (!greg) {
|
||||
throw new RangeError(
|
||||
`Hijri date ${hy}/${hm}/${hd} is out of range for the ${this.id} calendar`
|
||||
);
|
||||
}
|
||||
return Temporal.PlainDate.from({
|
||||
year: greg.getUTCFullYear(),
|
||||
month: greg.getUTCMonth() + 1,
|
||||
day: greg.getUTCDate(),
|
||||
});
|
||||
}
|
||||
|
||||
// ── Field accessors ───────────────────────────────────────────────────────
|
||||
|
||||
year(date: Temporal.PlainDate): number {
|
||||
return this.toHijri(date).hy;
|
||||
}
|
||||
|
||||
month(date: Temporal.PlainDate): number {
|
||||
return this.toHijri(date).hm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Month code per the Temporal proposal: "M01".."M12".
|
||||
* Hijri months are always 1-12 (no leap/intercalary month), so the code is
|
||||
* simply the zero-padded month number.
|
||||
*/
|
||||
monthCode(date: Temporal.PlainDate): string {
|
||||
const { hm } = this.toHijri(date);
|
||||
return `M${String(hm).padStart(2, '0')}`;
|
||||
}
|
||||
|
||||
day(date: Temporal.PlainDate): number {
|
||||
return this.toHijri(date).hd;
|
||||
}
|
||||
|
||||
// ── Month and year metrics ─────────────────────────────────────────────────
|
||||
|
||||
daysInMonth(date: Temporal.PlainDate): number {
|
||||
const { hy, hm } = this.toHijri(date);
|
||||
return this.engine.daysInMonth(hy, hm);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sum all 12 month lengths for the Hijri year. Standard lunar years are 354
|
||||
* days; leap years (with an added day in Dhul-Hijja) are 355 days.
|
||||
*/
|
||||
daysInYear(date: Temporal.PlainDate): number {
|
||||
const { hy } = this.toHijri(date);
|
||||
let total = 0;
|
||||
for (let m = 1; m <= 12; m++) {
|
||||
total += this.engine.daysInMonth(hy, m);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
monthsInYear(_date: Temporal.PlainDate): number {
|
||||
return 12;
|
||||
}
|
||||
|
||||
inLeapYear(date: Temporal.PlainDate): boolean {
|
||||
return this.daysInYear(date) === 355;
|
||||
}
|
||||
|
||||
// ── Day-of-week and day-of-year ────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* ISO weekday: 1 = Monday, 7 = Sunday.
|
||||
* PlainDate.dayOfWeek on an ISO-calendar date already gives ISO weekday,
|
||||
* so no conversion is needed.
|
||||
*/
|
||||
dayOfWeek(date: Temporal.PlainDate): number {
|
||||
return date.dayOfWeek;
|
||||
}
|
||||
|
||||
/**
|
||||
* Day within the Hijri year. Accumulates full months before the current one,
|
||||
* then adds the day-of-month offset.
|
||||
*/
|
||||
dayOfYear(date: Temporal.PlainDate): number {
|
||||
const { hy, hm, hd } = this.toHijri(date);
|
||||
let total = hd;
|
||||
for (let m = 1; m < hm; m++) {
|
||||
total += this.engine.daysInMonth(hy, m);
|
||||
}
|
||||
return total;
|
||||
}
|
||||
|
||||
weekOfYear(date: Temporal.PlainDate): number {
|
||||
return Math.ceil(this.dayOfYear(date) / 7);
|
||||
}
|
||||
|
||||
daysInWeek(_date: Temporal.PlainDate): number {
|
||||
return 7;
|
||||
}
|
||||
|
||||
// ── Construction from fields ───────────────────────────────────────────────
|
||||
|
||||
dateFromFields(
|
||||
fields: { year: number; month: number; day: number },
|
||||
_options?: { overflow?: 'constrain' | 'reject' }
|
||||
): Temporal.PlainDate {
|
||||
return this.fromHijri(fields.year, fields.month, fields.day);
|
||||
}
|
||||
|
||||
yearMonthFromFields(
|
||||
fields: { year: number; month: number },
|
||||
_options?: { overflow?: 'constrain' | 'reject' }
|
||||
): Temporal.PlainYearMonth {
|
||||
const isoDate = this.fromHijri(fields.year, fields.month, 1);
|
||||
return Temporal.PlainYearMonth.from({
|
||||
year: isoDate.year,
|
||||
month: isoDate.month,
|
||||
});
|
||||
}
|
||||
|
||||
monthDayFromFields(
|
||||
fields: { month: number; day: number; year?: number },
|
||||
_options?: { overflow?: 'constrain' | 'reject' }
|
||||
): Temporal.PlainMonthDay {
|
||||
// A reference year is needed to resolve the Hijri month/day to an ISO date.
|
||||
// Default to 1444 AH (2022-2023 CE), a recent well-covered year.
|
||||
const year = fields.year ?? 1444;
|
||||
const isoDate = this.fromHijri(year, fields.month, fields.day);
|
||||
return Temporal.PlainMonthDay.from({
|
||||
month: isoDate.month,
|
||||
day: isoDate.day,
|
||||
});
|
||||
}
|
||||
|
||||
// ── Arithmetic ─────────────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* Add a duration to a Hijri date.
|
||||
*
|
||||
* Year and month additions are handled in Hijri space to preserve calendar
|
||||
* semantics (e.g., adding one month to 1 Ramadan yields 1 Shawwal, not a
|
||||
* fixed 30-day offset). Day and week additions are then applied in ISO space
|
||||
* so that they always represent exact day counts.
|
||||
*
|
||||
* When the day-of-month exceeds the target month's length after a Hijri-space
|
||||
* adjustment, it is clamped to the last valid day of that month.
|
||||
*/
|
||||
dateAdd(
|
||||
date: Temporal.PlainDate,
|
||||
duration: Temporal.Duration,
|
||||
_options?: { overflow?: 'constrain' | 'reject' }
|
||||
): Temporal.PlainDate {
|
||||
const { hy, hm, hd } = this.toHijri(date);
|
||||
|
||||
let newHy = hy + (duration.years ?? 0);
|
||||
let newHm = hm + (duration.months ?? 0);
|
||||
|
||||
// Normalize month overflow into years.
|
||||
while (newHm > 12) {
|
||||
newHm -= 12;
|
||||
newHy++;
|
||||
}
|
||||
while (newHm < 1) {
|
||||
newHm += 12;
|
||||
newHy--;
|
||||
}
|
||||
|
||||
// Clamp day to the valid range for the target month.
|
||||
const maxDay = this.engine.daysInMonth(newHy, newHm);
|
||||
const clampedDay = Math.min(hd, maxDay);
|
||||
|
||||
// Convert the Hijri result back to ISO, then apply the day/week delta.
|
||||
const intermediate = this.fromHijri(newHy, newHm, clampedDay);
|
||||
const dayDelta = (duration.days ?? 0) + (duration.weeks ?? 0) * 7;
|
||||
return dayDelta !== 0 ? intermediate.add({ days: dayDelta }) : intermediate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the difference between two Hijri dates.
|
||||
*
|
||||
* For simplicity and correctness across variable-length Hijri months, this
|
||||
* delegates to the underlying ISO PlainDate difference when the largest unit
|
||||
* is days or weeks. Year/month differences require a Hijri-space calculation.
|
||||
*/
|
||||
dateUntil(
|
||||
one: Temporal.PlainDate,
|
||||
two: Temporal.PlainDate,
|
||||
options?: { largestUnit?: DateUnit }
|
||||
): Temporal.Duration {
|
||||
const largestUnit: DateUnit = options?.largestUnit ?? 'days';
|
||||
|
||||
if (largestUnit === 'years' || largestUnit === 'year') {
|
||||
const h1 = this.toHijri(one);
|
||||
const h2 = this.toHijri(two);
|
||||
|
||||
let years = h2.hy - h1.hy;
|
||||
let months = h2.hm - h1.hm;
|
||||
let days = h2.hd - h1.hd;
|
||||
|
||||
// Borrow from months when days are negative.
|
||||
if (days < 0) {
|
||||
months--;
|
||||
// Add the day count of the previous Hijri month to resolve the borrow.
|
||||
let borrowHm = h2.hm - 1;
|
||||
let borrowHy = h2.hy;
|
||||
if (borrowHm < 1) {
|
||||
borrowHm = 12;
|
||||
borrowHy--;
|
||||
}
|
||||
days += this.engine.daysInMonth(borrowHy, borrowHm);
|
||||
}
|
||||
|
||||
// Borrow from years when months are negative.
|
||||
if (months < 0) {
|
||||
years--;
|
||||
months += 12;
|
||||
}
|
||||
|
||||
return new Temporal.Duration(years, months, 0, days);
|
||||
}
|
||||
|
||||
if (largestUnit === 'months' || largestUnit === 'month') {
|
||||
const h1 = this.toHijri(one);
|
||||
const h2 = this.toHijri(two);
|
||||
|
||||
let years = h2.hy - h1.hy;
|
||||
let months = h2.hm - h1.hm;
|
||||
let days = h2.hd - h1.hd;
|
||||
|
||||
if (days < 0) {
|
||||
months--;
|
||||
let borrowHm = h2.hm - 1;
|
||||
let borrowHy = h2.hy;
|
||||
if (borrowHm < 1) {
|
||||
borrowHm = 12;
|
||||
borrowHy--;
|
||||
}
|
||||
days += this.engine.daysInMonth(borrowHy, borrowHm);
|
||||
}
|
||||
|
||||
if (months < 0) {
|
||||
years--;
|
||||
months += 12;
|
||||
}
|
||||
|
||||
// Roll years into months.
|
||||
return new Temporal.Duration(0, years * 12 + months, 0, days);
|
||||
}
|
||||
|
||||
// For weeks and days, delegate to ISO arithmetic which is exact.
|
||||
if (largestUnit === 'weeks' || largestUnit === 'week') {
|
||||
return one.until(two, { largestUnit: 'weeks' });
|
||||
}
|
||||
|
||||
return one.until(two, { largestUnit: 'days' });
|
||||
}
|
||||
|
||||
mergeFields(
|
||||
fields: Record<string, unknown>,
|
||||
additionalFields: Record<string, unknown>
|
||||
): Record<string, unknown> {
|
||||
return { ...fields, ...additionalFields };
|
||||
}
|
||||
}
|
||||
20
src/calendars/UaqCalendar.ts
Normal file
20
src/calendars/UaqCalendar.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
import { getCalendar } from 'hijri-core';
|
||||
import { HijriCalendar } from './HijriCalendar';
|
||||
|
||||
/**
|
||||
* Temporal calendar implementation for the Umm al-Qura calendar.
|
||||
*
|
||||
* Umm al-Qura is the official calendar of Saudi Arabia, maintained by the
|
||||
* King Abdulaziz City for Science and Technology (KACST). It is the most
|
||||
* widely used Hijri calendar standard for civil and religious purposes across
|
||||
* the Muslim world. Month boundaries are determined by pre-calculated tables
|
||||
* rather than real-time moon sighting.
|
||||
*
|
||||
* Calendar engine: hijri-core UAQ (table-driven, covers 1318-1500 AH).
|
||||
* Calendar ID: "hijri-uaq"
|
||||
*/
|
||||
export class UaqCalendar extends HijriCalendar {
|
||||
constructor() {
|
||||
super(getCalendar('uaq'));
|
||||
}
|
||||
}
|
||||
13
src/index.ts
Normal file
13
src/index.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
export { HijriCalendar } from './calendars/HijriCalendar';
|
||||
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.
|
||||
import { UaqCalendar } from './calendars/UaqCalendar';
|
||||
import { FcnaCalendar } from './calendars/FcnaCalendar';
|
||||
|
||||
export const uaqCalendar = new UaqCalendar();
|
||||
export const fcnaCalendar = new FcnaCalendar();
|
||||
5
src/types.ts
Normal file
5
src/types.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export type { HijriDate, CalendarEngine, ConversionOptions } from 'hijri-core';
|
||||
|
||||
export interface HijriCalendarOptions {
|
||||
calendar?: string;
|
||||
}
|
||||
79
test-cjs.cjs
Normal file
79
test-cjs.cjs
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
'use strict';
|
||||
|
||||
/**
|
||||
* CJS test suite for temporal-hijri.
|
||||
*
|
||||
* Verifies that the CommonJS build loads and functions correctly via require().
|
||||
*/
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const { Temporal } = require('@js-temporal/polyfill');
|
||||
const { UaqCalendar, FcnaCalendar, uaqCalendar, fcnaCalendar } = require('./dist/index.cjs');
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const total = 8;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(`[${name}]... PASS`);
|
||||
passed++;
|
||||
} catch (err) {
|
||||
console.error(`[${name}]... FAIL: ${err.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
const isoRamadan = Temporal.PlainDate.from('2023-03-23');
|
||||
|
||||
// ── Class and singleton exports ───────────────────────────────────────────────
|
||||
|
||||
test('UaqCalendar class loads via require', () => {
|
||||
assert(typeof UaqCalendar === 'function', 'UaqCalendar should be a constructor');
|
||||
const cal = new UaqCalendar();
|
||||
assert.equal(cal.id, 'hijri-uaq');
|
||||
});
|
||||
|
||||
test('FcnaCalendar class loads via require', () => {
|
||||
assert(typeof FcnaCalendar === 'function', 'FcnaCalendar should be a constructor');
|
||||
const cal = new FcnaCalendar();
|
||||
assert.equal(cal.id, 'hijri-fcna');
|
||||
});
|
||||
|
||||
test('uaqCalendar singleton id', () => {
|
||||
assert.equal(uaqCalendar.id, 'hijri-uaq');
|
||||
});
|
||||
|
||||
test('fcnaCalendar singleton id', () => {
|
||||
assert.equal(fcnaCalendar.id, 'hijri-fcna');
|
||||
});
|
||||
|
||||
// ── Field accessors ───────────────────────────────────────────────────────────
|
||||
|
||||
test('uaqCalendar.year(2023-03-23) = 1444', () => {
|
||||
assert.equal(uaqCalendar.year(isoRamadan), 1444);
|
||||
});
|
||||
|
||||
test('uaqCalendar.month(2023-03-23) = 9', () => {
|
||||
assert.equal(uaqCalendar.month(isoRamadan), 9);
|
||||
});
|
||||
|
||||
test('uaqCalendar.day(2023-03-23) = 1', () => {
|
||||
assert.equal(uaqCalendar.day(isoRamadan), 1);
|
||||
});
|
||||
|
||||
// ── dateFromFields ─────────────────────────────────────────────────────────────
|
||||
|
||||
test('uaqCalendar.dateFromFields({year:1444, month:9, day:1}) = 2023-03-23', () => {
|
||||
const result = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||
assert.equal(result.toString(), '2023-03-23');
|
||||
});
|
||||
|
||||
// ── Summary ───────────────────────────────────────────────────────────────────
|
||||
|
||||
console.log(`\n${passed}/${total} tests passed`);
|
||||
if (failed > 0) {
|
||||
console.error(`${failed} test(s) failed`);
|
||||
process.exit(1);
|
||||
}
|
||||
151
test.mjs
Normal file
151
test.mjs
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
/**
|
||||
* ESM test suite for temporal-hijri.
|
||||
*
|
||||
* Test dates are chosen against verified Umm al-Qura and FCNA calendar data.
|
||||
* Reference point: 2023-03-23 = 1 Ramadan 1444 AH (both UAQ and FCNA agree).
|
||||
*/
|
||||
|
||||
import assert from 'node:assert/strict';
|
||||
import { Temporal } from '@js-temporal/polyfill';
|
||||
import { UaqCalendar, FcnaCalendar, uaqCalendar, fcnaCalendar } from './dist/index.mjs';
|
||||
|
||||
let passed = 0;
|
||||
let failed = 0;
|
||||
const total = 18;
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
fn();
|
||||
console.log(`[${name}]... PASS`);
|
||||
passed++;
|
||||
} catch (err) {
|
||||
console.error(`[${name}]... FAIL: ${err.message}`);
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
// Reference date: 2023-03-23 = 1 Ramadan 1444 AH
|
||||
const isoRamadan = Temporal.PlainDate.from('2023-03-23');
|
||||
// 2023-04-21 = 1 Shawwal 1444 AH (first day after Ramadan)
|
||||
const isoShawwal = Temporal.PlainDate.from('2023-04-21');
|
||||
// 2021-08-09 = 1 Muharram 1443 AH (a 355-day / leap year)
|
||||
const isoLeapYear = Temporal.PlainDate.from('2021-08-09');
|
||||
|
||||
// ── 1. Class exports ──────────────────────────────────────────────────────────
|
||||
|
||||
test('UaqCalendar class export', () => {
|
||||
assert(UaqCalendar, 'UaqCalendar should be exported');
|
||||
const cal = new UaqCalendar();
|
||||
assert(cal instanceof UaqCalendar, 'UaqCalendar should be instantiable');
|
||||
});
|
||||
|
||||
test('FcnaCalendar class export', () => {
|
||||
assert(FcnaCalendar, 'FcnaCalendar should be exported');
|
||||
const cal = new FcnaCalendar();
|
||||
assert(cal instanceof FcnaCalendar, 'FcnaCalendar should be instantiable');
|
||||
});
|
||||
|
||||
// ── 2. Calendar IDs ───────────────────────────────────────────────────────────
|
||||
|
||||
test('uaqCalendar.id', () => {
|
||||
assert.equal(uaqCalendar.id, 'hijri-uaq');
|
||||
});
|
||||
|
||||
test('fcnaCalendar.id', () => {
|
||||
assert.equal(fcnaCalendar.id, 'hijri-fcna');
|
||||
});
|
||||
|
||||
// ── 3. Field accessors on 1 Ramadan 1444 (2023-03-23) ────────────────────────
|
||||
|
||||
test('uaqCalendar.year(2023-03-23) = 1444', () => {
|
||||
assert.equal(uaqCalendar.year(isoRamadan), 1444);
|
||||
});
|
||||
|
||||
test('uaqCalendar.month(2023-03-23) = 9 (Ramadan)', () => {
|
||||
assert.equal(uaqCalendar.month(isoRamadan), 9);
|
||||
});
|
||||
|
||||
test('uaqCalendar.day(2023-03-23) = 1', () => {
|
||||
assert.equal(uaqCalendar.day(isoRamadan), 1);
|
||||
});
|
||||
|
||||
test('uaqCalendar.monthCode(2023-03-23) = "M09"', () => {
|
||||
assert.equal(uaqCalendar.monthCode(isoRamadan), 'M09');
|
||||
});
|
||||
|
||||
test('uaqCalendar.daysInMonth(2023-03-23) = 29 (Ramadan 1444 is 29 days)', () => {
|
||||
assert.equal(uaqCalendar.daysInMonth(isoRamadan), 29);
|
||||
});
|
||||
|
||||
test('uaqCalendar.monthsInYear(2023-03-23) = 12', () => {
|
||||
assert.equal(uaqCalendar.monthsInYear(isoRamadan), 12);
|
||||
});
|
||||
|
||||
test('uaqCalendar.daysInWeek(2023-03-23) = 7', () => {
|
||||
assert.equal(uaqCalendar.daysInWeek(isoRamadan), 7);
|
||||
});
|
||||
|
||||
// 2023-03-23 is a Thursday. ISO weekday: 1=Mon, ..., 4=Thu, ..., 7=Sun.
|
||||
test('uaqCalendar.dayOfWeek(2023-03-23) = 4 (Thursday)', () => {
|
||||
assert.equal(uaqCalendar.dayOfWeek(isoRamadan), 4);
|
||||
});
|
||||
|
||||
// dayOfYear: sum of months 1-8 in 1444 + 1 (first day of month 9).
|
||||
// Months 1-8 of 1444 total 236 days, so day 237 of the year.
|
||||
test('uaqCalendar.dayOfYear(2023-03-23) = 237', () => {
|
||||
assert.equal(uaqCalendar.dayOfYear(isoRamadan), 237);
|
||||
});
|
||||
|
||||
// ── 4. dateFromFields ─────────────────────────────────────────────────────────
|
||||
|
||||
test('uaqCalendar.dateFromFields({year:1444, month:9, day:1}) = 2023-03-23', () => {
|
||||
const result = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||
assert.equal(result.toString(), '2023-03-23');
|
||||
});
|
||||
|
||||
// ── 5. dateAdd ────────────────────────────────────────────────────────────────
|
||||
|
||||
test('uaqCalendar.dateAdd: adding 1 month from 1 Ramadan 1444 lands on 1 Shawwal 1444', () => {
|
||||
const oneMonth = new Temporal.Duration(0, 1, 0, 0);
|
||||
const result = uaqCalendar.dateAdd(isoRamadan, oneMonth);
|
||||
// 1 Shawwal 1444 = 2023-04-21
|
||||
assert.equal(result.toString(), isoShawwal.toString());
|
||||
// Verify the result is in Shawwal (month 10)
|
||||
assert.equal(uaqCalendar.month(result), 10);
|
||||
});
|
||||
|
||||
// ── 6. inLeapYear ─────────────────────────────────────────────────────────────
|
||||
|
||||
test('uaqCalendar.inLeapYear: 1443 AH (355 days) is a leap year, 1444 AH (354) is not', () => {
|
||||
// 2021-08-09 = 1 Muharram 1443 (355-day year)
|
||||
assert.equal(uaqCalendar.inLeapYear(isoLeapYear), true);
|
||||
// 2023-03-23 = in 1444 (354-day year)
|
||||
assert.equal(uaqCalendar.inLeapYear(isoRamadan), false);
|
||||
});
|
||||
|
||||
// ── 7. FCNA calendar ──────────────────────────────────────────────────────────
|
||||
|
||||
// Both UAQ and FCNA agree on 1 Ramadan 1444 = 2023-03-23
|
||||
test('fcnaCalendar.year(2023-03-23) returns a valid Hijri year', () => {
|
||||
const year = fcnaCalendar.year(isoRamadan);
|
||||
assert(typeof year === 'number' && year > 1400, `Expected a Hijri year > 1400, got ${year}`);
|
||||
});
|
||||
|
||||
// ── 8. Out-of-range error ─────────────────────────────────────────────────────
|
||||
|
||||
test('uaqCalendar.year throws RangeError for out-of-range date (1800-01-01)', () => {
|
||||
// UAQ table covers 1318-1500 AH (Gregorian 1900-2076). 1800 is out of range.
|
||||
const outOfRange = Temporal.PlainDate.from('1800-01-01');
|
||||
assert.throws(
|
||||
() => uaqCalendar.year(outOfRange),
|
||||
(err) => err instanceof RangeError
|
||||
);
|
||||
});
|
||||
|
||||
// ── Summary ───────────────────────────────────────────────────────────────────
|
||||
|
||||
console.log(`\n${passed}/${total} tests passed`);
|
||||
if (failed > 0) {
|
||||
console.error(`${failed} test(s) failed`);
|
||||
process.exit(1);
|
||||
}
|
||||
16
tsconfig.json
Normal file
16
tsconfig.json
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"strict": true,
|
||||
"esModuleInterop": true,
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
"outDir": "dist",
|
||||
"rootDir": "src",
|
||||
"types": ["node"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
17
tsup.config.ts
Normal file
17
tsup.config.ts
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import { defineConfig } from 'tsup';
|
||||
|
||||
export default defineConfig({
|
||||
entry: ['src/index.ts'],
|
||||
format: ['cjs', 'esm'],
|
||||
dts: true,
|
||||
clean: true,
|
||||
outDir: 'dist',
|
||||
splitting: false,
|
||||
sourcemap: true,
|
||||
target: 'es2020',
|
||||
platform: 'node',
|
||||
external: ['hijri-core', '@js-temporal/polyfill'],
|
||||
outExtension({ format }) {
|
||||
return { js: format === 'esm' ? '.mjs' : '.cjs' };
|
||||
},
|
||||
});
|
||||
Loading…
Reference in a new issue