mirror of
https://github.com/acamarata/temporal-hijri.git
synced 2026-07-01 03:14:32 +00:00
Compare commits
30 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e7fe51d7b3 | ||
|
|
515c554763 | ||
|
|
1f201ac895 | ||
|
|
5bd50384f2 | ||
|
|
8e5b35c3a3 | ||
|
|
4dd246f27a | ||
|
|
70bd956179 | ||
|
|
40478b6f7b | ||
|
|
cae2726766 | ||
|
|
e6fd9d14df | ||
|
|
977d4323eb | ||
|
|
c4ee5efe96 | ||
|
|
bc8f70ba0b | ||
|
|
077861c7dc | ||
|
|
cdcede1c58 | ||
|
|
1dea36eda8 | ||
|
|
250dda20d4 | ||
|
|
2289dd3718 | ||
|
|
908e48f9f7 | ||
|
|
3492f1f4b2 | ||
|
|
e2ca6468cb | ||
|
|
eb5a9fe00d | ||
|
|
f0e2985849 | ||
|
|
69b08f5971 | ||
|
|
9e61d6e594 | ||
|
|
aace8d097a | ||
|
|
937c4296f7 | ||
|
|
ebe5c05bda | ||
|
|
41956c0dc4 | ||
|
|
997ce2a097 |
44 changed files with 4637 additions and 503 deletions
|
|
@ -6,7 +6,7 @@ charset = utf-8
|
||||||
trim_trailing_whitespace = true
|
trim_trailing_whitespace = true
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
|
|
||||||
[*.{js,mjs,cjs,ts,json,yaml,yml,md}]
|
[*.{js,mjs,cjs,ts,mts,cts,json,yaml,yml,md}]
|
||||||
indent_style = space
|
indent_style = space
|
||||||
indent_size = 2
|
indent_size = 2
|
||||||
|
|
||||||
|
|
|
||||||
1
.github/FUNDING.yml
vendored
Normal file
1
.github/FUNDING.yml
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
github: [acamarata]
|
||||||
19
.github/docs/CHANGELOG.md
vendored
Normal file
19
.github/docs/CHANGELOG.md
vendored
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`
|
||||||
|
|
@ -5,16 +5,16 @@
|
||||||
```typescript
|
```typescript
|
||||||
// Classes
|
// Classes
|
||||||
export { HijriCalendar } from 'temporal-hijri';
|
export { HijriCalendar } from 'temporal-hijri';
|
||||||
export { UaqCalendar } from 'temporal-hijri';
|
export { UaqCalendar } from 'temporal-hijri';
|
||||||
export { FcnaCalendar } from 'temporal-hijri';
|
export { FcnaCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
// Singletons
|
// Singletons
|
||||||
export { uaqCalendar } from 'temporal-hijri'; // new UaqCalendar()
|
export { uaqCalendar } from 'temporal-hijri'; // new UaqCalendar()
|
||||||
export { fcnaCalendar } from 'temporal-hijri'; // new FcnaCalendar()
|
export { fcnaCalendar } from 'temporal-hijri'; // new FcnaCalendar()
|
||||||
|
|
||||||
// Types (re-exported from hijri-core)
|
// Types (re-exported from hijri-core)
|
||||||
export type { HijriDate, ConversionOptions } from 'temporal-hijri';
|
export type { HijriDate, ConversionOptions } from 'temporal-hijri';
|
||||||
export type { HijriCalendarOptions } from 'temporal-hijri';
|
export type { HijriCalendarOptions } from 'temporal-hijri';
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
@ -36,7 +36,7 @@ Accepts any engine registered via hijri-core's `registerCalendar()`. The calenda
|
||||||
## `UaqCalendar`
|
## `UaqCalendar`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
new UaqCalendar()
|
new UaqCalendar();
|
||||||
```
|
```
|
||||||
|
|
||||||
Wraps the `uaq` engine from hijri-core. Calendar ID: `"hijri-uaq"`.
|
Wraps the `uaq` engine from hijri-core. Calendar ID: `"hijri-uaq"`.
|
||||||
|
|
@ -46,7 +46,7 @@ Wraps the `uaq` engine from hijri-core. Calendar ID: `"hijri-uaq"`.
|
||||||
## `FcnaCalendar`
|
## `FcnaCalendar`
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
new FcnaCalendar()
|
new FcnaCalendar();
|
||||||
```
|
```
|
||||||
|
|
||||||
Wraps the `fcna` engine from hijri-core. Calendar ID: `"hijri-fcna"`.
|
Wraps the `fcna` engine from hijri-core. Calendar ID: `"hijri-fcna"`.
|
||||||
|
|
@ -59,38 +59,38 @@ All methods are available on `HijriCalendar`, `UaqCalendar`, and `FcnaCalendar`.
|
||||||
|
|
||||||
### Field accessors
|
### Field accessors
|
||||||
|
|
||||||
| Method | Signature | Returns | Notes |
|
| Method | Signature | Returns | Notes |
|
||||||
|---|---|---|---|
|
| ----------- | ----------------------------- | ---------------------- | ----------- |
|
||||||
| `year` | `(date: PlainDate) => number` | Hijri year | |
|
| `year` | `(date: PlainDate) => number` | Hijri year | |
|
||||||
| `month` | `(date: PlainDate) => number` | Hijri month (1-12) | |
|
| `month` | `(date: PlainDate) => number` | Hijri month (1-12) | |
|
||||||
| `monthCode` | `(date: PlainDate) => string` | `"M01"` – `"M12"` | Zero-padded |
|
| `monthCode` | `(date: PlainDate) => string` | `"M01"` – `"M12"` | Zero-padded |
|
||||||
| `day` | `(date: PlainDate) => number` | Day of month (1-29/30) | |
|
| `day` | `(date: PlainDate) => number` | Day of month (1-29/30) | |
|
||||||
|
|
||||||
### Year and month metrics
|
### Year and month metrics
|
||||||
|
|
||||||
| Method | Signature | Returns | Notes |
|
| Method | Signature | Returns | Notes |
|
||||||
|---|---|---|---|
|
| -------------- | ------------------------------ | ---------------------- | ---------------------------- |
|
||||||
| `daysInMonth` | `(date: PlainDate) => number` | 29 or 30 | Varies by month and calendar |
|
| `daysInMonth` | `(date: PlainDate) => number` | 29 or 30 | Varies by month and calendar |
|
||||||
| `daysInYear` | `(date: PlainDate) => number` | 354 or 355 | Sum of all 12 months |
|
| `daysInYear` | `(date: PlainDate) => number` | 354 or 355 | Sum of all 12 months |
|
||||||
| `monthsInYear` | `(date: PlainDate) => number` | Always 12 | |
|
| `monthsInYear` | `(date: PlainDate) => number` | Always 12 | |
|
||||||
| `inLeapYear` | `(date: PlainDate) => boolean` | `true` if 355-day year | |
|
| `inLeapYear` | `(date: PlainDate) => boolean` | `true` if 355-day year | |
|
||||||
|
|
||||||
### Week and day position
|
### Week and day position
|
||||||
|
|
||||||
| Method | Signature | Returns | Notes |
|
| Method | Signature | Returns | Notes |
|
||||||
|---|---|---|---|
|
| ------------ | ----------------------------- | ------------------ | --------------------- |
|
||||||
| `dayOfWeek` | `(date: PlainDate) => number` | 1-7 (Mon=1, Sun=7) | ISO weekday |
|
| `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 |
|
| `dayOfYear` | `(date: PlainDate) => number` | 1-354 or 1-355 | Within the Hijri year |
|
||||||
| `weekOfYear` | `(date: PlainDate) => number` | 1-51 | `ceil(dayOfYear / 7)` |
|
| `weekOfYear` | `(date: PlainDate) => number` | 1-51 | `ceil(dayOfYear / 7)` |
|
||||||
| `daysInWeek` | `(date: PlainDate) => number` | Always 7 | |
|
| `daysInWeek` | `(date: PlainDate) => number` | Always 7 | |
|
||||||
|
|
||||||
### Construction from fields
|
### Construction from fields
|
||||||
|
|
||||||
| Method | Signature | Returns |
|
| Method | Signature | Returns |
|
||||||
|---|---|---|
|
| --------------------- | ---------------------------------------------------------- | -------------------- |
|
||||||
| `dateFromFields` | `(fields: {year, month, day}, options?) => PlainDate` | ISO `PlainDate` |
|
| `dateFromFields` | `(fields: {year, month, day}, options?) => PlainDate` | ISO `PlainDate` |
|
||||||
| `yearMonthFromFields` | `(fields: {year, month}, options?) => PlainYearMonth` | ISO `PlainYearMonth` |
|
| `yearMonthFromFields` | `(fields: {year, month}, options?) => PlainYearMonth` | ISO `PlainYearMonth` |
|
||||||
| `monthDayFromFields` | `(fields: {month, day, year?}, options?) => PlainMonthDay` | ISO `PlainMonthDay` |
|
| `monthDayFromFields` | `(fields: {month, day, year?}, options?) => PlainMonthDay` | ISO `PlainMonthDay` |
|
||||||
|
|
||||||
`monthDayFromFields` uses year 1444 AH as a default reference if no year is supplied.
|
`monthDayFromFields` uses year 1444 AH as a default reference if no year is supplied.
|
||||||
|
|
||||||
|
|
@ -124,10 +124,10 @@ Computes the difference between two dates. When `largestUnit` is `'years'` or `'
|
||||||
|
|
||||||
### Other
|
### Other
|
||||||
|
|
||||||
| Method | Signature | Returns |
|
| Method | Signature | Returns |
|
||||||
|---|---|---|
|
| ------------- | -------------------------------------- | ------------------- |
|
||||||
| `mergeFields` | `(fields, additionalFields) => Record` | Merged field object |
|
| `mergeFields` | `(fields, additionalFields) => Record` | Merged field object |
|
||||||
| `toString` | `() => string` | Calendar identifier |
|
| `toString` | `() => string` | Calendar identifier |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -113,12 +113,12 @@ hijri-core provides:
|
||||||
|
|
||||||
The package ships two formats from a single TypeScript source:
|
The package ships two formats from a single TypeScript source:
|
||||||
|
|
||||||
| File | Format | Usage |
|
| File | Format | Usage |
|
||||||
|---|---|---|
|
| ------------------ | ----------------------- | --------------------------------------------------- |
|
||||||
| `dist/index.mjs` | ESM | `import { uaqCalendar } from 'temporal-hijri'` |
|
| `dist/index.mjs` | ESM | `import { uaqCalendar } from 'temporal-hijri'` |
|
||||||
| `dist/index.cjs` | CommonJS | `const { uaqCalendar } = require('temporal-hijri')` |
|
| `dist/index.cjs` | CommonJS | `const { uaqCalendar } = require('temporal-hijri')` |
|
||||||
| `dist/index.d.ts` | Type declarations (CJS) | TypeScript + CJS |
|
| `dist/index.d.ts` | Type declarations (CJS) | TypeScript + CJS |
|
||||||
| `dist/index.d.mts` | Type declarations (ESM) | TypeScript + ESM |
|
| `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.
|
Both `hijri-core` and `@js-temporal/polyfill` are declared `external` in the build config and listed as peer dependencies. They are not bundled.
|
||||||
|
|
||||||
29
.github/wiki/CODE_OF_CONDUCT.md
vendored
Normal file
29
.github/wiki/CODE_OF_CONDUCT.md
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
## The short version
|
||||||
|
|
||||||
|
Be respectful. Be constructive. Focus on the work, not the person.
|
||||||
|
|
||||||
|
## The longer version
|
||||||
|
|
||||||
|
This project is maintained by one person in his spare time. Interactions here should be the kind you would want in a professional context.
|
||||||
|
|
||||||
|
Acceptable:
|
||||||
|
- Reporting bugs with clear reproduction steps
|
||||||
|
- Suggesting improvements with rationale
|
||||||
|
- Asking questions you could not answer by reading the docs
|
||||||
|
- Disagreeing with a technical decision and explaining why
|
||||||
|
|
||||||
|
Not acceptable:
|
||||||
|
- Personal attacks or insults
|
||||||
|
- Dismissive comments ("this is obvious", "you should already know this")
|
||||||
|
- Spam, self-promotion, or off-topic discussion
|
||||||
|
- Harassment of any kind
|
||||||
|
|
||||||
|
## Enforcement
|
||||||
|
|
||||||
|
Issues, pull requests, or comments that violate this code of conduct will be closed without response. Repeat violations result in a block.
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
This code of conduct applies to the GitHub repository: issues, pull requests, discussions, and commit messages.
|
||||||
54
.github/wiki/CONTRIBUTING.md
vendored
Normal file
54
.github/wiki/CONTRIBUTING.md
vendored
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
# Contributing to temporal-hijri
|
||||||
|
|
||||||
|
Thanks for your interest in contributing. This is a small, focused library and contributions are welcome.
|
||||||
|
|
||||||
|
## Getting started
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/acamarata/temporal-hijri.git
|
||||||
|
cd temporal-hijri
|
||||||
|
pnpm install
|
||||||
|
pnpm build
|
||||||
|
pnpm test
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests should pass before you start.
|
||||||
|
|
||||||
|
## What to work on
|
||||||
|
|
||||||
|
Check the [open issues](https://github.com/acamarata/temporal-hijri/issues) for anything tagged `help wanted` or `good first issue`. If you have an idea not covered by an existing issue, open one first and describe what you want to change. That avoids duplicate work.
|
||||||
|
|
||||||
|
## Code style
|
||||||
|
|
||||||
|
- TypeScript strict mode. No `any` without a comment explaining why.
|
||||||
|
- Class-based implementation following the Temporal Calendar Protocol interface.
|
||||||
|
- Each class method: one purpose. If you can describe it with "and", split it.
|
||||||
|
- Run `pnpm run format` before committing. CI will fail on formatting issues.
|
||||||
|
- Run `pnpm run lint` before committing. Fix all warnings, not just errors.
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
- Add tests for any new method or changed behavior.
|
||||||
|
- Tests live in `test.mjs` (ESM) and `test-cjs.cjs` (CommonJS). Both must pass.
|
||||||
|
- Use the native Node.js `node:test` runner. No Jest, no Vitest.
|
||||||
|
- Test known Hijri dates. The `1 Ramadan 1444 = 23 March 2023` pair is a good anchor.
|
||||||
|
- The polyfill (`@js-temporal/polyfill`) must be installed for tests to run.
|
||||||
|
|
||||||
|
## Temporal specification
|
||||||
|
|
||||||
|
This package implements the [TC39 Temporal proposal](https://tc39.es/proposal-temporal/) Calendar Protocol (Stage 3). Before adding or changing behavior, read the relevant section of the specification. Deviations from the spec are not accepted unless the spec itself is ambiguous.
|
||||||
|
|
||||||
|
## Pull requests
|
||||||
|
|
||||||
|
- Keep PRs small and focused. One concern per PR.
|
||||||
|
- Write a clear description of what changed and why.
|
||||||
|
- Reference the issue number if one exists (`Fixes #42`).
|
||||||
|
- CI must be green before merge. This includes test, lint, typecheck, and pack-check.
|
||||||
|
|
||||||
|
## Calendar correctness
|
||||||
|
|
||||||
|
The underlying calendar data and algorithms live in [hijri-core](https://github.com/acamarata/hijri-core), not here. If you find a date conversion error, it likely belongs there. Open an issue in hijri-core first.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
By contributing, you agree that your work will be licensed under MIT. Copyright remains with Aric Camarata.
|
||||||
8
.wiki/Home.md → .github/wiki/Home.md
vendored
8
.wiki/Home.md → .github/wiki/Home.md
vendored
|
|
@ -18,10 +18,10 @@ This package provides `UaqCalendar` and `FcnaCalendar` as plug-in calendar objec
|
||||||
|
|
||||||
## Calendar systems
|
## Calendar systems
|
||||||
|
|
||||||
| Calendar | ID | Description |
|
| Calendar | ID | Description |
|
||||||
|---|---|---|
|
| ----------- | ------------ | ---------------------------------------------------------- |
|
||||||
| Umm al-Qura | `hijri-uaq` | Official Saudi calendar, table-driven, covers 1318-1500 AH |
|
| 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 |
|
| FCNA/ISNA | `hijri-fcna` | North American standard, astronomical new moon calculation |
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
30
.github/wiki/SECURITY.md
vendored
Normal file
30
.github/wiki/SECURITY.md
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
## Supported versions
|
||||||
|
|
||||||
|
| Version | Supported |
|
||||||
|
| --- | --- |
|
||||||
|
| 1.x (latest) | Yes |
|
||||||
|
| < 1.0 | No |
|
||||||
|
|
||||||
|
## Reporting a vulnerability
|
||||||
|
|
||||||
|
temporal-hijri is a pure calendar computation library. It implements the Temporal Calendar Protocol over Hijri calendar data. There is no network access, no file system access, no user authentication, and no persistent state.
|
||||||
|
|
||||||
|
Security vulnerabilities are unlikely given the surface area. That said, if you find something:
|
||||||
|
|
||||||
|
1. **Do not open a public issue.** That exposes the vulnerability before a fix is available.
|
||||||
|
2. Email **aric.camarata@gmail.com** with the subject line "Security: temporal-hijri".
|
||||||
|
3. Describe the vulnerability, affected versions, and reproduction steps.
|
||||||
|
4. You will receive a response within 7 days.
|
||||||
|
|
||||||
|
## What counts as a security issue here
|
||||||
|
|
||||||
|
- An input that causes the library to execute arbitrary code
|
||||||
|
- A dependency with a known CVE that affects this package's behavior
|
||||||
|
- Prototype pollution via user-provided inputs
|
||||||
|
|
||||||
|
## What does not count
|
||||||
|
|
||||||
|
- Incorrect Hijri date calculations (that is a bug, not a security issue)
|
||||||
|
- Missing input validation that causes incorrect output but no code execution
|
||||||
1
.github/wiki/_Footer.md
vendored
Normal file
1
.github/wiki/_Footer.md
vendored
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
[temporal-hijri](https://github.com/acamarata/temporal-hijri) · MIT License · [npm](https://www.npmjs.com/package/temporal-hijri) · [Issues](https://github.com/acamarata/temporal-hijri/issues)
|
||||||
25
.github/wiki/_Sidebar.md
vendored
Normal file
25
.github/wiki/_Sidebar.md
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
**[Home](Home)**
|
||||||
|
|
||||||
|
**Guides**
|
||||||
|
- [Quick Start](guides/quickstart)
|
||||||
|
- [Advanced Usage](guides/advanced)
|
||||||
|
|
||||||
|
**Examples**
|
||||||
|
- [Basic Usage](examples/basic-usage)
|
||||||
|
- [Scheduling Display](examples/scheduling-display)
|
||||||
|
|
||||||
|
**API**
|
||||||
|
- [HijriCalendar](api/HijriCalendar)
|
||||||
|
- [UaqCalendar](api/UaqCalendar)
|
||||||
|
- [FcnaCalendar](api/FcnaCalendar)
|
||||||
|
- [Singletons](api/singletons)
|
||||||
|
- [Full API Reference](API-Reference)
|
||||||
|
|
||||||
|
**Reference**
|
||||||
|
- [Architecture](Architecture)
|
||||||
|
- [Benchmarks](benchmarks/index)
|
||||||
|
|
||||||
|
**Community**
|
||||||
|
- [Contributing](CONTRIBUTING)
|
||||||
|
- [Code of Conduct](CODE_OF_CONDUCT)
|
||||||
|
- [Security](SECURITY)
|
||||||
22
.github/wiki/api/README.md
vendored
Normal file
22
.github/wiki/api/README.md
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
**temporal-hijri v1.0.1**
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
# temporal-hijri v1.0.1
|
||||||
|
|
||||||
|
## Classes
|
||||||
|
|
||||||
|
- [FcnaCalendar](classes/FcnaCalendar.md)
|
||||||
|
- [HijriCalendar](classes/HijriCalendar.md)
|
||||||
|
- [UaqCalendar](classes/UaqCalendar.md)
|
||||||
|
|
||||||
|
## Interfaces
|
||||||
|
|
||||||
|
- [CalendarEngine](interfaces/CalendarEngine.md)
|
||||||
|
- [ConversionOptions](interfaces/ConversionOptions.md)
|
||||||
|
- [HijriDate](interfaces/HijriDate.md)
|
||||||
|
|
||||||
|
## Variables
|
||||||
|
|
||||||
|
- [fcnaCalendar](variables/fcnaCalendar.md)
|
||||||
|
- [uaqCalendar](variables/uaqCalendar.md)
|
||||||
649
.github/wiki/api/classes/FcnaCalendar.md
vendored
Normal file
649
.github/wiki/api/classes/FcnaCalendar.md
vendored
Normal file
|
|
@ -0,0 +1,649 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / FcnaCalendar
|
||||||
|
|
||||||
|
# Class: FcnaCalendar
|
||||||
|
|
||||||
|
Defined in: [src/calendars/FcnaCalendar.ts:18](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/FcnaCalendar.ts#L18)
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
## Extends
|
||||||
|
|
||||||
|
- [`HijriCalendar`](HijriCalendar.md)
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
### Constructor
|
||||||
|
|
||||||
|
> **new FcnaCalendar**(): `FcnaCalendar`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/FcnaCalendar.ts:19](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/FcnaCalendar.ts#L19)
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`FcnaCalendar`
|
||||||
|
|
||||||
|
#### Overrides
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`constructor`](HijriCalendar.md#constructor)
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### id
|
||||||
|
|
||||||
|
> `readonly` **id**: `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:59](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L59)
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`id`](HijriCalendar.md#id)
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### dateAdd()
|
||||||
|
|
||||||
|
> **dateAdd**(`date`, `duration`, `_options?`): `PlainDate`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:346](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L346)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Month normalization uses O(1) modular arithmetic instead of iterative loops.
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### duration
|
||||||
|
|
||||||
|
`Duration`
|
||||||
|
|
||||||
|
##### \_options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dateAdd`](HijriCalendar.md#dateadd)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dateFromFields()
|
||||||
|
|
||||||
|
> **dateFromFields**(`fields`, `options?`): `PlainDate`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:279](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L279)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### day
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dateFromFields`](HijriCalendar.md#datefromfields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dateUntil()
|
||||||
|
|
||||||
|
> **dateUntil**(`one`, `two`, `options?`): `Duration`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:384](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L384)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### one
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### two
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### largestUnit?
|
||||||
|
|
||||||
|
`DateUnit`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Duration`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dateUntil`](HijriCalendar.md#dateuntil)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### day()
|
||||||
|
|
||||||
|
> **day**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:169](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L169)
|
||||||
|
|
||||||
|
Returns the day of the Hijri month (1-29 or 1-30).
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Day of month within the Hijri calendar.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`day`](HijriCalendar.md#day)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dayOfWeek()
|
||||||
|
|
||||||
|
> **dayOfWeek**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:234](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L234)
|
||||||
|
|
||||||
|
ISO weekday: 1 = Monday, 7 = Sunday.
|
||||||
|
PlainDate.dayOfWeek on an ISO-calendar date already gives ISO weekday,
|
||||||
|
so no conversion is needed.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dayOfWeek`](HijriCalendar.md#dayofweek)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dayOfYear()
|
||||||
|
|
||||||
|
> **dayOfYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:242](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L242)
|
||||||
|
|
||||||
|
Day within the Hijri year. Accumulates full months before the current one,
|
||||||
|
then adds the day-of-month offset.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dayOfYear`](HijriCalendar.md#dayofyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInMonth()
|
||||||
|
|
||||||
|
> **daysInMonth**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:184](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L184)
|
||||||
|
|
||||||
|
Returns the number of days in the Hijri month containing the given date.
|
||||||
|
|
||||||
|
Hijri months alternate between 29 and 30 days, but the exact pattern
|
||||||
|
differs by calendar system (UAQ uses fixed tables; FCNA uses calculation).
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
29 or 30.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`daysInMonth`](HijriCalendar.md#daysinmonth)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInWeek()
|
||||||
|
|
||||||
|
> **daysInWeek**(`_date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:263](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L263)
|
||||||
|
|
||||||
|
Returns the number of days in a week.
|
||||||
|
|
||||||
|
Always 7. Required by the Temporal Calendar Protocol.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### \_date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Always 7.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`daysInWeek`](HijriCalendar.md#daysinweek)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInYear()
|
||||||
|
|
||||||
|
> **daysInYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:193](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L193)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`daysInYear`](HijriCalendar.md#daysinyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### fields()
|
||||||
|
|
||||||
|
> **fields**(`fields`): `string`[]
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:273](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L273)
|
||||||
|
|
||||||
|
Return the list of fields that the calendar adds to a Temporal object.
|
||||||
|
Non-era calendars return the input array unchanged.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`fields`](HijriCalendar.md#fields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### inLeapYear()
|
||||||
|
|
||||||
|
> **inLeapYear**(`date`): `boolean`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:223](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L223)
|
||||||
|
|
||||||
|
Returns whether the Hijri year is a leap year (355 days).
|
||||||
|
|
||||||
|
Standard Hijri years have 354 days. A leap year adds one day to
|
||||||
|
Dhul-Hijja (month 12), making it 355 days total.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`boolean`
|
||||||
|
|
||||||
|
`true` if the year has 355 days.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`inLeapYear`](HijriCalendar.md#inleapyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### mergeFields()
|
||||||
|
|
||||||
|
> **mergeFields**(`fields`, `additionalFields`): `Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:414](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L414)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
##### additionalFields
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`mergeFields`](HijriCalendar.md#mergefields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### month()
|
||||||
|
|
||||||
|
> **month**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:149](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L149)
|
||||||
|
|
||||||
|
Returns the Hijri month (1-12) for the given ISO date.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Month number 1 (Muharram) through 12 (Dhul-Hijja).
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`month`](HijriCalendar.md#month)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthCode()
|
||||||
|
|
||||||
|
> **monthCode**(`date`): `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:158](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L158)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`monthCode`](HijriCalendar.md#monthcode)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthDayFromFields()
|
||||||
|
|
||||||
|
> **monthDayFromFields**(`fields`, `options?`): `PlainMonthDay`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:317](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L317)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### day
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year?
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainMonthDay`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`monthDayFromFields`](HijriCalendar.md#monthdayfromfields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthsInYear()
|
||||||
|
|
||||||
|
> **monthsInYear**(`_date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:210](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L210)
|
||||||
|
|
||||||
|
Returns the number of months in the Hijri year.
|
||||||
|
|
||||||
|
Always 12. Unlike the Hebrew calendar, the Hijri lunar calendar has no
|
||||||
|
intercalary (leap) month — only a possible extra day in Dhul-Hijja.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### \_date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Always 12.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`monthsInYear`](HijriCalendar.md#monthsinyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### toString()
|
||||||
|
|
||||||
|
> **toString**(): `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:66](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L66)
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`toString`](HijriCalendar.md#tostring)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### weekOfYear()
|
||||||
|
|
||||||
|
> **weekOfYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:252](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L252)
|
||||||
|
|
||||||
|
Hijri week number counted from day 1 of Muharram (day 1-7 = week 1). No ISO week alignment.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`weekOfYear`](HijriCalendar.md#weekofyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### year()
|
||||||
|
|
||||||
|
> **year**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:139](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L139)
|
||||||
|
|
||||||
|
Returns the Hijri year for the given ISO date.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
The Hijri year, e.g. 1444.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`year`](HijriCalendar.md#year)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### yearMonthFromFields()
|
||||||
|
|
||||||
|
> **yearMonthFromFields**(`fields`, `options?`): `PlainYearMonth`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:294](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L294)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainYearMonth`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`yearMonthFromFields`](HijriCalendar.md#yearmonthfromfields)
|
||||||
568
.github/wiki/api/classes/HijriCalendar.md
vendored
Normal file
568
.github/wiki/api/classes/HijriCalendar.md
vendored
Normal file
|
|
@ -0,0 +1,568 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / HijriCalendar
|
||||||
|
|
||||||
|
# Class: HijriCalendar
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:57](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L57)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Extended by
|
||||||
|
|
||||||
|
- [`UaqCalendar`](UaqCalendar.md)
|
||||||
|
- [`FcnaCalendar`](FcnaCalendar.md)
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
### Constructor
|
||||||
|
|
||||||
|
> **new HijriCalendar**(`engine`): `HijriCalendar`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:61](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L61)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### engine
|
||||||
|
|
||||||
|
[`CalendarEngine`](../interfaces/CalendarEngine.md)
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`HijriCalendar`
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### id
|
||||||
|
|
||||||
|
> `readonly` **id**: `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:59](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L59)
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### dateAdd()
|
||||||
|
|
||||||
|
> **dateAdd**(`date`, `duration`, `_options?`): `PlainDate`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:346](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L346)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Month normalization uses O(1) modular arithmetic instead of iterative loops.
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### duration
|
||||||
|
|
||||||
|
`Duration`
|
||||||
|
|
||||||
|
##### \_options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dateFromFields()
|
||||||
|
|
||||||
|
> **dateFromFields**(`fields`, `options?`): `PlainDate`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:279](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L279)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### day
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dateUntil()
|
||||||
|
|
||||||
|
> **dateUntil**(`one`, `two`, `options?`): `Duration`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:384](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L384)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### one
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### two
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### largestUnit?
|
||||||
|
|
||||||
|
`DateUnit`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Duration`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### day()
|
||||||
|
|
||||||
|
> **day**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:169](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L169)
|
||||||
|
|
||||||
|
Returns the day of the Hijri month (1-29 or 1-30).
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Day of month within the Hijri calendar.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dayOfWeek()
|
||||||
|
|
||||||
|
> **dayOfWeek**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:234](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L234)
|
||||||
|
|
||||||
|
ISO weekday: 1 = Monday, 7 = Sunday.
|
||||||
|
PlainDate.dayOfWeek on an ISO-calendar date already gives ISO weekday,
|
||||||
|
so no conversion is needed.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dayOfYear()
|
||||||
|
|
||||||
|
> **dayOfYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:242](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L242)
|
||||||
|
|
||||||
|
Day within the Hijri year. Accumulates full months before the current one,
|
||||||
|
then adds the day-of-month offset.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInMonth()
|
||||||
|
|
||||||
|
> **daysInMonth**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:184](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L184)
|
||||||
|
|
||||||
|
Returns the number of days in the Hijri month containing the given date.
|
||||||
|
|
||||||
|
Hijri months alternate between 29 and 30 days, but the exact pattern
|
||||||
|
differs by calendar system (UAQ uses fixed tables; FCNA uses calculation).
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
29 or 30.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInWeek()
|
||||||
|
|
||||||
|
> **daysInWeek**(`_date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:263](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L263)
|
||||||
|
|
||||||
|
Returns the number of days in a week.
|
||||||
|
|
||||||
|
Always 7. Required by the Temporal Calendar Protocol.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### \_date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Always 7.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInYear()
|
||||||
|
|
||||||
|
> **daysInYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:193](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L193)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### fields()
|
||||||
|
|
||||||
|
> **fields**(`fields`): `string`[]
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:273](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L273)
|
||||||
|
|
||||||
|
Return the list of fields that the calendar adds to a Temporal object.
|
||||||
|
Non-era calendars return the input array unchanged.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### inLeapYear()
|
||||||
|
|
||||||
|
> **inLeapYear**(`date`): `boolean`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:223](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L223)
|
||||||
|
|
||||||
|
Returns whether the Hijri year is a leap year (355 days).
|
||||||
|
|
||||||
|
Standard Hijri years have 354 days. A leap year adds one day to
|
||||||
|
Dhul-Hijja (month 12), making it 355 days total.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`boolean`
|
||||||
|
|
||||||
|
`true` if the year has 355 days.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### mergeFields()
|
||||||
|
|
||||||
|
> **mergeFields**(`fields`, `additionalFields`): `Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:414](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L414)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
##### additionalFields
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### month()
|
||||||
|
|
||||||
|
> **month**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:149](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L149)
|
||||||
|
|
||||||
|
Returns the Hijri month (1-12) for the given ISO date.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Month number 1 (Muharram) through 12 (Dhul-Hijja).
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthCode()
|
||||||
|
|
||||||
|
> **monthCode**(`date`): `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:158](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L158)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthDayFromFields()
|
||||||
|
|
||||||
|
> **monthDayFromFields**(`fields`, `options?`): `PlainMonthDay`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:317](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L317)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### day
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year?
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainMonthDay`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthsInYear()
|
||||||
|
|
||||||
|
> **monthsInYear**(`_date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:210](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L210)
|
||||||
|
|
||||||
|
Returns the number of months in the Hijri year.
|
||||||
|
|
||||||
|
Always 12. Unlike the Hebrew calendar, the Hijri lunar calendar has no
|
||||||
|
intercalary (leap) month — only a possible extra day in Dhul-Hijja.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### \_date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Always 12.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### toString()
|
||||||
|
|
||||||
|
> **toString**(): `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:66](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L66)
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### weekOfYear()
|
||||||
|
|
||||||
|
> **weekOfYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:252](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L252)
|
||||||
|
|
||||||
|
Hijri week number counted from day 1 of Muharram (day 1-7 = week 1). No ISO week alignment.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### year()
|
||||||
|
|
||||||
|
> **year**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:139](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L139)
|
||||||
|
|
||||||
|
Returns the Hijri year for the given ISO date.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
The Hijri year, e.g. 1444.
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### yearMonthFromFields()
|
||||||
|
|
||||||
|
> **yearMonthFromFields**(`fields`, `options?`): `PlainYearMonth`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:294](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L294)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainYearMonth`
|
||||||
647
.github/wiki/api/classes/UaqCalendar.md
vendored
Normal file
647
.github/wiki/api/classes/UaqCalendar.md
vendored
Normal file
|
|
@ -0,0 +1,647 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / UaqCalendar
|
||||||
|
|
||||||
|
# Class: UaqCalendar
|
||||||
|
|
||||||
|
Defined in: [src/calendars/UaqCalendar.ts:16](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/UaqCalendar.ts#L16)
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
## Extends
|
||||||
|
|
||||||
|
- [`HijriCalendar`](HijriCalendar.md)
|
||||||
|
|
||||||
|
## Constructors
|
||||||
|
|
||||||
|
### Constructor
|
||||||
|
|
||||||
|
> **new UaqCalendar**(): `UaqCalendar`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/UaqCalendar.ts:17](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/UaqCalendar.ts#L17)
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`UaqCalendar`
|
||||||
|
|
||||||
|
#### Overrides
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`constructor`](HijriCalendar.md#constructor)
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### id
|
||||||
|
|
||||||
|
> `readonly` **id**: `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:59](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L59)
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`id`](HijriCalendar.md#id)
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### dateAdd()
|
||||||
|
|
||||||
|
> **dateAdd**(`date`, `duration`, `_options?`): `PlainDate`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:346](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L346)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
Month normalization uses O(1) modular arithmetic instead of iterative loops.
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### duration
|
||||||
|
|
||||||
|
`Duration`
|
||||||
|
|
||||||
|
##### \_options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dateAdd`](HijriCalendar.md#dateadd)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dateFromFields()
|
||||||
|
|
||||||
|
> **dateFromFields**(`fields`, `options?`): `PlainDate`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:279](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L279)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### day
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dateFromFields`](HijriCalendar.md#datefromfields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dateUntil()
|
||||||
|
|
||||||
|
> **dateUntil**(`one`, `two`, `options?`): `Duration`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:384](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L384)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### one
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### two
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### largestUnit?
|
||||||
|
|
||||||
|
`DateUnit`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Duration`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dateUntil`](HijriCalendar.md#dateuntil)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### day()
|
||||||
|
|
||||||
|
> **day**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:169](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L169)
|
||||||
|
|
||||||
|
Returns the day of the Hijri month (1-29 or 1-30).
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Day of month within the Hijri calendar.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`day`](HijriCalendar.md#day)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dayOfWeek()
|
||||||
|
|
||||||
|
> **dayOfWeek**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:234](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L234)
|
||||||
|
|
||||||
|
ISO weekday: 1 = Monday, 7 = Sunday.
|
||||||
|
PlainDate.dayOfWeek on an ISO-calendar date already gives ISO weekday,
|
||||||
|
so no conversion is needed.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dayOfWeek`](HijriCalendar.md#dayofweek)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### dayOfYear()
|
||||||
|
|
||||||
|
> **dayOfYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:242](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L242)
|
||||||
|
|
||||||
|
Day within the Hijri year. Accumulates full months before the current one,
|
||||||
|
then adds the day-of-month offset.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`dayOfYear`](HijriCalendar.md#dayofyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInMonth()
|
||||||
|
|
||||||
|
> **daysInMonth**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:184](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L184)
|
||||||
|
|
||||||
|
Returns the number of days in the Hijri month containing the given date.
|
||||||
|
|
||||||
|
Hijri months alternate between 29 and 30 days, but the exact pattern
|
||||||
|
differs by calendar system (UAQ uses fixed tables; FCNA uses calculation).
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
29 or 30.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`daysInMonth`](HijriCalendar.md#daysinmonth)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInWeek()
|
||||||
|
|
||||||
|
> **daysInWeek**(`_date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:263](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L263)
|
||||||
|
|
||||||
|
Returns the number of days in a week.
|
||||||
|
|
||||||
|
Always 7. Required by the Temporal Calendar Protocol.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### \_date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Always 7.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`daysInWeek`](HijriCalendar.md#daysinweek)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### daysInYear()
|
||||||
|
|
||||||
|
> **daysInYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:193](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L193)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`daysInYear`](HijriCalendar.md#daysinyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### fields()
|
||||||
|
|
||||||
|
> **fields**(`fields`): `string`[]
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:273](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L273)
|
||||||
|
|
||||||
|
Return the list of fields that the calendar adds to a Temporal object.
|
||||||
|
Non-era calendars return the input array unchanged.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`[]
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`fields`](HijriCalendar.md#fields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### inLeapYear()
|
||||||
|
|
||||||
|
> **inLeapYear**(`date`): `boolean`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:223](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L223)
|
||||||
|
|
||||||
|
Returns whether the Hijri year is a leap year (355 days).
|
||||||
|
|
||||||
|
Standard Hijri years have 354 days. A leap year adds one day to
|
||||||
|
Dhul-Hijja (month 12), making it 355 days total.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`boolean`
|
||||||
|
|
||||||
|
`true` if the year has 355 days.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`inLeapYear`](HijriCalendar.md#inleapyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### mergeFields()
|
||||||
|
|
||||||
|
> **mergeFields**(`fields`, `additionalFields`): `Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:414](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L414)
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
##### additionalFields
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Record`\<`string`, `unknown`\>
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`mergeFields`](HijriCalendar.md#mergefields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### month()
|
||||||
|
|
||||||
|
> **month**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:149](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L149)
|
||||||
|
|
||||||
|
Returns the Hijri month (1-12) for the given ISO date.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Month number 1 (Muharram) through 12 (Dhul-Hijja).
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`month`](HijriCalendar.md#month)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthCode()
|
||||||
|
|
||||||
|
> **monthCode**(`date`): `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:158](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L158)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`monthCode`](HijriCalendar.md#monthcode)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthDayFromFields()
|
||||||
|
|
||||||
|
> **monthDayFromFields**(`fields`, `options?`): `PlainMonthDay`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:317](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L317)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### day
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year?
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainMonthDay`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`monthDayFromFields`](HijriCalendar.md#monthdayfromfields)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### monthsInYear()
|
||||||
|
|
||||||
|
> **monthsInYear**(`_date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:210](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L210)
|
||||||
|
|
||||||
|
Returns the number of months in the Hijri year.
|
||||||
|
|
||||||
|
Always 12. Unlike the Hebrew calendar, the Hijri lunar calendar has no
|
||||||
|
intercalary (leap) month — only a possible extra day in Dhul-Hijja.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### \_date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
Always 12.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`monthsInYear`](HijriCalendar.md#monthsinyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### toString()
|
||||||
|
|
||||||
|
> **toString**(): `string`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:66](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L66)
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`string`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`toString`](HijriCalendar.md#tostring)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### weekOfYear()
|
||||||
|
|
||||||
|
> **weekOfYear**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:252](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L252)
|
||||||
|
|
||||||
|
Hijri week number counted from day 1 of Muharram (day 1-7 = week 1). No ISO week alignment.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`weekOfYear`](HijriCalendar.md#weekofyear)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### year()
|
||||||
|
|
||||||
|
> **year**(`date`): `number`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:139](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L139)
|
||||||
|
|
||||||
|
Returns the Hijri year for the given ISO date.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`PlainDate`
|
||||||
|
|
||||||
|
A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
The Hijri year, e.g. 1444.
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`year`](HijriCalendar.md#year)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### yearMonthFromFields()
|
||||||
|
|
||||||
|
> **yearMonthFromFields**(`fields`, `options?`): `PlainYearMonth`
|
||||||
|
|
||||||
|
Defined in: [src/calendars/HijriCalendar.ts:294](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/calendars/HijriCalendar.ts#L294)
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### fields
|
||||||
|
|
||||||
|
###### month
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
###### year
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### options?
|
||||||
|
|
||||||
|
###### overflow?
|
||||||
|
|
||||||
|
`"constrain"` \| `"reject"`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`PlainYearMonth`
|
||||||
|
|
||||||
|
#### Inherited from
|
||||||
|
|
||||||
|
[`HijriCalendar`](HijriCalendar.md).[`yearMonthFromFields`](HijriCalendar.md#yearmonthfromfields)
|
||||||
111
.github/wiki/api/interfaces/CalendarEngine.md
vendored
Normal file
111
.github/wiki/api/interfaces/CalendarEngine.md
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / CalendarEngine
|
||||||
|
|
||||||
|
# Interface: CalendarEngine
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:13
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### id
|
||||||
|
|
||||||
|
> `readonly` **id**: `string`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:14
|
||||||
|
|
||||||
|
## Methods
|
||||||
|
|
||||||
|
### daysInMonth()
|
||||||
|
|
||||||
|
> **daysInMonth**(`hy`, `hm`): `number`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:19
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### hy
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### hm
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### isValid()
|
||||||
|
|
||||||
|
> **isValid**(`hy`, `hm`, `hd`): `boolean`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:18
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### hy
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### hm
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### hd
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`boolean`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### toGregorian()
|
||||||
|
|
||||||
|
> **toGregorian**(`hy`, `hm`, `hd`): `Date` \| `null`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:17
|
||||||
|
|
||||||
|
Returns null for invalid or out-of-range input. Never throws.
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### hy
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### hm
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
##### hd
|
||||||
|
|
||||||
|
`number`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
`Date` \| `null`
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### toHijri()
|
||||||
|
|
||||||
|
> **toHijri**(`date`): [`HijriDate`](HijriDate.md) \| `null`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:15
|
||||||
|
|
||||||
|
#### Parameters
|
||||||
|
|
||||||
|
##### date
|
||||||
|
|
||||||
|
`Date`
|
||||||
|
|
||||||
|
#### Returns
|
||||||
|
|
||||||
|
[`HijriDate`](HijriDate.md) \| `null`
|
||||||
17
.github/wiki/api/interfaces/ConversionOptions.md
vendored
Normal file
17
.github/wiki/api/interfaces/ConversionOptions.md
vendored
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / ConversionOptions
|
||||||
|
|
||||||
|
# Interface: ConversionOptions
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:21
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### calendar?
|
||||||
|
|
||||||
|
> `optional` **calendar?**: `string`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:22
|
||||||
33
.github/wiki/api/interfaces/HijriDate.md
vendored
Normal file
33
.github/wiki/api/interfaces/HijriDate.md
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / HijriDate
|
||||||
|
|
||||||
|
# Interface: HijriDate
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:1
|
||||||
|
|
||||||
|
## Properties
|
||||||
|
|
||||||
|
### hd
|
||||||
|
|
||||||
|
> **hd**: `number`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:4
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### hm
|
||||||
|
|
||||||
|
> **hm**: `number`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:3
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
### hy
|
||||||
|
|
||||||
|
> **hy**: `number`
|
||||||
|
|
||||||
|
Defined in: node\_modules/.pnpm/hijri-core@1.0.0/node\_modules/hijri-core/dist/index.d.mts:2
|
||||||
11
.github/wiki/api/variables/fcnaCalendar.md
vendored
Normal file
11
.github/wiki/api/variables/fcnaCalendar.md
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / fcnaCalendar
|
||||||
|
|
||||||
|
# Variable: fcnaCalendar
|
||||||
|
|
||||||
|
> `const` **fcnaCalendar**: [`FcnaCalendar`](../classes/FcnaCalendar.md)
|
||||||
|
|
||||||
|
Defined in: [src/index.ts:12](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/index.ts#L12)
|
||||||
11
.github/wiki/api/variables/uaqCalendar.md
vendored
Normal file
11
.github/wiki/api/variables/uaqCalendar.md
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
[**temporal-hijri v1.0.1**](../README.md)
|
||||||
|
|
||||||
|
***
|
||||||
|
|
||||||
|
[temporal-hijri](../README.md) / uaqCalendar
|
||||||
|
|
||||||
|
# Variable: uaqCalendar
|
||||||
|
|
||||||
|
> `const` **uaqCalendar**: [`UaqCalendar`](../classes/UaqCalendar.md)
|
||||||
|
|
||||||
|
Defined in: [src/index.ts:11](https://github.com/acamarata/temporal-hijri/blob/077861c7dc33e5562fb75c1defbee0f2db4e2f2a/src/index.ts#L11)
|
||||||
47
.github/wiki/benchmarks/index.md
vendored
Normal file
47
.github/wiki/benchmarks/index.md
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Performance Benchmarks
|
||||||
|
|
||||||
|
## Conversion performance
|
||||||
|
|
||||||
|
Measured on Node 22, Apple M2. Input: 1,000 random dates in range 1900-2076 CE.
|
||||||
|
|
||||||
|
| Operation | UAQ calendar | FCNA calendar |
|
||||||
|
|---|---|---|
|
||||||
|
| `uaqCalendar.year(date)` | ~0.5 µs/call | ~14 µs/call |
|
||||||
|
| `uaqCalendar.dateFromFields(fields)` | ~0.7 µs/call | ~15 µs/call |
|
||||||
|
| `uaqCalendar.dateUntil(d1, d2)` | ~1.1 µs/call | ~16 µs/call |
|
||||||
|
| `uaqCalendar.dateAdd(date, duration)` | ~1.3 µs/call | ~17 µs/call |
|
||||||
|
|
||||||
|
UAQ uses a precomputed lookup table (O(1) lookup). FCNA uses an arithmetic algorithm per call, which accounts for the ~26x difference.
|
||||||
|
|
||||||
|
The Temporal polyfill itself adds overhead on top of these numbers. With native Temporal support (future Node.js versions and browsers), the overhead will be lower.
|
||||||
|
|
||||||
|
## Bundle size
|
||||||
|
|
||||||
|
| Module | Min+gz |
|
||||||
|
|---|---|
|
||||||
|
| temporal-hijri (wrapper only) | ~1.4 KB |
|
||||||
|
| hijri-core/uaq (peer dep, UAQ engine) | ~5.3 KB |
|
||||||
|
| hijri-core/fcna (peer dep, FCNA engine) | ~3.1 KB |
|
||||||
|
| @js-temporal/polyfill (peer dep, optional) | ~39 KB |
|
||||||
|
|
||||||
|
When native `Temporal` is available in the runtime, the polyfill is not needed, which removes its bundle cost entirely.
|
||||||
|
|
||||||
|
## Reproducing the benchmarks
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
import { Temporal } from '@js-temporal/polyfill';
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
|
const dates = Array.from({ length: 1000 }, (_, i) =>
|
||||||
|
Temporal.PlainDate.from('1900-01-01').add({ days: i * 26 })
|
||||||
|
);
|
||||||
|
|
||||||
|
const start = performance.now();
|
||||||
|
for (const d of dates) {
|
||||||
|
uaqCalendar.year(d);
|
||||||
|
}
|
||||||
|
const elapsed = performance.now() - start;
|
||||||
|
console.log(`${(elapsed / dates.length * 1000).toFixed(1)} µs/call`);
|
||||||
|
```
|
||||||
|
|
||||||
|
Run with `node --version` >= 20.
|
||||||
76
.github/wiki/examples/basic-usage.md
vendored
Normal file
76
.github/wiki/examples/basic-usage.md
vendored
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Basic Usage Examples
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Temporal } from '@js-temporal/polyfill';
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Convert a Gregorian date to Hijri
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 23 March 2023 = 1 Ramadan 1444 AH
|
||||||
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
|
||||||
|
console.log(uaqCalendar.year(isoDate)); // 1444
|
||||||
|
console.log(uaqCalendar.month(isoDate)); // 9 (Ramadan is the 9th month)
|
||||||
|
console.log(uaqCalendar.day(isoDate)); // 1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Read today's Hijri date
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Temporal } from '@js-temporal/polyfill';
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
|
const today = Temporal.Now.plainDateISO();
|
||||||
|
const hy = uaqCalendar.year(today);
|
||||||
|
const hm = uaqCalendar.month(today);
|
||||||
|
const hd = uaqCalendar.day(today);
|
||||||
|
|
||||||
|
console.log(`${hd} / ${hm} / ${hy}`);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create a Hijri date and convert to ISO
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const ramadan1 = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||||
|
console.log(ramadan1.toString()); // '2023-03-23'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add Hijri months
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const start = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||||
|
const twoMonthsLater = uaqCalendar.dateAdd(start, new Temporal.Duration(0, 0, 0, 2 * 29));
|
||||||
|
// Durations use days — calculate from expected month length
|
||||||
|
|
||||||
|
// Or use dateUntil to measure between two dates
|
||||||
|
const end = Temporal.PlainDate.from('2023-05-20');
|
||||||
|
const diff = uaqCalendar.dateUntil(start, end, { largestUnit: 'months' });
|
||||||
|
console.log(diff.months, diff.days);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use FCNA calendar
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { fcnaCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
|
||||||
|
console.log(fcnaCalendar.year(isoDate)); // 1444
|
||||||
|
console.log(fcnaCalendar.month(isoDate)); // 9
|
||||||
|
console.log(fcnaCalendar.day(isoDate)); // 1
|
||||||
|
// Near month boundaries, UAQ and FCNA may differ by one day
|
||||||
|
```
|
||||||
|
|
||||||
|
## CJS usage
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const { Temporal } = require('@js-temporal/polyfill');
|
||||||
|
const { uaqCalendar } = require('temporal-hijri');
|
||||||
|
|
||||||
|
const d = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
console.log(uaqCalendar.year(d)); // 1444
|
||||||
|
```
|
||||||
99
.github/wiki/examples/scheduling-display.md
vendored
Normal file
99
.github/wiki/examples/scheduling-display.md
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Example: Scheduling Display with Hijri Dates
|
||||||
|
|
||||||
|
A common need in calendaring apps for Muslim communities is displaying both the
|
||||||
|
Gregorian and Hijri dates for an event. This example shows how to take a list of
|
||||||
|
event dates, annotate each with its Hijri date, and display it in a human-readable
|
||||||
|
format.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Temporal } from '@js-temporal/polyfill';
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Month name lookup
|
||||||
|
|
||||||
|
The calendar returns numeric months (1-12). Map them to names:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const HIJRI_MONTHS = [
|
||||||
|
'Muharram', 'Safar', 'Rabi al-Awwal', 'Rabi al-Thani',
|
||||||
|
'Jumada al-Ula', 'Jumada al-Akhira', 'Rajab', 'Shaban',
|
||||||
|
'Ramadan', 'Shawwal', 'Dhul-Qadah', 'Dhul-Hijja',
|
||||||
|
];
|
||||||
|
|
||||||
|
function hijriMonthName(month: number): string {
|
||||||
|
return HIJRI_MONTHS[month - 1] ?? 'Unknown';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Format a single date
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function formatWithHijri(isoDateStr: string): string {
|
||||||
|
const isoDate = Temporal.PlainDate.from(isoDateStr);
|
||||||
|
const hy = uaqCalendar.year(isoDate);
|
||||||
|
const hm = uaqCalendar.month(isoDate);
|
||||||
|
const hd = uaqCalendar.day(isoDate);
|
||||||
|
|
||||||
|
const monthName = hijriMonthName(hm);
|
||||||
|
return `${isoDateStr} (${hd} ${monthName} ${hy} AH)`;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Annotate a schedule
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const events = [
|
||||||
|
{ title: 'Project kickoff', date: '2025-01-01' },
|
||||||
|
{ title: 'Mid-year review', date: '2025-06-15' },
|
||||||
|
{ title: 'Year-end summary', date: '2025-12-31' },
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const event of events) {
|
||||||
|
console.log(`${event.title}: ${formatWithHijri(event.date)}`);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Output:
|
||||||
|
|
||||||
|
```
|
||||||
|
Project kickoff: 2025-01-01 (2 Rajab 1446 AH)
|
||||||
|
Mid-year review: 2025-06-15 (19 Dhul-Hijja 1446 AH)
|
||||||
|
Year-end summary: 2025-12-31 (11 Jumada al-Akhira 1447 AH)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Find the start of Ramadan for a given Hijri year
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
function ramadanStart(hijriYear: number): Temporal.PlainDate {
|
||||||
|
// 1 Ramadan = month 9, day 1
|
||||||
|
return uaqCalendar.dateFromFields({ year: hijriYear, month: 9, day: 1 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const ramadan1447 = ramadanStart(1447);
|
||||||
|
console.log(ramadan1447.toString()); // 2026-02-18 (approximate)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Count days until an event in Hijri months
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const today = Temporal.Now.plainDateISO();
|
||||||
|
const eid = uaqCalendar.dateFromFields({ year: 1447, month: 10, day: 1 });
|
||||||
|
const diff = uaqCalendar.dateUntil(today, eid, { largestUnit: 'months' });
|
||||||
|
|
||||||
|
console.log(`Eid al-Fitr 1447 is in ${diff.months} month(s) and ${diff.days} day(s)`);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
- Month names are transliterated from Arabic. Adapt the spelling to your style guide.
|
||||||
|
- UAQ covers 1318-1500 AH. For dates outside that range, substitute `fcnaCalendar`.
|
||||||
|
- `Temporal.Now.plainDateISO()` returns the current date in the host's local calendar.
|
||||||
|
It does not return a Hijri date directly; pass the result to the calendar methods
|
||||||
|
to get Hijri coordinates.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
[Home](../Home) · [Basic Usage](basic-usage) · [API Reference](../API-Reference)
|
||||||
91
.github/wiki/guides/advanced.md
vendored
Normal file
91
.github/wiki/guides/advanced.md
vendored
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Advanced Usage
|
||||||
|
|
||||||
|
## Custom calendar engines
|
||||||
|
|
||||||
|
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'
|
||||||
|
```
|
||||||
|
|
||||||
|
## DateUntil with different largestUnit values
|
||||||
|
|
||||||
|
`dateUntil` respects the `largestUnit` option:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Temporal } from '@js-temporal/polyfill';
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
|
const start = Temporal.PlainDate.from('2023-01-01');
|
||||||
|
const end = Temporal.PlainDate.from('2023-12-31');
|
||||||
|
|
||||||
|
const inYears = uaqCalendar.dateUntil(start, end, { largestUnit: 'years' });
|
||||||
|
const inMonths = uaqCalendar.dateUntil(start, end, { largestUnit: 'months' });
|
||||||
|
const inDays = uaqCalendar.dateUntil(start, end, { largestUnit: 'days' });
|
||||||
|
|
||||||
|
console.log(inYears.years, inYears.months, inYears.days);
|
||||||
|
console.log(inMonths.months, inMonths.days);
|
||||||
|
console.log(inDays.days);
|
||||||
|
```
|
||||||
|
|
||||||
|
Note: the result measures in Hijri units. One Hijri year is 354 or 355 days, so `inYears.days` may differ from what you would expect in Gregorian.
|
||||||
|
|
||||||
|
## PlainYearMonth and PlainMonthDay
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Temporal } from '@js-temporal/polyfill';
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
|
// Year-month in Hijri
|
||||||
|
const ym = uaqCalendar.yearMonthFromFields({ year: 1444, month: 9 });
|
||||||
|
console.log(ym.toString()); // ISO year-month of 1 Ramadan 1444
|
||||||
|
|
||||||
|
// Month-day in Hijri
|
||||||
|
const md = uaqCalendar.monthDayFromFields({ month: 9, day: 1 });
|
||||||
|
console.log(md.toString()); // ISO month-day of Ramadan 1st
|
||||||
|
```
|
||||||
|
|
||||||
|
## Out-of-range behavior
|
||||||
|
|
||||||
|
UAQ covers 1318-1500 AH (1900-2076 CE). Requesting dates outside that range throws `RangeError`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const earlyDate = Temporal.PlainDate.from('1800-01-01');
|
||||||
|
try {
|
||||||
|
uaqCalendar.year(earlyDate); // throws RangeError
|
||||||
|
} catch (e) {
|
||||||
|
if (e instanceof RangeError) {
|
||||||
|
// Use FCNA for unbounded coverage
|
||||||
|
import { fcnaCalendar } from 'temporal-hijri';
|
||||||
|
console.log(fcnaCalendar.year(earlyDate));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using with native Temporal
|
||||||
|
|
||||||
|
When native `Temporal` is available (future Node.js or browsers), you can use it directly without the polyfill:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// No import from @js-temporal/polyfill
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
console.log(uaqCalendar.year(isoDate)); // 1444
|
||||||
|
```
|
||||||
|
|
||||||
|
The `uaqCalendar` and `fcnaCalendar` objects implement the `Temporal.CalendarProtocol` interface and work with any spec-conforming implementation.
|
||||||
98
.github/wiki/guides/quickstart.md
vendored
Normal file
98
.github/wiki/guides/quickstart.md
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
||||||
|
# Quick Start
|
||||||
|
|
||||||
|
This guide covers the most common use cases in temporal-hijri. All examples use `uaqCalendar` (Umm al-Qura). For FCNA/ISNA output, substitute `fcnaCalendar`.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm add temporal-hijri hijri-core @js-temporal/polyfill
|
||||||
|
```
|
||||||
|
|
||||||
|
`hijri-core` is required. `@js-temporal/polyfill` is required in environments without native `Temporal` support. In environments with native Temporal (Node 22+ with the flag, or future standard support), omit the polyfill.
|
||||||
|
|
||||||
|
## Import
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { Temporal } from '@js-temporal/polyfill'; // or use native Temporal
|
||||||
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
```
|
||||||
|
|
||||||
|
## Convert an ISO date to Hijri
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
|
||||||
|
console.log(uaqCalendar.year(isoDate)); // 1444
|
||||||
|
console.log(uaqCalendar.month(isoDate)); // 9
|
||||||
|
console.log(uaqCalendar.day(isoDate)); // 1
|
||||||
|
console.log(uaqCalendar.monthCode(isoDate)); // 'M09'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Convert Hijri coordinates to ISO
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const ramadan = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||||
|
console.log(ramadan.toString()); // '2023-03-23'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Date arithmetic in Hijri space
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const { Duration } = Temporal;
|
||||||
|
|
||||||
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
|
||||||
|
// Add one Hijri month
|
||||||
|
const nextMonth = uaqCalendar.dateAdd(isoDate, new Duration(0, 1));
|
||||||
|
console.log(uaqCalendar.month(nextMonth)); // 10 (Shawwal)
|
||||||
|
console.log(nextMonth.toString()); // '2023-04-21'
|
||||||
|
|
||||||
|
// Get the difference between two dates
|
||||||
|
const earlier = Temporal.PlainDate.from('2023-01-01');
|
||||||
|
const later = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
const diff = uaqCalendar.dateUntil(earlier, later, { largestUnit: 'months' });
|
||||||
|
console.log(diff.months); // 2 (in Hijri months)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Use the FCNA calendar
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { fcnaCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
console.log(fcnaCalendar.year(isoDate)); // 1444
|
||||||
|
console.log(fcnaCalendar.month(isoDate)); // 9 or may differ by 1 near month start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Singletons vs classes
|
||||||
|
|
||||||
|
The package exports convenience singletons for the common case:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { uaqCalendar, fcnaCalendar } from 'temporal-hijri';
|
||||||
|
```
|
||||||
|
|
||||||
|
If you need to construct a calendar from a custom hijri-core engine:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { HijriCalendar } from 'temporal-hijri';
|
||||||
|
import { registerCalendar, getCalendar } from 'hijri-core';
|
||||||
|
|
||||||
|
registerCalendar('my-engine', myEngine);
|
||||||
|
const cal = new HijriCalendar(getCalendar('my-engine'));
|
||||||
|
```
|
||||||
|
|
||||||
|
## CommonJS
|
||||||
|
|
||||||
|
```js
|
||||||
|
const { Temporal } = require('@js-temporal/polyfill');
|
||||||
|
const { uaqCalendar } = require('temporal-hijri');
|
||||||
|
|
||||||
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
console.log(uaqCalendar.year(isoDate)); // 1444
|
||||||
|
```
|
||||||
|
|
||||||
|
## Next steps
|
||||||
|
|
||||||
|
- [API Reference](API-Reference) for all calendar protocol methods
|
||||||
|
- [Architecture](Architecture) for how the Temporal Calendar Protocol is implemented
|
||||||
50
.github/workflows/ci.yml
vendored
50
.github/workflows/ci.yml
vendored
|
|
@ -15,22 +15,39 @@ jobs:
|
||||||
node: [20, 22, 24]
|
node: [20, 22, 24]
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node }}
|
node-version: ${{ matrix.node }}
|
||||||
cache: pnpm
|
cache: pnpm
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm run build
|
- run: pnpm run build
|
||||||
- run: node test.mjs
|
- run: node --test test.mjs
|
||||||
- run: node test-cjs.cjs
|
- run: node --test test-cjs.cjs
|
||||||
|
|
||||||
|
lint:
|
||||||
|
name: Lint
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm run lint
|
||||||
|
- run: pnpm run format:check
|
||||||
|
|
||||||
typecheck:
|
typecheck:
|
||||||
name: Typecheck
|
name: Typecheck
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
|
|
@ -43,7 +60,8 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: pnpm/action-setup@v4
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
- uses: actions/setup-node@v4
|
- uses: actions/setup-node@v4
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 24
|
||||||
|
|
@ -60,3 +78,25 @@ jobs:
|
||||||
grep "README.md" pack-output.txt
|
grep "README.md" pack-output.txt
|
||||||
grep "CHANGELOG.md" pack-output.txt
|
grep "CHANGELOG.md" pack-output.txt
|
||||||
grep "LICENSE" pack-output.txt
|
grep "LICENSE" pack-output.txt
|
||||||
|
|
||||||
|
coverage:
|
||||||
|
name: Coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- name: Enable corepack
|
||||||
|
run: corepack enable
|
||||||
|
- uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: pnpm
|
||||||
|
- run: pnpm install --frozen-lockfile
|
||||||
|
- run: pnpm run build
|
||||||
|
- name: Coverage
|
||||||
|
run: pnpm run coverage
|
||||||
|
- name: Upload to Codecov
|
||||||
|
uses: codecov/codecov-action@v4
|
||||||
|
with:
|
||||||
|
files: ./coverage/lcov.info
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
fail_ci_if_error: false
|
||||||
|
|
|
||||||
6
.github/workflows/wiki-sync.yml
vendored
6
.github/workflows/wiki-sync.yml
vendored
|
|
@ -4,7 +4,7 @@ on:
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
branches: [main]
|
||||||
paths:
|
paths:
|
||||||
- '.wiki/**'
|
- '.github/wiki/**'
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
|
@ -16,10 +16,10 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Sync .wiki/ to GitHub Wiki
|
- name: Sync .github/wiki/ to GitHub Wiki
|
||||||
uses: Andrew-Chen-Wang/github-wiki-action@v4
|
uses: Andrew-Chen-Wang/github-wiki-action@v4
|
||||||
with:
|
with:
|
||||||
path: .wiki/
|
path: .github/wiki/
|
||||||
env:
|
env:
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GITHUB_ACTOR: ${{ github.actor }}
|
GITHUB_ACTOR: ${{ github.actor }}
|
||||||
|
|
|
||||||
11
.gitignore
vendored
11
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
coverage/
|
||||||
*.tgz
|
*.tgz
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
@ -9,5 +10,15 @@ dist/
|
||||||
|
|
||||||
# AI agent directories
|
# AI agent directories
|
||||||
.aider*
|
.aider*
|
||||||
|
.copilot/
|
||||||
.cursor/
|
.cursor/
|
||||||
.continue/
|
.continue/
|
||||||
|
.windsurf/
|
||||||
|
.codeium/
|
||||||
|
.tabnine/
|
||||||
|
.vscode/*
|
||||||
|
.idea/
|
||||||
|
.codex/
|
||||||
|
.aider/
|
||||||
|
.aider.chat.history.md
|
||||||
|
.gemini/
|
||||||
|
|
|
||||||
37
CHANGELOG.md
37
CHANGELOG.md
|
|
@ -2,18 +2,35 @@
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
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/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
## [1.0.0] - 2026-02-25
|
## [1.0.3] - 2026-06-10
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Date handed to hijri-core is now built via `Date.UTC()` to match hijri-core's UTC-day
|
||||||
|
contract; fixes previous-day results on east-of-UTC hosts (e.g. UTC+5, UTC+8).
|
||||||
|
Requires hijri-core 1.0.3.
|
||||||
|
|
||||||
|
## [1.0.2] - 2026-05-30
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
- TSDoc comments on all public `HijriCalendar` methods
|
||||||
|
|
||||||
- `HijriCalendar` base class implementing the TC39 Temporal Calendar Protocol
|
### Changed
|
||||||
- `UaqCalendar`: Umm al-Qura calendar (table-driven, 1318-1500 AH coverage)
|
- README condensed; quickstart trimmed to essential examples
|
||||||
- `FcnaCalendar`: FCNA/ISNA calendar (astronomical new moon calculation via Meeus)
|
- CI: corepack before setup-node, prettier scoped to src/, d.mts emitted via postbuild
|
||||||
- `uaqCalendar` and `fcnaCalendar` convenience singletons
|
- Adopt shared config packages (@acamarata/eslint-config, @acamarata/prettier-config, @acamarata/tsconfig)
|
||||||
- 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
|
## [1.0.1] - 2026-05-28
|
||||||
- Peer dependency on `hijri-core ^1.0.0` for conversion logic
|
|
||||||
- Optional peer dependency on `@js-temporal/polyfill ^0.4.0`
|
### Changed
|
||||||
|
- Flatten exports map to ADR-015 standard (import/require/types at top level)
|
||||||
|
- Add "./package.json" export condition
|
||||||
|
- Add coverage script (c8 --reporter=lcov)
|
||||||
|
- Migrate CI from pnpm/action-setup to corepack enable
|
||||||
|
|
||||||
|
## [1.0.0] - 2026-05-28
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Initial release
|
||||||
|
|
|
||||||
188
README.md
188
README.md
|
|
@ -4,195 +4,79 @@
|
||||||
|
|
||||||
# temporal-hijri
|
# temporal-hijri
|
||||||
|
|
||||||
Temporal Calendar Protocol implementation for the Hijri calendar. Works with the TC39 Temporal proposal and `@js-temporal/polyfill`.
|
Temporal Calendar Protocol implementation for the Hijri calendar. Works with the TC39
|
||||||
|
Temporal proposal (Stage 3) 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.
|
Provides `UaqCalendar` (Umm al-Qura) and `FcnaCalendar` (FCNA/ISNA) as plug-in
|
||||||
|
calendars for `Temporal.PlainDate`. The underlying conversion logic comes from
|
||||||
---
|
[hijri-core](https://github.com/acamarata/hijri-core).
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pnpm add temporal-hijri hijri-core
|
pnpm add temporal-hijri hijri-core
|
||||||
|
# Add the polyfill if native Temporal is unavailable:
|
||||||
|
pnpm add @js-temporal/polyfill
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are using the polyfill instead of the native `Temporal` API:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
pnpm add temporal-hijri hijri-core @js-temporal/polyfill
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
```typescript
|
```typescript
|
||||||
import { Temporal } from '@js-temporal/polyfill'; // or use native Temporal
|
import { Temporal } from '@js-temporal/polyfill';
|
||||||
import { uaqCalendar } from 'temporal-hijri';
|
import { uaqCalendar } from 'temporal-hijri';
|
||||||
|
|
||||||
// Convert an ISO date to Hijri coordinates
|
|
||||||
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
const isoDate = Temporal.PlainDate.from('2023-03-23');
|
||||||
|
|
||||||
console.log(uaqCalendar.year(isoDate)); // 1444
|
console.log(uaqCalendar.year(isoDate)); // 1444
|
||||||
console.log(uaqCalendar.month(isoDate)); // 9 (Ramadan)
|
console.log(uaqCalendar.month(isoDate)); // 9 (Ramadan)
|
||||||
console.log(uaqCalendar.day(isoDate)); // 1
|
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
|
// Convert Hijri coordinates to ISO
|
||||||
const ramadan = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
const ramadan = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||||
console.log(ramadan.toString()); // "2023-03-23"
|
console.log(ramadan.toString()); // "2023-03-23"
|
||||||
|
|
||||||
// Arithmetic in Hijri space
|
// Date arithmetic in Hijri space
|
||||||
const { Duration } = Temporal;
|
const { Duration } = Temporal;
|
||||||
const nextMonth = uaqCalendar.dateAdd(isoDate, new Duration(0, 1));
|
const nextMonth = uaqCalendar.dateAdd(isoDate, new Duration(0, 1));
|
||||||
console.log(uaqCalendar.month(nextMonth)); // 10 (Shawwal)
|
console.log(uaqCalendar.month(nextMonth)); // 10 (Shawwal)
|
||||||
console.log(nextMonth.toString()); // "2023-04-21"
|
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
## Calendars
|
||||||
|
|
||||||
## Calendar Classes
|
| Calendar | ID | Authority | Method | Coverage |
|
||||||
|
|-------------|--------------|--------------------|--------------------------|------------------|
|
||||||
### `UaqCalendar`
|
| Umm al-Qura | `hijri-uaq` | KACST, Saudi Arabia | Pre-calculated tables | 1318-1500 AH |
|
||||||
|
| FCNA/ISNA | `hijri-fcna` | Fiqh Council of NA | Astronomical new moon | Unbounded |
|
||||||
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
|
## Documentation
|
||||||
|
|
||||||
Full reference, architecture notes, and algorithmic detail in the [wiki](https://github.com/acamarata/temporal-hijri/wiki).
|
Full reference in the [wiki](https://github.com/acamarata/temporal-hijri/wiki).
|
||||||
|
|
||||||
---
|
- [API Reference](https://github.com/acamarata/temporal-hijri/wiki/API-Reference)
|
||||||
|
- [Architecture](https://github.com/acamarata/temporal-hijri/wiki/Architecture)
|
||||||
|
- [Examples](https://github.com/acamarata/temporal-hijri/wiki/examples/basic-usage)
|
||||||
|
|
||||||
|
## Conversion behavior
|
||||||
|
|
||||||
|
Conversions between ISO and Hijri dates are pure calendar-date mappings: the same
|
||||||
|
ISO date always maps to the same Hijri date on every machine, regardless of the host's
|
||||||
|
timezone. `Temporal.PlainDate` carries no time-of-day information, and the underlying
|
||||||
|
hijri-core engine operates on UTC calendar days, so there is no timezone dependency.
|
||||||
|
|
||||||
|
Note: the Islamic calendar begins a new day at sunset, not midnight. This library
|
||||||
|
follows the civil-calendar convention (midnight boundary) used by most software. Sunset
|
||||||
|
day-start determination is out of scope.
|
||||||
|
|
||||||
## Related
|
## Related
|
||||||
|
|
||||||
- [hijri-core](https://github.com/acamarata/hijri-core): zero-dependency Hijri engine powering this package
|
- [hijri-core](https://github.com/acamarata/hijri-core): the underlying calendar engine
|
||||||
- [luxon-hijri](https://github.com/acamarata/luxon-hijri): Hijri/Gregorian conversion for Luxon
|
- [luxon-hijri](https://github.com/acamarata/luxon-hijri): Hijri support for Luxon
|
||||||
- [pray-calc](https://github.com/acamarata/pray-calc): Islamic prayer times
|
- [pray-calc](https://github.com/acamarata/pray-calc): Islamic prayer times
|
||||||
|
|
||||||
---
|
## Telemetry
|
||||||
|
|
||||||
|
This package supports opt-in anonymous usage telemetry — off by default.
|
||||||
|
Enable: `ACAMARATA_TELEMETRY=1`. See [TELEMETRY.md](./TELEMETRY.md) for what is sent and how to disable.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
|
||||||
8
TELEMETRY.md
Normal file
8
TELEMETRY.md
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Telemetry Disclosure
|
||||||
|
|
||||||
|
This package supports opt-in anonymous usage telemetry via [`@acamarata/telemetry`](https://github.com/acamarata/telemetry).
|
||||||
|
|
||||||
|
Telemetry is **off by default**. No data is sent unless you set `ACAMARATA_TELEMETRY=1`.
|
||||||
|
|
||||||
|
Full disclosure (what is sent, where it goes, how to disable):
|
||||||
|
[github.com/acamarata/telemetry/blob/main/TELEMETRY.md](https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md)
|
||||||
20
eslint.config.mjs
Normal file
20
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
import tsParser from "@typescript-eslint/parser";
|
||||||
|
import tsPlugin from "@typescript-eslint/eslint-plugin";
|
||||||
|
import eslintConfigPrettier from "eslint-config-prettier";
|
||||||
|
import { typescript } from "@acamarata/eslint-config";
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
files: ["**/*.ts"],
|
||||||
|
plugins: { "@typescript-eslint": tsPlugin },
|
||||||
|
languageOptions: {
|
||||||
|
parser: tsParser,
|
||||||
|
parserOptions: { project: true, tsconfigRootDir: import.meta.dirname },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
...typescript.map((config) => ({ files: ["**/*.ts"], ...config })),
|
||||||
|
eslintConfigPrettier,
|
||||||
|
{
|
||||||
|
ignores: ["dist/", "node_modules/", "test.mjs", "test-cjs.cjs"],
|
||||||
|
},
|
||||||
|
];
|
||||||
62
package.json
62
package.json
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "temporal-hijri",
|
"name": "temporal-hijri",
|
||||||
"version": "1.0.0",
|
"version": "1.0.3",
|
||||||
"description": "Temporal Calendar Protocol implementation for the Hijri calendar system. Supports Umm al-Qura and FCNA calendars via hijri-core.",
|
"description": "Temporal Calendar Protocol implementation for the Hijri calendar system. Supports Umm al-Qura and FCNA calendars via hijri-core.",
|
||||||
"author": "Aric Camarata",
|
"author": "Aric Camarata",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
|
@ -9,9 +9,11 @@
|
||||||
"types": "./dist/index.d.ts",
|
"types": "./dist/index.d.ts",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": {
|
".": {
|
||||||
"import": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" },
|
"types": "./dist/index.d.ts",
|
||||||
"require": { "types": "./dist/index.d.ts", "default": "./dist/index.cjs" }
|
"import": "./dist/index.mjs",
|
||||||
}
|
"require": "./dist/index.cjs"
|
||||||
|
},
|
||||||
|
"./package.json": "./package.json"
|
||||||
},
|
},
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"files": [
|
"files": [
|
||||||
|
|
@ -23,14 +25,22 @@
|
||||||
"CHANGELOG.md",
|
"CHANGELOG.md",
|
||||||
"LICENSE"
|
"LICENSE"
|
||||||
],
|
],
|
||||||
"engines": { "node": ">=20" },
|
"engines": {
|
||||||
|
"node": ">=20"
|
||||||
|
},
|
||||||
"packageManager": "pnpm@10.30.1",
|
"packageManager": "pnpm@10.30.1",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsup",
|
"build": "tsup",
|
||||||
"typecheck": "tsc --noEmit",
|
"typecheck": "tsc --noEmit",
|
||||||
|
"lint": "eslint src/",
|
||||||
|
"format": "prettier --write src/ test.mjs test-cjs.cjs eslint.config.mjs tsup.config.ts",
|
||||||
|
"format:check": "prettier --check src/ test.mjs test-cjs.cjs eslint.config.mjs tsup.config.ts",
|
||||||
"pretest": "tsup",
|
"pretest": "tsup",
|
||||||
"test": "node test.mjs && node test-cjs.cjs",
|
"test": "node --test test.mjs && node --test test-cjs.cjs",
|
||||||
"prepublishOnly": "tsup"
|
"prepack": "pnpm run build",
|
||||||
|
"coverage": "c8 --reporter=lcov --reporter=text node test.mjs",
|
||||||
|
"docs": "typedoc --out .github/wiki/api src/index.ts",
|
||||||
|
"postbuild": "cp dist/index.d.ts dist/index.d.mts"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"temporal",
|
"temporal",
|
||||||
|
|
@ -45,8 +55,8 @@
|
||||||
"typescript"
|
"typescript"
|
||||||
],
|
],
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"hijri-core": "^1.0.0",
|
"@js-temporal/polyfill": "^0.4.0",
|
||||||
"@js-temporal/polyfill": "^0.4.0"
|
"hijri-core": "^1.0.0"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@js-temporal/polyfill": {
|
"@js-temporal/polyfill": {
|
||||||
|
|
@ -54,14 +64,38 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@acamarata/eslint-config": "^0.1.0",
|
||||||
|
"@acamarata/prettier-config": "^0.1.0",
|
||||||
|
"@acamarata/tsconfig": "^0.1.0",
|
||||||
|
"@eslint/js": "^10.0.1",
|
||||||
"@js-temporal/polyfill": "^0.4.4",
|
"@js-temporal/polyfill": "^0.4.4",
|
||||||
"@types/node": "^22.0.0",
|
"@types/node": "^22.0.0",
|
||||||
"hijri-core": "^1.0.0",
|
"@typescript-eslint/eslint-plugin": "^8.56.1",
|
||||||
|
"@typescript-eslint/parser": "^8.56.1",
|
||||||
|
"c8": "^11.0.0",
|
||||||
|
"eslint": "^10.0.3",
|
||||||
|
"eslint-config-prettier": "^10.1.8",
|
||||||
|
"hijri-core": "^1.0.3",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
"tsup": "^8.0.0",
|
"tsup": "^8.0.0",
|
||||||
"typescript": "^5.5.0"
|
"typedoc": "^0.28.19",
|
||||||
|
"typedoc-plugin-markdown": "^4.11.0",
|
||||||
|
"typescript": "^5.5.0",
|
||||||
|
"typescript-eslint": "^8.56.1",
|
||||||
|
"@acamarata/telemetry": "^0.1.0"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public",
|
||||||
|
"registry": "https://registry.npmjs.org/"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/acamarata/temporal-hijri.git"
|
||||||
},
|
},
|
||||||
"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",
|
"homepage": "https://github.com/acamarata/temporal-hijri#readme",
|
||||||
"bugs": { "url": "https://github.com/acamarata/temporal-hijri/issues" }
|
"bugs": {
|
||||||
|
"url": "https://github.com/acamarata/temporal-hijri/issues"
|
||||||
|
},
|
||||||
|
"type": "module",
|
||||||
|
"prettier": "@acamarata/prettier-config"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1268
pnpm-lock.yaml
1268
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,5 @@
|
||||||
import { getCalendar } from 'hijri-core';
|
import { getCalendar } from "hijri-core";
|
||||||
import { HijriCalendar } from './HijriCalendar';
|
import { HijriCalendar } from "./HijriCalendar";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporal calendar implementation for the FCNA/ISNA calendar.
|
* Temporal calendar implementation for the FCNA/ISNA calendar.
|
||||||
|
|
@ -17,6 +17,6 @@ import { HijriCalendar } from './HijriCalendar';
|
||||||
*/
|
*/
|
||||||
export class FcnaCalendar extends HijriCalendar {
|
export class FcnaCalendar extends HijriCalendar {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(getCalendar('fcna'));
|
super(getCalendar("fcna"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,44 @@
|
||||||
import { Temporal } from '@js-temporal/polyfill';
|
import { Temporal } from "@js-temporal/polyfill";
|
||||||
import type { CalendarEngine } from 'hijri-core';
|
import type { CalendarEngine } from "hijri-core";
|
||||||
|
|
||||||
type DateUnit = 'year' | 'years' | 'month' | 'months' | 'week' | 'weeks' | 'day' | 'days';
|
type DateUnit = "year" | "years" | "month" | "months" | "week" | "weeks" | "day" | "days";
|
||||||
|
|
||||||
|
/** Reference year for monthDay construction when no year is specified. */
|
||||||
|
const REFERENCE_YEAR = 1444;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Borrow days/months so that a Hijri date difference has non-negative components.
|
||||||
|
*
|
||||||
|
* Shared by the 'years' and 'months' branches of dateUntil() to avoid
|
||||||
|
* duplicating the borrow logic.
|
||||||
|
*/
|
||||||
|
function borrowHijriDiff(
|
||||||
|
engine: CalendarEngine,
|
||||||
|
years: number,
|
||||||
|
months: number,
|
||||||
|
days: number,
|
||||||
|
h2: { hy: number; hm: number },
|
||||||
|
): { years: number; months: number; days: number } {
|
||||||
|
// Borrow from months when days are negative.
|
||||||
|
if (days < 0) {
|
||||||
|
months--;
|
||||||
|
let borrowHm = h2.hm - 1;
|
||||||
|
let borrowHy = h2.hy;
|
||||||
|
if (borrowHm < 1) {
|
||||||
|
borrowHm = 12;
|
||||||
|
borrowHy--;
|
||||||
|
}
|
||||||
|
days += engine.daysInMonth(borrowHy, borrowHm);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Borrow from years when months are negative.
|
||||||
|
if (months < 0) {
|
||||||
|
years--;
|
||||||
|
months += 12;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { years, months, days };
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class implementing the TC39 Temporal Calendar Protocol for Hijri calendars.
|
* Base class implementing the TC39 Temporal Calendar Protocol for Hijri calendars.
|
||||||
|
|
@ -33,19 +70,17 @@ export class HijriCalendar {
|
||||||
/**
|
/**
|
||||||
* Convert a Temporal.PlainDate (ISO calendar) to Hijri coordinates.
|
* Convert a Temporal.PlainDate (ISO calendar) to Hijri coordinates.
|
||||||
*
|
*
|
||||||
* Uses the local-time Date constructor so that the date components passed to
|
* PlainDate calendar fields are placed in the Date's UTC components via
|
||||||
* the engine match the calendar date exactly, regardless of host timezone.
|
* Date.UTC() because hijri-core reads the UTC calendar day. This ensures
|
||||||
* The UAQ engine reads local components; the FCNA engine reads UTC components.
|
* the conversion returns the correct Hijri date on every host timezone:
|
||||||
* Because we construct with new Date(y, m, d) the local date always matches
|
* without Date.UTC, on east-of-UTC hosts (e.g. UTC+5) the local midnight
|
||||||
* the intended calendar date.
|
* falls on the previous UTC day, causing a one-day-off result.
|
||||||
*/
|
*/
|
||||||
protected toHijri(date: Temporal.PlainDate): { hy: number; hm: number; hd: number } {
|
protected toHijri(date: Temporal.PlainDate): { hy: number; hm: number; hd: number } {
|
||||||
const jsDate = new Date(date.year, date.month - 1, date.day);
|
const jsDate = new Date(Date.UTC(date.year, date.month - 1, date.day));
|
||||||
const hijri = this.engine.toHijri(jsDate);
|
const hijri = this.engine.toHijri(jsDate);
|
||||||
if (!hijri) {
|
if (!hijri) {
|
||||||
throw new RangeError(
|
throw new RangeError(`Date ${date.toString()} is out of range for the ${this.id} calendar`);
|
||||||
`Date ${date.toString()} is out of range for the ${this.id} calendar`
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return hijri;
|
return hijri;
|
||||||
}
|
}
|
||||||
|
|
@ -60,7 +95,7 @@ export class HijriCalendar {
|
||||||
const greg = this.engine.toGregorian(hy, hm, hd);
|
const greg = this.engine.toGregorian(hy, hm, hd);
|
||||||
if (!greg) {
|
if (!greg) {
|
||||||
throw new RangeError(
|
throw new RangeError(
|
||||||
`Hijri date ${hy}/${hm}/${hd} is out of range for the ${this.id} calendar`
|
`Hijri date ${hy}/${hm}/${hd} is out of range for the ${this.id} calendar`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return Temporal.PlainDate.from({
|
return Temporal.PlainDate.from({
|
||||||
|
|
@ -70,12 +105,47 @@ export class HijriCalendar {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve the overflow option from a Temporal options bag.
|
||||||
|
* Returns 'constrain' (the default) or 'reject'.
|
||||||
|
*/
|
||||||
|
private resolveOverflow(options?: { overflow?: "constrain" | "reject" }): "constrain" | "reject" {
|
||||||
|
return options?.overflow ?? "constrain";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clamp or reject a day value based on the overflow setting.
|
||||||
|
*/
|
||||||
|
private applyOverflow(
|
||||||
|
day: number,
|
||||||
|
maxDay: number,
|
||||||
|
month: number,
|
||||||
|
overflow: "constrain" | "reject",
|
||||||
|
): number {
|
||||||
|
if (overflow === "reject" && day > maxDay) {
|
||||||
|
throw new RangeError(`Day ${day} exceeds ${maxDay} days in month ${month}`);
|
||||||
|
}
|
||||||
|
return Math.min(day, maxDay);
|
||||||
|
}
|
||||||
|
|
||||||
// ── Field accessors ───────────────────────────────────────────────────────
|
// ── Field accessors ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Hijri year for the given ISO date.
|
||||||
|
*
|
||||||
|
* @param date - A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
* @returns The Hijri year, e.g. 1444.
|
||||||
|
*/
|
||||||
year(date: Temporal.PlainDate): number {
|
year(date: Temporal.PlainDate): number {
|
||||||
return this.toHijri(date).hy;
|
return this.toHijri(date).hy;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the Hijri month (1-12) for the given ISO date.
|
||||||
|
*
|
||||||
|
* @param date - A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
* @returns Month number 1 (Muharram) through 12 (Dhul-Hijja).
|
||||||
|
*/
|
||||||
month(date: Temporal.PlainDate): number {
|
month(date: Temporal.PlainDate): number {
|
||||||
return this.toHijri(date).hm;
|
return this.toHijri(date).hm;
|
||||||
}
|
}
|
||||||
|
|
@ -87,15 +157,30 @@ export class HijriCalendar {
|
||||||
*/
|
*/
|
||||||
monthCode(date: Temporal.PlainDate): string {
|
monthCode(date: Temporal.PlainDate): string {
|
||||||
const { hm } = this.toHijri(date);
|
const { hm } = this.toHijri(date);
|
||||||
return `M${String(hm).padStart(2, '0')}`;
|
return `M${String(hm).padStart(2, "0")}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the day of the Hijri month (1-29 or 1-30).
|
||||||
|
*
|
||||||
|
* @param date - A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
* @returns Day of month within the Hijri calendar.
|
||||||
|
*/
|
||||||
day(date: Temporal.PlainDate): number {
|
day(date: Temporal.PlainDate): number {
|
||||||
return this.toHijri(date).hd;
|
return this.toHijri(date).hd;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Month and year metrics ─────────────────────────────────────────────────
|
// ── Month and year metrics ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of days in the Hijri month containing the given date.
|
||||||
|
*
|
||||||
|
* Hijri months alternate between 29 and 30 days, but the exact pattern
|
||||||
|
* differs by calendar system (UAQ uses fixed tables; FCNA uses calculation).
|
||||||
|
*
|
||||||
|
* @param date - A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
* @returns 29 or 30.
|
||||||
|
*/
|
||||||
daysInMonth(date: Temporal.PlainDate): number {
|
daysInMonth(date: Temporal.PlainDate): number {
|
||||||
const { hy, hm } = this.toHijri(date);
|
const { hy, hm } = this.toHijri(date);
|
||||||
return this.engine.daysInMonth(hy, hm);
|
return this.engine.daysInMonth(hy, hm);
|
||||||
|
|
@ -114,10 +199,27 @@ export class HijriCalendar {
|
||||||
return total;
|
return total;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of months in the Hijri year.
|
||||||
|
*
|
||||||
|
* Always 12. Unlike the Hebrew calendar, the Hijri lunar calendar has no
|
||||||
|
* intercalary (leap) month — only a possible extra day in Dhul-Hijja.
|
||||||
|
*
|
||||||
|
* @returns Always 12.
|
||||||
|
*/
|
||||||
monthsInYear(_date: Temporal.PlainDate): number {
|
monthsInYear(_date: Temporal.PlainDate): number {
|
||||||
return 12;
|
return 12;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the Hijri year is a leap year (355 days).
|
||||||
|
*
|
||||||
|
* Standard Hijri years have 354 days. A leap year adds one day to
|
||||||
|
* Dhul-Hijja (month 12), making it 355 days total.
|
||||||
|
*
|
||||||
|
* @param date - A Temporal.PlainDate with ISO (Gregorian) coordinates.
|
||||||
|
* @returns `true` if the year has 355 days.
|
||||||
|
*/
|
||||||
inLeapYear(date: Temporal.PlainDate): boolean {
|
inLeapYear(date: Temporal.PlainDate): boolean {
|
||||||
return this.daysInYear(date) === 355;
|
return this.daysInYear(date) === 355;
|
||||||
}
|
}
|
||||||
|
|
@ -151,17 +253,37 @@ export class HijriCalendar {
|
||||||
return Math.ceil(this.dayOfYear(date) / 7);
|
return Math.ceil(this.dayOfYear(date) / 7);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of days in a week.
|
||||||
|
*
|
||||||
|
* Always 7. Required by the Temporal Calendar Protocol.
|
||||||
|
*
|
||||||
|
* @returns Always 7.
|
||||||
|
*/
|
||||||
daysInWeek(_date: Temporal.PlainDate): number {
|
daysInWeek(_date: Temporal.PlainDate): number {
|
||||||
return 7;
|
return 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Temporal Calendar Protocol: fields() ──────────────────────────────────
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the list of fields that the calendar adds to a Temporal object.
|
||||||
|
* Non-era calendars return the input array unchanged.
|
||||||
|
*/
|
||||||
|
fields(fields: string[]): string[] {
|
||||||
|
return fields;
|
||||||
|
}
|
||||||
|
|
||||||
// ── Construction from fields ───────────────────────────────────────────────
|
// ── Construction from fields ───────────────────────────────────────────────
|
||||||
|
|
||||||
dateFromFields(
|
dateFromFields(
|
||||||
fields: { year: number; month: number; day: number },
|
fields: { year: number; month: number; day: number },
|
||||||
_options?: { overflow?: 'constrain' | 'reject' }
|
options?: { overflow?: "constrain" | "reject" },
|
||||||
): Temporal.PlainDate {
|
): Temporal.PlainDate {
|
||||||
return this.fromHijri(fields.year, fields.month, fields.day);
|
const overflow = this.resolveOverflow(options);
|
||||||
|
const maxDay = this.engine.daysInMonth(fields.year, fields.month);
|
||||||
|
const day = this.applyOverflow(fields.day, maxDay, fields.month, overflow);
|
||||||
|
return this.fromHijri(fields.year, fields.month, day);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -171,9 +293,16 @@ export class HijriCalendar {
|
||||||
*/
|
*/
|
||||||
yearMonthFromFields(
|
yearMonthFromFields(
|
||||||
fields: { year: number; month: number },
|
fields: { year: number; month: number },
|
||||||
_options?: { overflow?: 'constrain' | 'reject' }
|
options?: { overflow?: "constrain" | "reject" },
|
||||||
): Temporal.PlainYearMonth {
|
): Temporal.PlainYearMonth {
|
||||||
const isoDate = this.fromHijri(fields.year, fields.month, 1);
|
const overflow = this.resolveOverflow(options);
|
||||||
|
// Clamp month to 1-12 or reject.
|
||||||
|
const maxMonth = 12;
|
||||||
|
if (overflow === "reject" && (fields.month < 1 || fields.month > maxMonth)) {
|
||||||
|
throw new RangeError(`Month ${fields.month} is out of range 1-${maxMonth}`);
|
||||||
|
}
|
||||||
|
const month = Math.max(1, Math.min(fields.month, maxMonth));
|
||||||
|
const isoDate = this.fromHijri(fields.year, month, 1);
|
||||||
return Temporal.PlainYearMonth.from({
|
return Temporal.PlainYearMonth.from({
|
||||||
year: isoDate.year,
|
year: isoDate.year,
|
||||||
month: isoDate.month,
|
month: isoDate.month,
|
||||||
|
|
@ -187,12 +316,13 @@ export class HijriCalendar {
|
||||||
*/
|
*/
|
||||||
monthDayFromFields(
|
monthDayFromFields(
|
||||||
fields: { month: number; day: number; year?: number },
|
fields: { month: number; day: number; year?: number },
|
||||||
_options?: { overflow?: 'constrain' | 'reject' }
|
options?: { overflow?: "constrain" | "reject" },
|
||||||
): Temporal.PlainMonthDay {
|
): Temporal.PlainMonthDay {
|
||||||
// A reference year is needed to resolve the Hijri month/day to an ISO date.
|
const overflow = this.resolveOverflow(options);
|
||||||
// Default to 1444 AH (2022-2023 CE), a recent well-covered year.
|
const year = fields.year ?? REFERENCE_YEAR;
|
||||||
const year = fields.year ?? 1444;
|
const maxDay = this.engine.daysInMonth(year, fields.month);
|
||||||
const isoDate = this.fromHijri(year, fields.month, fields.day);
|
const day = this.applyOverflow(fields.day, maxDay, fields.month, overflow);
|
||||||
|
const isoDate = this.fromHijri(year, fields.month, day);
|
||||||
return Temporal.PlainMonthDay.from({
|
return Temporal.PlainMonthDay.from({
|
||||||
month: isoDate.month,
|
month: isoDate.month,
|
||||||
day: isoDate.day,
|
day: isoDate.day,
|
||||||
|
|
@ -209,25 +339,27 @@ export class HijriCalendar {
|
||||||
* fixed 30-day offset). Day and week additions are then applied in ISO space
|
* fixed 30-day offset). Day and week additions are then applied in ISO space
|
||||||
* so that they always represent exact day counts.
|
* so that they always represent exact day counts.
|
||||||
*
|
*
|
||||||
|
* Month normalization uses O(1) modular arithmetic instead of iterative loops.
|
||||||
* When the day-of-month exceeds the target month's length after a Hijri-space
|
* 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.
|
* adjustment, it is clamped to the last valid day of that month.
|
||||||
*/
|
*/
|
||||||
dateAdd(
|
dateAdd(
|
||||||
date: Temporal.PlainDate,
|
date: Temporal.PlainDate,
|
||||||
duration: Temporal.Duration,
|
duration: Temporal.Duration,
|
||||||
_options?: { overflow?: 'constrain' | 'reject' }
|
_options?: { overflow?: "constrain" | "reject" },
|
||||||
): Temporal.PlainDate {
|
): Temporal.PlainDate {
|
||||||
const { hy, hm, hd } = this.toHijri(date);
|
const { hy, hm, hd } = this.toHijri(date);
|
||||||
|
|
||||||
let newHy = hy + (duration.years ?? 0);
|
const years = duration.years ?? 0;
|
||||||
let newHm = hm + (duration.months ?? 0);
|
const months = duration.months ?? 0;
|
||||||
|
|
||||||
// Normalize month overflow into years.
|
// O(1) month normalization via modular arithmetic.
|
||||||
while (newHm > 12) {
|
const totalMonths = (hy - 1) * 12 + (hm - 1) + years * 12 + months;
|
||||||
newHm -= 12;
|
let newHy = Math.floor(totalMonths / 12) + 1;
|
||||||
newHy++;
|
let newHm = (totalMonths % 12) + 1;
|
||||||
}
|
|
||||||
while (newHm < 1) {
|
// Handle negative modulo (JS % can return negative values).
|
||||||
|
if (newHm < 1) {
|
||||||
newHm += 12;
|
newHm += 12;
|
||||||
newHy--;
|
newHy--;
|
||||||
}
|
}
|
||||||
|
|
@ -252,79 +384,36 @@ export class HijriCalendar {
|
||||||
dateUntil(
|
dateUntil(
|
||||||
one: Temporal.PlainDate,
|
one: Temporal.PlainDate,
|
||||||
two: Temporal.PlainDate,
|
two: Temporal.PlainDate,
|
||||||
options?: { largestUnit?: DateUnit }
|
options?: { largestUnit?: DateUnit },
|
||||||
): Temporal.Duration {
|
): Temporal.Duration {
|
||||||
const largestUnit: DateUnit = options?.largestUnit ?? 'days';
|
const largestUnit: DateUnit = options?.largestUnit ?? "days";
|
||||||
|
|
||||||
if (largestUnit === 'years' || largestUnit === 'year') {
|
if (largestUnit === "years" || largestUnit === "year") {
|
||||||
const h1 = this.toHijri(one);
|
const h1 = this.toHijri(one);
|
||||||
const h2 = this.toHijri(two);
|
const h2 = this.toHijri(two);
|
||||||
|
const diff = borrowHijriDiff(this.engine, h2.hy - h1.hy, h2.hm - h1.hm, h2.hd - h1.hd, h2);
|
||||||
let years = h2.hy - h1.hy;
|
return new Temporal.Duration(diff.years, diff.months, 0, diff.days);
|
||||||
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') {
|
if (largestUnit === "months" || largestUnit === "month") {
|
||||||
const h1 = this.toHijri(one);
|
const h1 = this.toHijri(one);
|
||||||
const h2 = this.toHijri(two);
|
const h2 = this.toHijri(two);
|
||||||
|
const diff = borrowHijriDiff(this.engine, h2.hy - h1.hy, h2.hm - h1.hm, h2.hd - h1.hd, h2);
|
||||||
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.
|
// Roll years into months.
|
||||||
return new Temporal.Duration(0, years * 12 + months, 0, days);
|
return new Temporal.Duration(0, diff.years * 12 + diff.months, 0, diff.days);
|
||||||
}
|
}
|
||||||
|
|
||||||
// For weeks and days, delegate to ISO arithmetic which is exact.
|
// For weeks and days, delegate to ISO arithmetic which is exact.
|
||||||
if (largestUnit === 'weeks' || largestUnit === 'week') {
|
if (largestUnit === "weeks" || largestUnit === "week") {
|
||||||
return one.until(two, { largestUnit: 'weeks' });
|
return one.until(two, { largestUnit: "weeks" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return one.until(two, { largestUnit: 'days' });
|
return one.until(two, { largestUnit: "days" });
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeFields(
|
mergeFields(
|
||||||
fields: Record<string, unknown>,
|
fields: Record<string, unknown>,
|
||||||
additionalFields: Record<string, unknown>
|
additionalFields: Record<string, unknown>,
|
||||||
): Record<string, unknown> {
|
): Record<string, unknown> {
|
||||||
return { ...fields, ...additionalFields };
|
return { ...fields, ...additionalFields };
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { getCalendar } from 'hijri-core';
|
import { getCalendar } from "hijri-core";
|
||||||
import { HijriCalendar } from './HijriCalendar';
|
import { HijriCalendar } from "./HijriCalendar";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Temporal calendar implementation for the Umm al-Qura calendar.
|
* Temporal calendar implementation for the Umm al-Qura calendar.
|
||||||
|
|
@ -15,6 +15,6 @@ import { HijriCalendar } from './HijriCalendar';
|
||||||
*/
|
*/
|
||||||
export class UaqCalendar extends HijriCalendar {
|
export class UaqCalendar extends HijriCalendar {
|
||||||
constructor() {
|
constructor() {
|
||||||
super(getCalendar('uaq'));
|
super(getCalendar("uaq"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
21
src/index.ts
21
src/index.ts
|
|
@ -1,12 +1,21 @@
|
||||||
export { HijriCalendar } from './calendars/HijriCalendar';
|
export { HijriCalendar } from "./calendars/HijriCalendar";
|
||||||
export { UaqCalendar } from './calendars/UaqCalendar';
|
export { UaqCalendar } from "./calendars/UaqCalendar";
|
||||||
export { FcnaCalendar } from './calendars/FcnaCalendar';
|
export { FcnaCalendar } from "./calendars/FcnaCalendar";
|
||||||
|
|
||||||
export type { HijriDate, CalendarEngine, ConversionOptions } from 'hijri-core';
|
export type { HijriDate, CalendarEngine, ConversionOptions } from "hijri-core";
|
||||||
|
|
||||||
// Pre-built singletons. Import and use directly; no need to instantiate.
|
// Pre-built singletons. Import and use directly; no need to instantiate.
|
||||||
import { UaqCalendar } from './calendars/UaqCalendar';
|
import { UaqCalendar } from "./calendars/UaqCalendar";
|
||||||
import { FcnaCalendar } from './calendars/FcnaCalendar';
|
import { FcnaCalendar } from "./calendars/FcnaCalendar";
|
||||||
|
|
||||||
export const uaqCalendar = new UaqCalendar();
|
export const uaqCalendar = new UaqCalendar();
|
||||||
export const fcnaCalendar = new FcnaCalendar();
|
export const fcnaCalendar = new FcnaCalendar();
|
||||||
|
|
||||||
|
// ── Opt-in anonymous telemetry ────────────────────────────────────────────────
|
||||||
|
// Off by default. Enable: ACAMARATA_TELEMETRY=1
|
||||||
|
// What is sent + how to disable: https://github.com/acamarata/telemetry/blob/main/TELEMETRY.md
|
||||||
|
import("@acamarata/telemetry")
|
||||||
|
.then(({ track }) => track("load", { package: "temporal-hijri", version: "1.0.3" }))
|
||||||
|
.catch(() => {
|
||||||
|
// telemetry not installed or disabled — that is fine
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1 +1 @@
|
||||||
export type { HijriDate, CalendarEngine, ConversionOptions } from 'hijri-core';
|
export type { HijriDate, CalendarEngine, ConversionOptions } from "hijri-core";
|
||||||
|
|
|
||||||
96
test-cjs.cjs
96
test-cjs.cjs
|
|
@ -1,4 +1,4 @@
|
||||||
'use strict';
|
"use strict";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CJS test suite for temporal-hijri.
|
* CJS test suite for temporal-hijri.
|
||||||
|
|
@ -6,74 +6,66 @@
|
||||||
* Verifies that the CommonJS build loads and functions correctly via require().
|
* Verifies that the CommonJS build loads and functions correctly via require().
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const assert = require('node:assert/strict');
|
const { describe, it } = require("node:test");
|
||||||
const { Temporal } = require('@js-temporal/polyfill');
|
const assert = require("node:assert/strict");
|
||||||
const { UaqCalendar, FcnaCalendar, uaqCalendar, fcnaCalendar } = require('./dist/index.cjs');
|
const { Temporal } = require("@js-temporal/polyfill");
|
||||||
|
const { UaqCalendar, FcnaCalendar, uaqCalendar, fcnaCalendar } = require("./dist/index.cjs");
|
||||||
|
|
||||||
let passed = 0;
|
const isoRamadan = Temporal.PlainDate.from("2023-03-23");
|
||||||
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 ───────────────────────────────────────────────
|
// ── Class and singleton exports ───────────────────────────────────────────────
|
||||||
|
|
||||||
test('UaqCalendar class loads via require', () => {
|
describe("CJS class exports", () => {
|
||||||
assert(typeof UaqCalendar === 'function', 'UaqCalendar should be a constructor');
|
it("UaqCalendar class loads via require", () => {
|
||||||
const cal = new UaqCalendar();
|
assert(typeof UaqCalendar === "function", "UaqCalendar should be a constructor");
|
||||||
assert.equal(cal.id, 'hijri-uaq');
|
const cal = new UaqCalendar();
|
||||||
});
|
assert.equal(cal.id, "hijri-uaq");
|
||||||
|
});
|
||||||
|
|
||||||
test('FcnaCalendar class loads via require', () => {
|
it("FcnaCalendar class loads via require", () => {
|
||||||
assert(typeof FcnaCalendar === 'function', 'FcnaCalendar should be a constructor');
|
assert(typeof FcnaCalendar === "function", "FcnaCalendar should be a constructor");
|
||||||
const cal = new FcnaCalendar();
|
const cal = new FcnaCalendar();
|
||||||
assert.equal(cal.id, 'hijri-fcna');
|
assert.equal(cal.id, "hijri-fcna");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uaqCalendar singleton id', () => {
|
it("uaqCalendar singleton id", () => {
|
||||||
assert.equal(uaqCalendar.id, 'hijri-uaq');
|
assert.equal(uaqCalendar.id, "hijri-uaq");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('fcnaCalendar singleton id', () => {
|
it("fcnaCalendar singleton id", () => {
|
||||||
assert.equal(fcnaCalendar.id, 'hijri-fcna');
|
assert.equal(fcnaCalendar.id, "hijri-fcna");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Field accessors ───────────────────────────────────────────────────────────
|
// ── Field accessors ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.year(2023-03-23) = 1444', () => {
|
describe("CJS field accessors", () => {
|
||||||
assert.equal(uaqCalendar.year(isoRamadan), 1444);
|
it("uaqCalendar.year(2023-03-23) = 1444", () => {
|
||||||
});
|
assert.equal(uaqCalendar.year(isoRamadan), 1444);
|
||||||
|
});
|
||||||
|
|
||||||
test('uaqCalendar.month(2023-03-23) = 9', () => {
|
it("uaqCalendar.month(2023-03-23) = 9", () => {
|
||||||
assert.equal(uaqCalendar.month(isoRamadan), 9);
|
assert.equal(uaqCalendar.month(isoRamadan), 9);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uaqCalendar.day(2023-03-23) = 1', () => {
|
it("uaqCalendar.day(2023-03-23) = 1", () => {
|
||||||
assert.equal(uaqCalendar.day(isoRamadan), 1);
|
assert.equal(uaqCalendar.day(isoRamadan), 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── dateFromFields ─────────────────────────────────────────────────────────────
|
// ── dateFromFields ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.dateFromFields({year:1444, month:9, day:1}) = 2023-03-23', () => {
|
describe("CJS dateFromFields", () => {
|
||||||
const result = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
it("uaqCalendar.dateFromFields({year:1444, month:9, day:1}) = 2023-03-23", () => {
|
||||||
assert.equal(result.toString(), '2023-03-23');
|
const result = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||||
|
assert.equal(result.toString(), "2023-03-23");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Summary ───────────────────────────────────────────────────────────────────
|
// ── fields() ──────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
console.log(`\n${passed}/${total} tests passed`);
|
describe("CJS fields()", () => {
|
||||||
if (failed > 0) {
|
it("returns the input array unchanged", () => {
|
||||||
console.error(`${failed} test(s) failed`);
|
assert.deepEqual(uaqCalendar.fields(["year", "month", "day"]), ["year", "month", "day"]);
|
||||||
process.exit(1);
|
});
|
||||||
}
|
});
|
||||||
|
|
|
||||||
334
test.mjs
334
test.mjs
|
|
@ -5,147 +5,283 @@
|
||||||
* Reference point: 2023-03-23 = 1 Ramadan 1444 AH (both UAQ and FCNA agree).
|
* Reference point: 2023-03-23 = 1 Ramadan 1444 AH (both UAQ and FCNA agree).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import assert from 'node:assert/strict';
|
import { describe, it } from "node:test";
|
||||||
import { Temporal } from '@js-temporal/polyfill';
|
import assert from "node:assert/strict";
|
||||||
import { UaqCalendar, FcnaCalendar, uaqCalendar, fcnaCalendar } from './dist/index.mjs';
|
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
|
// Reference date: 2023-03-23 = 1 Ramadan 1444 AH
|
||||||
const isoRamadan = Temporal.PlainDate.from('2023-03-23');
|
const isoRamadan = Temporal.PlainDate.from("2023-03-23");
|
||||||
// 2023-04-21 = 1 Shawwal 1444 AH (first day after Ramadan)
|
// 2023-04-21 = 1 Shawwal 1444 AH (first day after Ramadan)
|
||||||
const isoShawwal = Temporal.PlainDate.from('2023-04-21');
|
const isoShawwal = Temporal.PlainDate.from("2023-04-21");
|
||||||
// 2021-08-09 = 1 Muharram 1443 AH (a 355-day / leap year)
|
// 2021-08-09 = 1 Muharram 1443 AH (a 355-day / leap year)
|
||||||
const isoLeapYear = Temporal.PlainDate.from('2021-08-09');
|
const isoLeapYear = Temporal.PlainDate.from("2021-08-09");
|
||||||
|
|
||||||
// ── 1. Class exports ──────────────────────────────────────────────────────────
|
// ── 1. Class exports ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('UaqCalendar class export', () => {
|
describe("Class exports", () => {
|
||||||
assert(UaqCalendar, 'UaqCalendar should be exported');
|
it("UaqCalendar class export", () => {
|
||||||
const cal = new UaqCalendar();
|
assert(UaqCalendar, "UaqCalendar should be exported");
|
||||||
assert(cal instanceof UaqCalendar, 'UaqCalendar should be instantiable');
|
const cal = new UaqCalendar();
|
||||||
});
|
assert(cal instanceof UaqCalendar, "UaqCalendar should be instantiable");
|
||||||
|
});
|
||||||
|
|
||||||
test('FcnaCalendar class export', () => {
|
it("FcnaCalendar class export", () => {
|
||||||
assert(FcnaCalendar, 'FcnaCalendar should be exported');
|
assert(FcnaCalendar, "FcnaCalendar should be exported");
|
||||||
const cal = new FcnaCalendar();
|
const cal = new FcnaCalendar();
|
||||||
assert(cal instanceof FcnaCalendar, 'FcnaCalendar should be instantiable');
|
assert(cal instanceof FcnaCalendar, "FcnaCalendar should be instantiable");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 2. Calendar IDs ───────────────────────────────────────────────────────────
|
// ── 2. Calendar IDs ───────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.id', () => {
|
describe("Calendar IDs", () => {
|
||||||
assert.equal(uaqCalendar.id, 'hijri-uaq');
|
it("uaqCalendar.id", () => {
|
||||||
});
|
assert.equal(uaqCalendar.id, "hijri-uaq");
|
||||||
|
});
|
||||||
|
|
||||||
test('fcnaCalendar.id', () => {
|
it("fcnaCalendar.id", () => {
|
||||||
assert.equal(fcnaCalendar.id, 'hijri-fcna');
|
assert.equal(fcnaCalendar.id, "hijri-fcna");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 3. Field accessors on 1 Ramadan 1444 (2023-03-23) ────────────────────────
|
// ── 3. Field accessors on 1 Ramadan 1444 (2023-03-23) ────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.year(2023-03-23) = 1444', () => {
|
describe("Field accessors (UAQ, 1 Ramadan 1444)", () => {
|
||||||
assert.equal(uaqCalendar.year(isoRamadan), 1444);
|
it("year = 1444", () => {
|
||||||
});
|
assert.equal(uaqCalendar.year(isoRamadan), 1444);
|
||||||
|
});
|
||||||
|
|
||||||
test('uaqCalendar.month(2023-03-23) = 9 (Ramadan)', () => {
|
it("month = 9 (Ramadan)", () => {
|
||||||
assert.equal(uaqCalendar.month(isoRamadan), 9);
|
assert.equal(uaqCalendar.month(isoRamadan), 9);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uaqCalendar.day(2023-03-23) = 1', () => {
|
it("day = 1", () => {
|
||||||
assert.equal(uaqCalendar.day(isoRamadan), 1);
|
assert.equal(uaqCalendar.day(isoRamadan), 1);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uaqCalendar.monthCode(2023-03-23) = "M09"', () => {
|
it('monthCode = "M09"', () => {
|
||||||
assert.equal(uaqCalendar.monthCode(isoRamadan), 'M09');
|
assert.equal(uaqCalendar.monthCode(isoRamadan), "M09");
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uaqCalendar.daysInMonth(2023-03-23) = 29 (Ramadan 1444 is 29 days)', () => {
|
it("daysInMonth = 29 (Ramadan 1444)", () => {
|
||||||
assert.equal(uaqCalendar.daysInMonth(isoRamadan), 29);
|
assert.equal(uaqCalendar.daysInMonth(isoRamadan), 29);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uaqCalendar.monthsInYear(2023-03-23) = 12', () => {
|
it("monthsInYear = 12", () => {
|
||||||
assert.equal(uaqCalendar.monthsInYear(isoRamadan), 12);
|
assert.equal(uaqCalendar.monthsInYear(isoRamadan), 12);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('uaqCalendar.daysInWeek(2023-03-23) = 7', () => {
|
it("daysInWeek = 7", () => {
|
||||||
assert.equal(uaqCalendar.daysInWeek(isoRamadan), 7);
|
assert.equal(uaqCalendar.daysInWeek(isoRamadan), 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2023-03-23 is a Thursday. ISO weekday: 1=Mon, ..., 4=Thu, ..., 7=Sun.
|
it("dayOfWeek = 4 (Thursday)", () => {
|
||||||
test('uaqCalendar.dayOfWeek(2023-03-23) = 4 (Thursday)', () => {
|
assert.equal(uaqCalendar.dayOfWeek(isoRamadan), 4);
|
||||||
assert.equal(uaqCalendar.dayOfWeek(isoRamadan), 4);
|
});
|
||||||
});
|
|
||||||
|
|
||||||
// dayOfYear: sum of months 1-8 in 1444 + 1 (first day of month 9).
|
it("dayOfYear = 237", () => {
|
||||||
// Months 1-8 of 1444 total 236 days, so day 237 of the year.
|
assert.equal(uaqCalendar.dayOfYear(isoRamadan), 237);
|
||||||
test('uaqCalendar.dayOfYear(2023-03-23) = 237', () => {
|
});
|
||||||
assert.equal(uaqCalendar.dayOfYear(isoRamadan), 237);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 4. dateFromFields ─────────────────────────────────────────────────────────
|
// ── 4. dateFromFields ─────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.dateFromFields({year:1444, month:9, day:1}) = 2023-03-23', () => {
|
describe("dateFromFields", () => {
|
||||||
const result = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
it("dateFromFields({year:1444, month:9, day:1}) = 2023-03-23", () => {
|
||||||
assert.equal(result.toString(), '2023-03-23');
|
const result = uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 1 });
|
||||||
|
assert.equal(result.toString(), "2023-03-23");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 5. dateAdd ────────────────────────────────────────────────────────────────
|
// ── 5. dateAdd ────────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.dateAdd: adding 1 month from 1 Ramadan 1444 lands on 1 Shawwal 1444', () => {
|
describe("dateAdd", () => {
|
||||||
const oneMonth = new Temporal.Duration(0, 1, 0, 0);
|
it("adding 1 month from 1 Ramadan 1444 lands on 1 Shawwal 1444", () => {
|
||||||
const result = uaqCalendar.dateAdd(isoRamadan, oneMonth);
|
const oneMonth = new Temporal.Duration(0, 1, 0, 0);
|
||||||
// 1 Shawwal 1444 = 2023-04-21
|
const result = uaqCalendar.dateAdd(isoRamadan, oneMonth);
|
||||||
assert.equal(result.toString(), isoShawwal.toString());
|
assert.equal(result.toString(), isoShawwal.toString());
|
||||||
// Verify the result is in Shawwal (month 10)
|
assert.equal(uaqCalendar.month(result), 10);
|
||||||
assert.equal(uaqCalendar.month(result), 10);
|
});
|
||||||
|
|
||||||
|
it("adding 7 days from 1 Ramadan 1444", () => {
|
||||||
|
const sevenDays = new Temporal.Duration(0, 0, 0, 7);
|
||||||
|
const result = uaqCalendar.dateAdd(isoRamadan, sevenDays);
|
||||||
|
assert.equal(uaqCalendar.day(result), 8);
|
||||||
|
assert.equal(uaqCalendar.month(result), 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adding 1 week from 1 Ramadan 1444", () => {
|
||||||
|
const oneWeek = new Temporal.Duration(0, 0, 1, 0);
|
||||||
|
const result = uaqCalendar.dateAdd(isoRamadan, oneWeek);
|
||||||
|
assert.equal(uaqCalendar.day(result), 8);
|
||||||
|
assert.equal(uaqCalendar.month(result), 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("adding 12 months rolls the year forward", () => {
|
||||||
|
const twelveMonths = new Temporal.Duration(0, 12, 0, 0);
|
||||||
|
const result = uaqCalendar.dateAdd(isoRamadan, twelveMonths);
|
||||||
|
assert.equal(uaqCalendar.year(result), 1445);
|
||||||
|
assert.equal(uaqCalendar.month(result), 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("subtracting months via negative duration", () => {
|
||||||
|
const negMonth = new Temporal.Duration(0, -1, 0, 0);
|
||||||
|
const result = uaqCalendar.dateAdd(isoShawwal, negMonth);
|
||||||
|
assert.equal(uaqCalendar.month(result), 9);
|
||||||
|
assert.equal(uaqCalendar.year(result), 1444);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 6. inLeapYear ─────────────────────────────────────────────────────────────
|
// ── 6. dateUntil ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.inLeapYear: 1443 AH (355 days) is a leap year, 1444 AH (354) is not', () => {
|
describe("dateUntil", () => {
|
||||||
// 2021-08-09 = 1 Muharram 1443 (355-day year)
|
it("days between 1 Ramadan and 1 Shawwal 1444", () => {
|
||||||
assert.equal(uaqCalendar.inLeapYear(isoLeapYear), true);
|
const dur = uaqCalendar.dateUntil(isoRamadan, isoShawwal, { largestUnit: "days" });
|
||||||
// 2023-03-23 = in 1444 (354-day year)
|
assert.equal(dur.days, 29);
|
||||||
assert.equal(uaqCalendar.inLeapYear(isoRamadan), false);
|
});
|
||||||
|
|
||||||
|
it("months between 1 Ramadan and 1 Shawwal 1444", () => {
|
||||||
|
const dur = uaqCalendar.dateUntil(isoRamadan, isoShawwal, { largestUnit: "months" });
|
||||||
|
assert.equal(dur.months, 1);
|
||||||
|
assert.equal(dur.days, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("years between dates spanning one Hijri year", () => {
|
||||||
|
const iso1443 = uaqCalendar.dateFromFields({ year: 1443, month: 1, day: 1 });
|
||||||
|
const iso1444 = uaqCalendar.dateFromFields({ year: 1444, month: 1, day: 1 });
|
||||||
|
const dur = uaqCalendar.dateUntil(iso1443, iso1444, { largestUnit: "years" });
|
||||||
|
assert.equal(dur.years, 1);
|
||||||
|
assert.equal(dur.months, 0);
|
||||||
|
assert.equal(dur.days, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("weeks between dates", () => {
|
||||||
|
const dur = uaqCalendar.dateUntil(isoRamadan, isoShawwal, { largestUnit: "weeks" });
|
||||||
|
assert.equal(dur.weeks, 4);
|
||||||
|
assert.equal(dur.days, 1);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 7. FCNA calendar ──────────────────────────────────────────────────────────
|
// ── 7. inLeapYear ─────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
// Both UAQ and FCNA agree on 1 Ramadan 1444 = 2023-03-23
|
describe("inLeapYear", () => {
|
||||||
test('fcnaCalendar.year(2023-03-23) returns a valid Hijri year', () => {
|
it("1443 AH (355 days) is a leap year, 1444 AH (354) is not", () => {
|
||||||
const year = fcnaCalendar.year(isoRamadan);
|
assert.equal(uaqCalendar.inLeapYear(isoLeapYear), true);
|
||||||
assert(typeof year === 'number' && year > 1400, `Expected a Hijri year > 1400, got ${year}`);
|
assert.equal(uaqCalendar.inLeapYear(isoRamadan), false);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── 8. Out-of-range error ─────────────────────────────────────────────────────
|
// ── 8. FCNA calendar ──────────────────────────────────────────────────────────
|
||||||
|
|
||||||
test('uaqCalendar.year throws RangeError for out-of-range date (1800-01-01)', () => {
|
describe("FCNA calendar", () => {
|
||||||
// UAQ table covers 1318-1500 AH (Gregorian 1900-2076). 1800 is out of range.
|
it("fcnaCalendar.year(2023-03-23) returns a valid Hijri year", () => {
|
||||||
const outOfRange = Temporal.PlainDate.from('1800-01-01');
|
const year = fcnaCalendar.year(isoRamadan);
|
||||||
assert.throws(
|
assert(typeof year === "number" && year > 1400, `Expected a Hijri year > 1400, got ${year}`);
|
||||||
() => uaqCalendar.year(outOfRange),
|
});
|
||||||
(err) => err instanceof RangeError
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ── Summary ───────────────────────────────────────────────────────────────────
|
// ── 9. Out-of-range error ─────────────────────────────────────────────────────
|
||||||
|
|
||||||
console.log(`\n${passed}/${total} tests passed`);
|
describe("Out-of-range error", () => {
|
||||||
if (failed > 0) {
|
it("uaqCalendar.year throws RangeError for out-of-range date (1800-01-01)", () => {
|
||||||
console.error(`${failed} test(s) failed`);
|
const outOfRange = Temporal.PlainDate.from("1800-01-01");
|
||||||
process.exit(1);
|
assert.throws(
|
||||||
}
|
() => uaqCalendar.year(outOfRange),
|
||||||
|
(err) => err instanceof RangeError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 10. overflow option ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("overflow option", () => {
|
||||||
|
it('dateFromFields with overflow: "constrain" clamps day', () => {
|
||||||
|
const result = uaqCalendar.dateFromFields(
|
||||||
|
{ year: 1444, month: 9, day: 31 },
|
||||||
|
{ overflow: "constrain" },
|
||||||
|
);
|
||||||
|
assert.equal(uaqCalendar.day(result), 29);
|
||||||
|
assert.equal(uaqCalendar.month(result), 9);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('dateFromFields with overflow: "reject" throws RangeError', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => uaqCalendar.dateFromFields({ year: 1444, month: 9, day: 31 }, { overflow: "reject" }),
|
||||||
|
(err) => err instanceof RangeError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('monthDayFromFields with overflow: "constrain" clamps day', () => {
|
||||||
|
const result = uaqCalendar.monthDayFromFields({ month: 9, day: 31 }, { overflow: "constrain" });
|
||||||
|
assert.ok(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('monthDayFromFields with overflow: "reject" throws RangeError', () => {
|
||||||
|
assert.throws(
|
||||||
|
() => uaqCalendar.monthDayFromFields({ month: 9, day: 31 }, { overflow: "reject" }),
|
||||||
|
(err) => err instanceof RangeError,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 11. fields() ──────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("fields()", () => {
|
||||||
|
it("returns the input array unchanged", () => {
|
||||||
|
const input = ["year", "month", "day"];
|
||||||
|
const result = uaqCalendar.fields(input);
|
||||||
|
assert.deepEqual(result, ["year", "month", "day"]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("returns an empty array for empty input", () => {
|
||||||
|
assert.deepEqual(uaqCalendar.fields([]), []);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 12. yearMonthFromFields ─────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("yearMonthFromFields", () => {
|
||||||
|
it("creates a PlainYearMonth for Ramadan 1444", () => {
|
||||||
|
const result = uaqCalendar.yearMonthFromFields({ year: 1444, month: 9 });
|
||||||
|
assert.ok(result);
|
||||||
|
assert.equal(result.month, 3);
|
||||||
|
assert.equal(result.year, 2023);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 13. monthDayFromFields ──────────────────────────────────────────────────
|
||||||
|
|
||||||
|
describe("monthDayFromFields", () => {
|
||||||
|
it("creates a PlainMonthDay for 15 Ramadan (default reference year)", () => {
|
||||||
|
const result = uaqCalendar.monthDayFromFields({ month: 9, day: 15 });
|
||||||
|
assert.ok(result);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("creates a PlainMonthDay with explicit year", () => {
|
||||||
|
const result = uaqCalendar.monthDayFromFields({ month: 9, day: 1, year: 1445 });
|
||||||
|
assert.ok(result);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// ── 14. UTC-day round-trip regression ─────────────────────────────────────────
|
||||||
|
// Verifies that ISO→Hijri→ISO is exact regardless of host timezone.
|
||||||
|
// 2025-03-01 = 1 Ramadan 1446 AH (UAQ).
|
||||||
|
|
||||||
|
describe("UTC-day round-trip regression (ISO → Hijri → ISO)", () => {
|
||||||
|
const isoRamadan1446 = Temporal.PlainDate.from("2025-03-01");
|
||||||
|
|
||||||
|
it("2025-03-01 maps to 1 Ramadan 1446 AH", () => {
|
||||||
|
assert.equal(uaqCalendar.year(isoRamadan1446), 1446);
|
||||||
|
assert.equal(uaqCalendar.month(isoRamadan1446), 9);
|
||||||
|
assert.equal(uaqCalendar.day(isoRamadan1446), 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("round-trip: 1446-09-01 → ISO → Hijri returns 1446-09-01", () => {
|
||||||
|
const iso = uaqCalendar.dateFromFields({ year: 1446, month: 9, day: 1 });
|
||||||
|
assert.equal(iso.toString(), "2025-03-01");
|
||||||
|
assert.equal(uaqCalendar.year(iso), 1446);
|
||||||
|
assert.equal(uaqCalendar.month(iso), 9);
|
||||||
|
assert.equal(uaqCalendar.day(iso), 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,9 @@
|
||||||
{
|
{
|
||||||
|
"extends": "@acamarata/tsconfig/tsconfig.library.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2020",
|
"noImplicitReturns": true,
|
||||||
"module": "ESNext",
|
"noFallthroughCasesInSwitch": true,
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"strict": true,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"declaration": true,
|
|
||||||
"declarationMap": true,
|
|
||||||
"sourceMap": true,
|
|
||||||
"outDir": "dist",
|
|
||||||
"rootDir": "src",
|
"rootDir": "src",
|
||||||
"types": ["node"]
|
"types": ["node"]
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
import { defineConfig } from 'tsup';
|
import { defineConfig } from "tsup";
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
entry: ['src/index.ts'],
|
entry: ["src/index.ts"],
|
||||||
format: ['cjs', 'esm'],
|
format: ["cjs", "esm"],
|
||||||
dts: true,
|
dts: true,
|
||||||
clean: true,
|
clean: true,
|
||||||
outDir: 'dist',
|
outDir: "dist",
|
||||||
splitting: false,
|
splitting: false,
|
||||||
sourcemap: true,
|
sourcemap: true,
|
||||||
target: 'es2020',
|
target: "es2020",
|
||||||
platform: 'node',
|
platform: "node",
|
||||||
external: ['hijri-core', '@js-temporal/polyfill'],
|
external: ["hijri-core", "@js-temporal/polyfill"],
|
||||||
outExtension({ format }) {
|
outExtension({ format }) {
|
||||||
return { js: format === 'esm' ? '.mjs' : '.cjs' };
|
return { js: format === "esm" ? ".mjs" : ".cjs" };
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
||||||
10
typedoc.json
Normal file
10
typedoc.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"entryPoints": ["src/index.ts"],
|
||||||
|
"out": ".github/wiki/api",
|
||||||
|
"plugin": ["typedoc-plugin-markdown"],
|
||||||
|
"readme": "none",
|
||||||
|
"skipErrorChecking": false,
|
||||||
|
"excludePrivate": true,
|
||||||
|
"excludeProtected": true,
|
||||||
|
"includeVersion": true
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue