calendaryjs-plugin-hijri 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/dist/index.cjs +112 -0
- package/dist/index.d.cts +94 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.js +105 -0
- package/package.json +78 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 calendaryjs
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
# calendaryjs-plugin-hijri
|
|
2
|
+
|
|
3
|
+
Islamic (Hijri) calendar plugin for [calendaryjs](https://github.com/calendaryjs/calendaryjs). Gregorian ↔ Hijri date conversion + Hijri recurring events. Uses the **tabular (civil) Islamic calendar** — arithmetic, deterministic, and round-trip safe.
|
|
4
|
+
|
|
5
|
+
> The tabular variant is computed, not observed. A sighting- or announcement-based calendar (e.g. Umm al-Qurā) may differ by ±1 day.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **🌙 Hijri events** — define events by Hijri date (`type: "hijri"`)
|
|
10
|
+
- **🔄 Date conversion** — Gregorian ↔ Hijri, exact inverses (round-trip safe)
|
|
11
|
+
- **📅 Drift-aware** — a Hijri date can fall 0, 1, or 2 times in a Gregorian year; all occurrences are returned
|
|
12
|
+
- **🗓 Leap years** — standard 30-year tabular cycle (11 leap years)
|
|
13
|
+
|
|
14
|
+
## Installation
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add calendaryjs calendaryjs-plugin-hijri
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Requirements
|
|
21
|
+
|
|
22
|
+
- `calendaryjs` >= 0.1.0
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { calendary } from "calendaryjs";
|
|
28
|
+
import { hijri } from "calendaryjs-plugin-hijri";
|
|
29
|
+
|
|
30
|
+
const cal = calendary();
|
|
31
|
+
cal.use(hijri());
|
|
32
|
+
|
|
33
|
+
cal.addGroup({
|
|
34
|
+
id: "islamic-holidays",
|
|
35
|
+
name: "Islamic Holidays",
|
|
36
|
+
events: [
|
|
37
|
+
{
|
|
38
|
+
type: "hijri",
|
|
39
|
+
id: "islamic-new-year",
|
|
40
|
+
hijriMonth: 1,
|
|
41
|
+
hijriDay: 1,
|
|
42
|
+
title: "Islamic New Year",
|
|
43
|
+
},
|
|
44
|
+
{ type: "hijri", id: "eid-al-fitr", hijriMonth: 10, hijriDay: 1, title: "Eid al-Fitr" },
|
|
45
|
+
{ type: "hijri", id: "eid-al-adha", hijriMonth: 12, hijriDay: 10, title: "Eid al-Adha" },
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Resolves Hijri dates to Gregorian automatically
|
|
50
|
+
const events = cal.getEventsInRange("2025-01-01", "2025-12-31");
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Event Type
|
|
54
|
+
|
|
55
|
+
### `hijri`
|
|
56
|
+
|
|
57
|
+
Events based on Hijri calendar dates.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
{
|
|
61
|
+
type: 'hijri';
|
|
62
|
+
id: string;
|
|
63
|
+
hijriMonth: number; // 1-12
|
|
64
|
+
hijriDay: number; // 1-30
|
|
65
|
+
title?: string;
|
|
66
|
+
// ... other standard event properties
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Because a Hijri month/day drifts ~11 days earlier each Gregorian year, an event
|
|
71
|
+
may occur **twice** (early January + late December) or **not at all** within a
|
|
72
|
+
given Gregorian year. The engine returns every occurrence that lands in range.
|
|
73
|
+
|
|
74
|
+
## Date Conversion
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { gregorianToHijri, hijriToGregorian } from "calendaryjs-plugin-hijri";
|
|
78
|
+
|
|
79
|
+
// Gregorian → Hijri
|
|
80
|
+
gregorianToHijri({ year: 2024, month: 7, day: 8 });
|
|
81
|
+
// → { year: 1446, month: 1, day: 1 }
|
|
82
|
+
|
|
83
|
+
// Hijri → Gregorian
|
|
84
|
+
hijriToGregorian({ year: 1446, month: 1, day: 1 });
|
|
85
|
+
// → { year: 2024, month: 7, day: 8 }
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Calendar helpers
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { isHijriLeapYear, hijriMonthLength, isValidHijriDate } from "calendaryjs-plugin-hijri";
|
|
92
|
+
|
|
93
|
+
isHijriLeapYear(1447); // → true (355-day year)
|
|
94
|
+
hijriMonthLength(1446, 1); // → 30 (odd months are 30 days)
|
|
95
|
+
isValidHijriDate({ year: 1446, month: 12, day: 30 }); // → false (not a leap year)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Optional: enrich days with Hijri info
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
const cal = calendary().use(hijri({ enrichDays: true }));
|
|
102
|
+
cal.addGroup({ id: "g", events: [] });
|
|
103
|
+
|
|
104
|
+
const [day] = cal.getDays({ from: "2024-07-08", to: "2024-07-08" });
|
|
105
|
+
day.hijri; // → { year: 1446, month: 1, day: 1 }
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Related Packages
|
|
109
|
+
|
|
110
|
+
- [calendaryjs](https://github.com/calendaryjs/calendaryjs/tree/main/packages/calendaryjs) - Core package
|
|
111
|
+
- [calendaryjs-plugin-lunar](https://github.com/calendaryjs/calendaryjs/tree/main/packages/lunar) - Lunisolar calendar
|
|
112
|
+
- [calendaryjs-plugin-liturgical](https://github.com/calendaryjs/calendaryjs/tree/main/packages/liturgical) - Liturgical calendar
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// package.json
|
|
4
|
+
var package_default = {
|
|
5
|
+
name: "calendaryjs-plugin-hijri",
|
|
6
|
+
version: "0.1.0"};
|
|
7
|
+
|
|
8
|
+
// src/converter.ts
|
|
9
|
+
var ISLAMIC_EPOCH = 1948440;
|
|
10
|
+
function gregorianToJDN(year, month, day) {
|
|
11
|
+
const a = Math.floor((14 - month) / 12);
|
|
12
|
+
const y = year + 4800 - a;
|
|
13
|
+
const m = month + 12 * a - 3;
|
|
14
|
+
return day + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
|
|
15
|
+
}
|
|
16
|
+
function jdnToGregorian(jdn) {
|
|
17
|
+
const a = jdn + 32044;
|
|
18
|
+
const b = Math.floor((4 * a + 3) / 146097);
|
|
19
|
+
const c = a - Math.floor(146097 * b / 4);
|
|
20
|
+
const d = Math.floor((4 * c + 3) / 1461);
|
|
21
|
+
const e = c - Math.floor(1461 * d / 4);
|
|
22
|
+
const m = Math.floor((5 * e + 2) / 153);
|
|
23
|
+
return {
|
|
24
|
+
year: 100 * b + d - 4800 + Math.floor(m / 10),
|
|
25
|
+
month: m + 3 - 12 * Math.floor(m / 10),
|
|
26
|
+
day: e - Math.floor((153 * m + 2) / 5) + 1
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function hijriToJDN(year, month, day) {
|
|
30
|
+
return day + Math.ceil(29.5 * (month - 1)) + (year - 1) * 354 + Math.floor((3 + 11 * year) / 30) + ISLAMIC_EPOCH - 1;
|
|
31
|
+
}
|
|
32
|
+
function gregorianToHijri(g) {
|
|
33
|
+
const jdn = gregorianToJDN(g.year, g.month, g.day);
|
|
34
|
+
const year = Math.floor((30 * (jdn - ISLAMIC_EPOCH) + 10646) / 10631);
|
|
35
|
+
const month = Math.min(12, Math.ceil((jdn - (29 + hijriToJDN(year, 1, 1))) / 29.5) + 1);
|
|
36
|
+
const day = jdn - hijriToJDN(year, month, 1) + 1;
|
|
37
|
+
return { year, month, day };
|
|
38
|
+
}
|
|
39
|
+
function hijriToGregorian(h) {
|
|
40
|
+
return jdnToGregorian(hijriToJDN(h.year, h.month, h.day));
|
|
41
|
+
}
|
|
42
|
+
function isHijriLeapYear(year) {
|
|
43
|
+
return (11 * year + 14) % 30 < 11;
|
|
44
|
+
}
|
|
45
|
+
function hijriMonthLength(year, month) {
|
|
46
|
+
if (month % 2 === 1) return 30;
|
|
47
|
+
if (month === 12 && isHijriLeapYear(year)) return 30;
|
|
48
|
+
return 29;
|
|
49
|
+
}
|
|
50
|
+
function isValidHijriDate(h) {
|
|
51
|
+
if (h.month < 1 || h.month > 12) return false;
|
|
52
|
+
if (h.day < 1 || h.day > hijriMonthLength(h.year, h.month)) return false;
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// src/generator.ts
|
|
57
|
+
var hijriEventHandler = {
|
|
58
|
+
validate(event) {
|
|
59
|
+
if (typeof event !== "object" || event === null) return false;
|
|
60
|
+
const e = event;
|
|
61
|
+
return e.type === "hijri" && typeof e.hijriMonth === "number" && e.hijriMonth >= 1 && e.hijriMonth <= 12 && typeof e.hijriDay === "number" && e.hijriDay >= 1 && e.hijriDay <= 30 && typeof e.title === "string" && typeof e.id === "string";
|
|
62
|
+
},
|
|
63
|
+
generate(event, gregorianYear) {
|
|
64
|
+
const e = event;
|
|
65
|
+
const dates = [];
|
|
66
|
+
const hyStart = gregorianToHijri({ year: gregorianYear, month: 1, day: 1 }).year;
|
|
67
|
+
const hyEnd = gregorianToHijri({ year: gregorianYear, month: 12, day: 31 }).year;
|
|
68
|
+
for (let hy = hyStart; hy <= hyEnd; hy++) {
|
|
69
|
+
const g = hijriToGregorian({ year: hy, month: e.hijriMonth, day: e.hijriDay });
|
|
70
|
+
if (g.year === gregorianYear) dates.push(new Date(g.year, g.month - 1, g.day));
|
|
71
|
+
}
|
|
72
|
+
return dates;
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// src/plugin.ts
|
|
77
|
+
var hijriDayEnricher = {
|
|
78
|
+
name: "hijri",
|
|
79
|
+
priority: 10,
|
|
80
|
+
enrich: (day) => {
|
|
81
|
+
const h = gregorianToHijri({ year: day.year, month: day.month, day: day.day });
|
|
82
|
+
return { ...day, hijri: { year: h.year, month: h.month, day: h.day } };
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
function hijriPlugin(options = {}) {
|
|
86
|
+
const { enrichDays = false } = options;
|
|
87
|
+
return {
|
|
88
|
+
name: package_default.name,
|
|
89
|
+
version: package_default.version,
|
|
90
|
+
install(calendary) {
|
|
91
|
+
if (enrichDays) {
|
|
92
|
+
calendary.registerDayEnricher(hijriDayEnricher);
|
|
93
|
+
}
|
|
94
|
+
},
|
|
95
|
+
eventTypes: {
|
|
96
|
+
hijri: hijriEventHandler
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
var hijri = Object.assign(hijriPlugin, {
|
|
101
|
+
/** Builder selector for a hijri date — `every("year").on(hijri.date(9, 1))`. */
|
|
102
|
+
date(month, day) {
|
|
103
|
+
return { type: "hijri", fields: { hijriMonth: month, hijriDay: day } };
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
exports.gregorianToHijri = gregorianToHijri;
|
|
108
|
+
exports.hijri = hijri;
|
|
109
|
+
exports.hijriMonthLength = hijriMonthLength;
|
|
110
|
+
exports.hijriToGregorian = hijriToGregorian;
|
|
111
|
+
exports.isHijriLeapYear = isHijriLeapYear;
|
|
112
|
+
exports.isValidHijriDate = isValidHijriDate;
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { CalendarDay, CalendaryPlugin, BaseEventProperties } from 'calendaryjs';
|
|
2
|
+
import { Selector } from 'calendaryjs/builder';
|
|
3
|
+
|
|
4
|
+
/** Hijri date info added by the hijri plugin. */
|
|
5
|
+
interface HijriInfo {
|
|
6
|
+
year: number;
|
|
7
|
+
month: number;
|
|
8
|
+
day: number;
|
|
9
|
+
}
|
|
10
|
+
/** CalendarDay with hijri enrichment. */
|
|
11
|
+
interface CalendarDayWithHijri extends CalendarDay {
|
|
12
|
+
hijri: HijriInfo;
|
|
13
|
+
}
|
|
14
|
+
/** Options for the hijri plugin. */
|
|
15
|
+
interface HijriPluginOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Whether to enrich days with Hijri date information.
|
|
18
|
+
* When true, getDays()/getDay() include `hijri` on each day.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
enrichDays?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Islamic (Hijri) calendar plugin for calendaryjs — tabular/civil variant.
|
|
25
|
+
*
|
|
26
|
+
* Provides:
|
|
27
|
+
* - a `hijri` event type (recurring by Hijri month/day, resolved to Gregorian)
|
|
28
|
+
* - an optional day enricher adding `hijri` info to CalendarDay (opt-in via enrichDays)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const cal = calendary().use(hijri());
|
|
33
|
+
* cal.addGroup({
|
|
34
|
+
* id: "islamic",
|
|
35
|
+
* name: "Islamic",
|
|
36
|
+
* events: [{ type: "hijri", id: "eid-al-fitr", title: "Eid al-Fitr", hijriMonth: 10, hijriDay: 1 }],
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function hijriPlugin(options?: HijriPluginOptions): CalendaryPlugin;
|
|
41
|
+
/**
|
|
42
|
+
* The hijri plugin, plus a builder selector:
|
|
43
|
+
* - `calendary().use(hijri())` registers the `hijri` event type.
|
|
44
|
+
* - `every("year").on(hijri.date(9, 1))` authors a hijri event via the builder.
|
|
45
|
+
*/
|
|
46
|
+
declare const hijri: typeof hijriPlugin & {
|
|
47
|
+
/** Builder selector for a hijri date — `every("year").on(hijri.date(9, 1))`. */
|
|
48
|
+
date(month: number, day: number): Selector;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Module augmentation for CalendarDay.
|
|
53
|
+
* When this plugin is imported, TypeScript recognizes the optional `hijri` property
|
|
54
|
+
* on CalendarDay objects returned by getDays() and getDay().
|
|
55
|
+
*/
|
|
56
|
+
declare module "calendaryjs" {
|
|
57
|
+
interface CalendarDay {
|
|
58
|
+
/** Hijri date information (available when the hijri plugin is used with enrichDays: true) */
|
|
59
|
+
hijri?: HijriInfo;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Calendar date representation (year, month, day). */
|
|
63
|
+
interface CalendarDate {
|
|
64
|
+
year: number;
|
|
65
|
+
month: number;
|
|
66
|
+
day: number;
|
|
67
|
+
}
|
|
68
|
+
/** Gregorian (solar) date. */
|
|
69
|
+
type GregorianDate = CalendarDate;
|
|
70
|
+
/** Hijri (Islamic) date. */
|
|
71
|
+
type HijriDate = CalendarDate;
|
|
72
|
+
/**
|
|
73
|
+
* Hijri event configuration. The developer provides hijriMonth + hijriDay; the
|
|
74
|
+
* plugin resolves the Gregorian date(s) at which it falls in a queried year.
|
|
75
|
+
* @template TMetadata - Custom metadata type extending Record<string, unknown>
|
|
76
|
+
*/
|
|
77
|
+
interface HijriEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>> extends BaseEventProperties<TMetadata> {
|
|
78
|
+
type: "hijri";
|
|
79
|
+
hijriMonth: number;
|
|
80
|
+
hijriDay: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Convert a Gregorian date to its tabular Hijri date. */
|
|
84
|
+
declare function gregorianToHijri(g: GregorianDate): HijriDate;
|
|
85
|
+
/** Convert a tabular Hijri date to its Gregorian date. */
|
|
86
|
+
declare function hijriToGregorian(h: HijriDate): GregorianDate;
|
|
87
|
+
/** Whether a Hijri year is a leap year (355 days) in the 30-year tabular cycle. */
|
|
88
|
+
declare function isHijriLeapYear(year: number): boolean;
|
|
89
|
+
/** Number of days in a Hijri month (1–12). */
|
|
90
|
+
declare function hijriMonthLength(year: number, month: number): number;
|
|
91
|
+
/** Whether a Hijri date is structurally valid. */
|
|
92
|
+
declare function isValidHijriDate(h: HijriDate): boolean;
|
|
93
|
+
|
|
94
|
+
export { type CalendarDate, type CalendarDayWithHijri, type GregorianDate, type HijriDate, type HijriEvent, type HijriInfo, type HijriPluginOptions, gregorianToHijri, hijri, hijriMonthLength, hijriToGregorian, isHijriLeapYear, isValidHijriDate };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { CalendarDay, CalendaryPlugin, BaseEventProperties } from 'calendaryjs';
|
|
2
|
+
import { Selector } from 'calendaryjs/builder';
|
|
3
|
+
|
|
4
|
+
/** Hijri date info added by the hijri plugin. */
|
|
5
|
+
interface HijriInfo {
|
|
6
|
+
year: number;
|
|
7
|
+
month: number;
|
|
8
|
+
day: number;
|
|
9
|
+
}
|
|
10
|
+
/** CalendarDay with hijri enrichment. */
|
|
11
|
+
interface CalendarDayWithHijri extends CalendarDay {
|
|
12
|
+
hijri: HijriInfo;
|
|
13
|
+
}
|
|
14
|
+
/** Options for the hijri plugin. */
|
|
15
|
+
interface HijriPluginOptions {
|
|
16
|
+
/**
|
|
17
|
+
* Whether to enrich days with Hijri date information.
|
|
18
|
+
* When true, getDays()/getDay() include `hijri` on each day.
|
|
19
|
+
* @default false
|
|
20
|
+
*/
|
|
21
|
+
enrichDays?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Islamic (Hijri) calendar plugin for calendaryjs — tabular/civil variant.
|
|
25
|
+
*
|
|
26
|
+
* Provides:
|
|
27
|
+
* - a `hijri` event type (recurring by Hijri month/day, resolved to Gregorian)
|
|
28
|
+
* - an optional day enricher adding `hijri` info to CalendarDay (opt-in via enrichDays)
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* const cal = calendary().use(hijri());
|
|
33
|
+
* cal.addGroup({
|
|
34
|
+
* id: "islamic",
|
|
35
|
+
* name: "Islamic",
|
|
36
|
+
* events: [{ type: "hijri", id: "eid-al-fitr", title: "Eid al-Fitr", hijriMonth: 10, hijriDay: 1 }],
|
|
37
|
+
* });
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
declare function hijriPlugin(options?: HijriPluginOptions): CalendaryPlugin;
|
|
41
|
+
/**
|
|
42
|
+
* The hijri plugin, plus a builder selector:
|
|
43
|
+
* - `calendary().use(hijri())` registers the `hijri` event type.
|
|
44
|
+
* - `every("year").on(hijri.date(9, 1))` authors a hijri event via the builder.
|
|
45
|
+
*/
|
|
46
|
+
declare const hijri: typeof hijriPlugin & {
|
|
47
|
+
/** Builder selector for a hijri date — `every("year").on(hijri.date(9, 1))`. */
|
|
48
|
+
date(month: number, day: number): Selector;
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Module augmentation for CalendarDay.
|
|
53
|
+
* When this plugin is imported, TypeScript recognizes the optional `hijri` property
|
|
54
|
+
* on CalendarDay objects returned by getDays() and getDay().
|
|
55
|
+
*/
|
|
56
|
+
declare module "calendaryjs" {
|
|
57
|
+
interface CalendarDay {
|
|
58
|
+
/** Hijri date information (available when the hijri plugin is used with enrichDays: true) */
|
|
59
|
+
hijri?: HijriInfo;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
/** Calendar date representation (year, month, day). */
|
|
63
|
+
interface CalendarDate {
|
|
64
|
+
year: number;
|
|
65
|
+
month: number;
|
|
66
|
+
day: number;
|
|
67
|
+
}
|
|
68
|
+
/** Gregorian (solar) date. */
|
|
69
|
+
type GregorianDate = CalendarDate;
|
|
70
|
+
/** Hijri (Islamic) date. */
|
|
71
|
+
type HijriDate = CalendarDate;
|
|
72
|
+
/**
|
|
73
|
+
* Hijri event configuration. The developer provides hijriMonth + hijriDay; the
|
|
74
|
+
* plugin resolves the Gregorian date(s) at which it falls in a queried year.
|
|
75
|
+
* @template TMetadata - Custom metadata type extending Record<string, unknown>
|
|
76
|
+
*/
|
|
77
|
+
interface HijriEvent<TMetadata extends Record<string, unknown> = Record<string, unknown>> extends BaseEventProperties<TMetadata> {
|
|
78
|
+
type: "hijri";
|
|
79
|
+
hijriMonth: number;
|
|
80
|
+
hijriDay: number;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Convert a Gregorian date to its tabular Hijri date. */
|
|
84
|
+
declare function gregorianToHijri(g: GregorianDate): HijriDate;
|
|
85
|
+
/** Convert a tabular Hijri date to its Gregorian date. */
|
|
86
|
+
declare function hijriToGregorian(h: HijriDate): GregorianDate;
|
|
87
|
+
/** Whether a Hijri year is a leap year (355 days) in the 30-year tabular cycle. */
|
|
88
|
+
declare function isHijriLeapYear(year: number): boolean;
|
|
89
|
+
/** Number of days in a Hijri month (1–12). */
|
|
90
|
+
declare function hijriMonthLength(year: number, month: number): number;
|
|
91
|
+
/** Whether a Hijri date is structurally valid. */
|
|
92
|
+
declare function isValidHijriDate(h: HijriDate): boolean;
|
|
93
|
+
|
|
94
|
+
export { type CalendarDate, type CalendarDayWithHijri, type GregorianDate, type HijriDate, type HijriEvent, type HijriInfo, type HijriPluginOptions, gregorianToHijri, hijri, hijriMonthLength, hijriToGregorian, isHijriLeapYear, isValidHijriDate };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "calendaryjs-plugin-hijri",
|
|
4
|
+
version: "0.1.0"};
|
|
5
|
+
|
|
6
|
+
// src/converter.ts
|
|
7
|
+
var ISLAMIC_EPOCH = 1948440;
|
|
8
|
+
function gregorianToJDN(year, month, day) {
|
|
9
|
+
const a = Math.floor((14 - month) / 12);
|
|
10
|
+
const y = year + 4800 - a;
|
|
11
|
+
const m = month + 12 * a - 3;
|
|
12
|
+
return day + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - Math.floor(y / 100) + Math.floor(y / 400) - 32045;
|
|
13
|
+
}
|
|
14
|
+
function jdnToGregorian(jdn) {
|
|
15
|
+
const a = jdn + 32044;
|
|
16
|
+
const b = Math.floor((4 * a + 3) / 146097);
|
|
17
|
+
const c = a - Math.floor(146097 * b / 4);
|
|
18
|
+
const d = Math.floor((4 * c + 3) / 1461);
|
|
19
|
+
const e = c - Math.floor(1461 * d / 4);
|
|
20
|
+
const m = Math.floor((5 * e + 2) / 153);
|
|
21
|
+
return {
|
|
22
|
+
year: 100 * b + d - 4800 + Math.floor(m / 10),
|
|
23
|
+
month: m + 3 - 12 * Math.floor(m / 10),
|
|
24
|
+
day: e - Math.floor((153 * m + 2) / 5) + 1
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
function hijriToJDN(year, month, day) {
|
|
28
|
+
return day + Math.ceil(29.5 * (month - 1)) + (year - 1) * 354 + Math.floor((3 + 11 * year) / 30) + ISLAMIC_EPOCH - 1;
|
|
29
|
+
}
|
|
30
|
+
function gregorianToHijri(g) {
|
|
31
|
+
const jdn = gregorianToJDN(g.year, g.month, g.day);
|
|
32
|
+
const year = Math.floor((30 * (jdn - ISLAMIC_EPOCH) + 10646) / 10631);
|
|
33
|
+
const month = Math.min(12, Math.ceil((jdn - (29 + hijriToJDN(year, 1, 1))) / 29.5) + 1);
|
|
34
|
+
const day = jdn - hijriToJDN(year, month, 1) + 1;
|
|
35
|
+
return { year, month, day };
|
|
36
|
+
}
|
|
37
|
+
function hijriToGregorian(h) {
|
|
38
|
+
return jdnToGregorian(hijriToJDN(h.year, h.month, h.day));
|
|
39
|
+
}
|
|
40
|
+
function isHijriLeapYear(year) {
|
|
41
|
+
return (11 * year + 14) % 30 < 11;
|
|
42
|
+
}
|
|
43
|
+
function hijriMonthLength(year, month) {
|
|
44
|
+
if (month % 2 === 1) return 30;
|
|
45
|
+
if (month === 12 && isHijriLeapYear(year)) return 30;
|
|
46
|
+
return 29;
|
|
47
|
+
}
|
|
48
|
+
function isValidHijriDate(h) {
|
|
49
|
+
if (h.month < 1 || h.month > 12) return false;
|
|
50
|
+
if (h.day < 1 || h.day > hijriMonthLength(h.year, h.month)) return false;
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// src/generator.ts
|
|
55
|
+
var hijriEventHandler = {
|
|
56
|
+
validate(event) {
|
|
57
|
+
if (typeof event !== "object" || event === null) return false;
|
|
58
|
+
const e = event;
|
|
59
|
+
return e.type === "hijri" && typeof e.hijriMonth === "number" && e.hijriMonth >= 1 && e.hijriMonth <= 12 && typeof e.hijriDay === "number" && e.hijriDay >= 1 && e.hijriDay <= 30 && typeof e.title === "string" && typeof e.id === "string";
|
|
60
|
+
},
|
|
61
|
+
generate(event, gregorianYear) {
|
|
62
|
+
const e = event;
|
|
63
|
+
const dates = [];
|
|
64
|
+
const hyStart = gregorianToHijri({ year: gregorianYear, month: 1, day: 1 }).year;
|
|
65
|
+
const hyEnd = gregorianToHijri({ year: gregorianYear, month: 12, day: 31 }).year;
|
|
66
|
+
for (let hy = hyStart; hy <= hyEnd; hy++) {
|
|
67
|
+
const g = hijriToGregorian({ year: hy, month: e.hijriMonth, day: e.hijriDay });
|
|
68
|
+
if (g.year === gregorianYear) dates.push(new Date(g.year, g.month - 1, g.day));
|
|
69
|
+
}
|
|
70
|
+
return dates;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
// src/plugin.ts
|
|
75
|
+
var hijriDayEnricher = {
|
|
76
|
+
name: "hijri",
|
|
77
|
+
priority: 10,
|
|
78
|
+
enrich: (day) => {
|
|
79
|
+
const h = gregorianToHijri({ year: day.year, month: day.month, day: day.day });
|
|
80
|
+
return { ...day, hijri: { year: h.year, month: h.month, day: h.day } };
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
function hijriPlugin(options = {}) {
|
|
84
|
+
const { enrichDays = false } = options;
|
|
85
|
+
return {
|
|
86
|
+
name: package_default.name,
|
|
87
|
+
version: package_default.version,
|
|
88
|
+
install(calendary) {
|
|
89
|
+
if (enrichDays) {
|
|
90
|
+
calendary.registerDayEnricher(hijriDayEnricher);
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
eventTypes: {
|
|
94
|
+
hijri: hijriEventHandler
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
var hijri = Object.assign(hijriPlugin, {
|
|
99
|
+
/** Builder selector for a hijri date — `every("year").on(hijri.date(9, 1))`. */
|
|
100
|
+
date(month, day) {
|
|
101
|
+
return { type: "hijri", fields: { hijriMonth: month, hijriDay: day } };
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
export { gregorianToHijri, hijri, hijriMonthLength, hijriToGregorian, isHijriLeapYear, isValidHijriDate };
|
package/package.json
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "calendaryjs-plugin-hijri",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Islamic (Hijri) calendar plugin for calendaryjs — Gregorian ↔ Hijri date conversion (tabular/civil) and Hijri recurring events.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"default": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"require": {
|
|
14
|
+
"types": "./dist/index.d.cts",
|
|
15
|
+
"default": "./dist/index.cjs"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"hijri",
|
|
21
|
+
"islamic-calendar",
|
|
22
|
+
"islamic",
|
|
23
|
+
"hijra",
|
|
24
|
+
"ramadan",
|
|
25
|
+
"calendar",
|
|
26
|
+
"calendaryjs",
|
|
27
|
+
"calendaryjs-plugin",
|
|
28
|
+
"plugin"
|
|
29
|
+
],
|
|
30
|
+
"calendaryjs": {
|
|
31
|
+
"category": "calendar"
|
|
32
|
+
},
|
|
33
|
+
"author": "calendaryjs",
|
|
34
|
+
"license": "MIT",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"calendaryjs": ">=0.1.0"
|
|
37
|
+
},
|
|
38
|
+
"devDependencies": {
|
|
39
|
+
"moment-hijri": "^3.0.0",
|
|
40
|
+
"typescript": "^5.3.2",
|
|
41
|
+
"vitest": "^4.0.18",
|
|
42
|
+
"calendaryjs": "0.2.0"
|
|
43
|
+
},
|
|
44
|
+
"files": [
|
|
45
|
+
"dist"
|
|
46
|
+
],
|
|
47
|
+
"tsup": {
|
|
48
|
+
"entry": [
|
|
49
|
+
"src/index.ts"
|
|
50
|
+
],
|
|
51
|
+
"format": [
|
|
52
|
+
"esm",
|
|
53
|
+
"cjs"
|
|
54
|
+
],
|
|
55
|
+
"dts": true,
|
|
56
|
+
"clean": true,
|
|
57
|
+
"treeshake": true
|
|
58
|
+
},
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "git+https://github.com/calendaryjs/calendaryjs.git",
|
|
62
|
+
"directory": "packages/hijri"
|
|
63
|
+
},
|
|
64
|
+
"homepage": "https://github.com/calendaryjs/calendaryjs/tree/main/packages/hijri#readme",
|
|
65
|
+
"bugs": {
|
|
66
|
+
"url": "https://github.com/calendaryjs/calendaryjs/issues"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"typecheck": "tsc --noEmit",
|
|
70
|
+
"test": "vitest run",
|
|
71
|
+
"test:watch": "vitest",
|
|
72
|
+
"build": "tsup",
|
|
73
|
+
"test:coverage": "vitest run --coverage"
|
|
74
|
+
},
|
|
75
|
+
"main": "./dist/index.cjs",
|
|
76
|
+
"module": "./dist/index.js",
|
|
77
|
+
"types": "./dist/index.d.ts"
|
|
78
|
+
}
|