@nextera.one/tps-standard 0.5.33 → 0.6.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/CHANGELOG.md +88 -0
- package/README.md +133 -56
- package/dist/date.d.ts +54 -0
- package/dist/date.js +174 -0
- package/dist/date.js.map +1 -0
- package/dist/driver-manager.d.ts +34 -0
- package/dist/driver-manager.js +53 -0
- package/dist/driver-manager.js.map +1 -0
- package/dist/drivers/chinese.d.ts +25 -0
- package/dist/drivers/chinese.js +485 -0
- package/dist/drivers/chinese.js.map +1 -0
- package/dist/drivers/gregorian.d.ts +3 -5
- package/dist/drivers/gregorian.js +26 -19
- package/dist/drivers/gregorian.js.map +1 -1
- package/dist/drivers/hijri.d.ts +1 -16
- package/dist/drivers/hijri.js +9 -102
- package/dist/drivers/hijri.js.map +1 -1
- package/dist/drivers/holocene.d.ts +6 -3
- package/dist/drivers/holocene.js +7 -20
- package/dist/drivers/holocene.js.map +1 -1
- package/dist/drivers/julian.d.ts +3 -10
- package/dist/drivers/julian.js +11 -71
- package/dist/drivers/julian.js.map +1 -1
- package/dist/drivers/persian.d.ts +1 -6
- package/dist/drivers/persian.js +17 -92
- package/dist/drivers/persian.js.map +1 -1
- package/dist/drivers/tps.d.ts +11 -28
- package/dist/drivers/tps.js +8 -58
- package/dist/drivers/tps.js.map +1 -1
- package/dist/drivers/unix.d.ts +5 -6
- package/dist/drivers/unix.js +10 -32
- package/dist/drivers/unix.js.map +1 -1
- package/dist/esm/date.js +170 -0
- package/dist/esm/date.js.map +1 -0
- package/dist/esm/driver-manager.js +49 -0
- package/dist/esm/driver-manager.js.map +1 -0
- package/dist/esm/drivers/chinese.js +481 -0
- package/dist/esm/drivers/chinese.js.map +1 -0
- package/dist/esm/drivers/gregorian.js +160 -0
- package/dist/esm/drivers/gregorian.js.map +1 -0
- package/dist/esm/drivers/hijri.js +184 -0
- package/dist/esm/drivers/hijri.js.map +1 -0
- package/dist/esm/drivers/holocene.js +115 -0
- package/dist/esm/drivers/holocene.js.map +1 -0
- package/dist/esm/drivers/julian.js +161 -0
- package/dist/esm/drivers/julian.js.map +1 -0
- package/dist/esm/drivers/persian.js +190 -0
- package/dist/esm/drivers/persian.js.map +1 -0
- package/dist/esm/drivers/tps.js +181 -0
- package/dist/esm/drivers/tps.js.map +1 -0
- package/dist/esm/drivers/unix.js +50 -0
- package/dist/esm/drivers/unix.js.map +1 -0
- package/dist/esm/index.js +873 -0
- package/dist/esm/index.js.map +1 -0
- package/dist/esm/types.js +28 -0
- package/dist/esm/types.js.map +1 -0
- package/dist/esm/uid.js +221 -0
- package/dist/esm/uid.js.map +1 -0
- package/dist/esm/utils/calendar.js +126 -0
- package/dist/esm/utils/calendar.js.map +1 -0
- package/dist/esm/utils/env.js +76 -0
- package/dist/esm/utils/env.js.map +1 -0
- package/dist/esm/utils/timezone.js +168 -0
- package/dist/esm/utils/timezone.js.map +1 -0
- package/dist/esm/utils/tps-string.js +160 -0
- package/dist/esm/utils/tps-string.js.map +1 -0
- package/dist/index.d.ts +84 -466
- package/dist/index.js +430 -1095
- package/dist/index.js.map +1 -1
- package/dist/types.d.ts +103 -0
- package/dist/types.js +31 -0
- package/dist/types.js.map +1 -0
- package/dist/uid.d.ts +48 -0
- package/dist/uid.js +225 -0
- package/dist/uid.js.map +1 -0
- package/dist/utils/calendar.d.ts +55 -0
- package/dist/utils/calendar.js +136 -0
- package/dist/utils/calendar.js.map +1 -0
- package/dist/utils/env.d.ts +12 -0
- package/dist/utils/env.js +79 -0
- package/dist/utils/env.js.map +1 -0
- package/dist/utils/timezone.d.ts +32 -0
- package/dist/utils/timezone.js +173 -0
- package/dist/utils/timezone.js.map +1 -0
- package/dist/utils/tps-string.d.ts +12 -0
- package/dist/utils/tps-string.js +164 -0
- package/dist/utils/tps-string.js.map +1 -0
- package/package.json +20 -5
- package/src/date.ts +243 -0
- package/src/driver-manager.ts +54 -0
- package/src/drivers/chinese.ts +542 -0
- package/src/drivers/gregorian.ts +29 -27
- package/src/drivers/hijri.ts +13 -113
- package/src/drivers/holocene.ts +11 -12
- package/src/drivers/julian.ts +18 -72
- package/src/drivers/persian.ts +25 -92
- package/src/drivers/tps.ts +16 -55
- package/src/drivers/unix.ts +12 -33
- package/src/index.ts +384 -1556
- package/src/types.ts +131 -0
- package/src/uid.ts +308 -0
- package/src/utils/calendar.ts +161 -0
- package/src/utils/env.ts +88 -0
- package/src/utils/timezone.ts +182 -0
- package/src/utils/tps-string.ts +166 -0
package/src/drivers/hijri.ts
CHANGED
|
@@ -10,7 +10,9 @@
|
|
|
10
10
|
* This uses the Tabular Islamic Calendar (civil/Type II-A) algorithm
|
|
11
11
|
* based on the formulas from Meeus "Astronomical Algorithms".
|
|
12
12
|
*/
|
|
13
|
-
import { CalendarDriver, CalendarMetadata, TPSComponents
|
|
13
|
+
import { CalendarDriver, CalendarMetadata, TPSComponents } from "../types";
|
|
14
|
+
import { buildTimePart } from "../utils/tps-string";
|
|
15
|
+
import { gregorianToHijri, hijriToGregorian } from "../utils/calendar";
|
|
14
16
|
|
|
15
17
|
export class HijriDriver implements CalendarDriver {
|
|
16
18
|
readonly code = "hij";
|
|
@@ -56,15 +58,8 @@ export class HijriDriver implements CalendarDriver {
|
|
|
56
58
|
"as-Sabt",
|
|
57
59
|
];
|
|
58
60
|
|
|
59
|
-
/** Leap years in a 30-year cycle (civil / Type II-A pattern) */
|
|
60
|
-
private readonly LEAP_YEARS_IN_CYCLE = new Set([
|
|
61
|
-
2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29,
|
|
62
|
-
]);
|
|
63
|
-
|
|
64
|
-
// ── CalendarDriver interface ──────────────────────────────────────────
|
|
65
|
-
|
|
66
61
|
getComponentsFromDate(date: Date): Partial<TPSComponents> {
|
|
67
|
-
const { hy, hm, hd } =
|
|
62
|
+
const { hy, hm, hd } = gregorianToHijri(
|
|
68
63
|
date.getUTCFullYear(),
|
|
69
64
|
date.getUTCMonth() + 1,
|
|
70
65
|
date.getUTCDate(),
|
|
@@ -96,7 +91,7 @@ export class HijriDriver implements CalendarDriver {
|
|
|
96
91
|
}
|
|
97
92
|
const hm = components.month ?? 1;
|
|
98
93
|
const hd = components.day ?? 1;
|
|
99
|
-
const { gy, gm, gd } =
|
|
94
|
+
const { gy, gm, gd } = hijriToGregorian(hy, hm, hd);
|
|
100
95
|
|
|
101
96
|
return new Date(
|
|
102
97
|
Date.UTC(
|
|
@@ -113,7 +108,7 @@ export class HijriDriver implements CalendarDriver {
|
|
|
113
108
|
|
|
114
109
|
getFromDate(date: Date): string {
|
|
115
110
|
const comp = this.getComponentsFromDate(date) as TPSComponents;
|
|
116
|
-
return
|
|
111
|
+
return buildTimePart(comp);
|
|
117
112
|
}
|
|
118
113
|
|
|
119
114
|
parseDate(input: string, format?: string): Partial<TPSComponents> {
|
|
@@ -166,9 +161,8 @@ export class HijriDriver implements CalendarDriver {
|
|
|
166
161
|
fullYear = components.year ?? 0;
|
|
167
162
|
}
|
|
168
163
|
|
|
169
|
-
if (format === "short")
|
|
164
|
+
if (format === "short")
|
|
170
165
|
return `${components.day}/${pad(components.month)}/${fullYear}`;
|
|
171
|
-
}
|
|
172
166
|
if (format === "long") {
|
|
173
167
|
const mn = this.MONTH_NAMES[(components.month ?? 1) - 1];
|
|
174
168
|
return `${components.day} ${mn} ${fullYear}`;
|
|
@@ -192,7 +186,6 @@ export class HijriDriver implements CalendarDriver {
|
|
|
192
186
|
comp = input;
|
|
193
187
|
}
|
|
194
188
|
const { year, month, day } = comp;
|
|
195
|
-
// Reconstruct full year for leap check
|
|
196
189
|
let fullYear: number;
|
|
197
190
|
if (comp.millennium !== undefined) {
|
|
198
191
|
fullYear =
|
|
@@ -205,7 +198,12 @@ export class HijriDriver implements CalendarDriver {
|
|
|
205
198
|
if (fullYear < 1) return false;
|
|
206
199
|
if (!month || month < 1 || month > 12) return false;
|
|
207
200
|
if (!day || day < 1) return false;
|
|
208
|
-
|
|
201
|
+
|
|
202
|
+
// leap check (cycle of 30 years)
|
|
203
|
+
const isLeap = new Set([2, 5, 7, 10, 13, 16, 18, 21, 24, 26, 29]).has(
|
|
204
|
+
((fullYear - 1) % 30) + 1,
|
|
205
|
+
);
|
|
206
|
+
const maxDays = month === 12 && isLeap ? 30 : month % 2 === 1 ? 30 : 29;
|
|
209
207
|
return day <= maxDays;
|
|
210
208
|
}
|
|
211
209
|
|
|
@@ -221,102 +219,4 @@ export class HijriDriver implements CalendarDriver {
|
|
|
221
219
|
epochYear: 1,
|
|
222
220
|
};
|
|
223
221
|
}
|
|
224
|
-
|
|
225
|
-
// ── Internal helpers ──────────────────────────────────────────────────
|
|
226
|
-
|
|
227
|
-
private isLeapYear(year: number): boolean {
|
|
228
|
-
return this.LEAP_YEARS_IN_CYCLE.has(((year - 1) % 30) + 1);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
private daysInMonth(year: number, month: number): number {
|
|
232
|
-
if (month === 12 && this.isLeapYear(year)) return 30;
|
|
233
|
-
return month % 2 === 1 ? 30 : 29;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// ── Gregorian ↔ Hijri (Tabular algorithm from Meeus) ──────────────────
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Convert Gregorian to Hijri (Tabular Islamic Calendar).
|
|
240
|
-
* Algorithm from "Astronomical Algorithms" by Jean Meeus.
|
|
241
|
-
*/
|
|
242
|
-
private gregorianToHijri(
|
|
243
|
-
gy: number,
|
|
244
|
-
gm: number,
|
|
245
|
-
gd: number,
|
|
246
|
-
): { hy: number; hm: number; hd: number } {
|
|
247
|
-
// Step 1: Gregorian → JDN
|
|
248
|
-
const jdn = this.gregorianToJdn(gy, gm, gd);
|
|
249
|
-
// Step 2: JDN → Hijri
|
|
250
|
-
// L = JDN − 1948440 + 10632
|
|
251
|
-
const L = jdn - 1948440 + 10632;
|
|
252
|
-
// N = floor((L − 1) / 10631)
|
|
253
|
-
const N = Math.floor((L - 1) / 10631);
|
|
254
|
-
// L = L − 10631 × N + 354
|
|
255
|
-
const L2 = L - 10631 * N + 354;
|
|
256
|
-
// J = floor((10985 − L2) / 5316) × floor((50×L2) / 17719) + floor(L2 / 5670) × floor((43×L2) / 15238)
|
|
257
|
-
const J =
|
|
258
|
-
Math.floor((10985 - L2) / 5316) * Math.floor((50 * L2) / 17719) +
|
|
259
|
-
Math.floor(L2 / 5670) * Math.floor((43 * L2) / 15238);
|
|
260
|
-
// L3 = L2 − floor((30 − J) / 15) × floor((17719 × J) / 50) − floor(J / 16) × floor((15238 × J) / 43) + 29
|
|
261
|
-
const L3 =
|
|
262
|
-
L2 -
|
|
263
|
-
Math.floor((30 - J) / 15) * Math.floor((17719 * J) / 50) -
|
|
264
|
-
Math.floor(J / 16) * Math.floor((15238 * J) / 43) +
|
|
265
|
-
29;
|
|
266
|
-
const hm = Math.floor((24 * L3) / 709);
|
|
267
|
-
const hd = L3 - Math.floor((709 * hm) / 24);
|
|
268
|
-
const hy = 30 * N + J - 30;
|
|
269
|
-
return { hy, hm, hd };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Convert Hijri to Gregorian.
|
|
274
|
-
*/
|
|
275
|
-
private hijriToGregorian(
|
|
276
|
-
hy: number,
|
|
277
|
-
hm: number,
|
|
278
|
-
hd: number,
|
|
279
|
-
): { gy: number; gm: number; gd: number } {
|
|
280
|
-
// Hijri → JDN
|
|
281
|
-
const jdn =
|
|
282
|
-
Math.floor((11 * hy + 3) / 30) +
|
|
283
|
-
354 * hy +
|
|
284
|
-
30 * hm -
|
|
285
|
-
Math.floor((hm - 1) / 2) +
|
|
286
|
-
hd +
|
|
287
|
-
1948440 -
|
|
288
|
-
385;
|
|
289
|
-
// JDN → Gregorian
|
|
290
|
-
return this.jdnToGregorian(jdn);
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// ── JDN helpers ───────────────────────────────────────────────────────
|
|
294
|
-
|
|
295
|
-
private gregorianToJdn(gy: number, gm: number, gd: number): number {
|
|
296
|
-
const a = Math.floor((14 - gm) / 12);
|
|
297
|
-
const y = gy + 4800 - a;
|
|
298
|
-
const m = gm + 12 * a - 3;
|
|
299
|
-
return (
|
|
300
|
-
gd +
|
|
301
|
-
Math.floor((153 * m + 2) / 5) +
|
|
302
|
-
365 * y +
|
|
303
|
-
Math.floor(y / 4) -
|
|
304
|
-
Math.floor(y / 100) +
|
|
305
|
-
Math.floor(y / 400) -
|
|
306
|
-
32045
|
|
307
|
-
);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
private jdnToGregorian(jdn: number): { gy: number; gm: number; gd: number } {
|
|
311
|
-
const a = jdn + 32044;
|
|
312
|
-
const b = Math.floor((4 * a + 3) / 146097);
|
|
313
|
-
const c = a - Math.floor((146097 * b) / 4);
|
|
314
|
-
const d = Math.floor((4 * c + 3) / 1461);
|
|
315
|
-
const e = c - Math.floor((1461 * d) / 4);
|
|
316
|
-
const m = Math.floor((5 * e + 2) / 153);
|
|
317
|
-
const gd = e - Math.floor((153 * m + 2) / 5) + 1;
|
|
318
|
-
const gm = m + 3 - 12 * Math.floor(m / 10);
|
|
319
|
-
const gy = 100 * b + d - 4800 + Math.floor(m / 10);
|
|
320
|
-
return { gy, gm, gd };
|
|
321
|
-
}
|
|
322
222
|
}
|
package/src/drivers/holocene.ts
CHANGED
|
@@ -9,9 +9,13 @@
|
|
|
9
9
|
*
|
|
10
10
|
* This is a thin wrapper around GregorianDriver with a year offset.
|
|
11
11
|
*/
|
|
12
|
-
import { CalendarDriver, CalendarMetadata, TPSComponents
|
|
12
|
+
import { CalendarDriver, CalendarMetadata, TPSComponents } from "../types";
|
|
13
|
+
import { buildTimePart } from "../utils/tps-string";
|
|
13
14
|
import { GregorianDriver } from "./gregorian";
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Holocene (Human Era) Calendar Driver
|
|
18
|
+
*/
|
|
15
19
|
export class HoloceneDriver implements CalendarDriver {
|
|
16
20
|
readonly code = "holo";
|
|
17
21
|
readonly name = "Holocene (Human Era)";
|
|
@@ -19,8 +23,6 @@ export class HoloceneDriver implements CalendarDriver {
|
|
|
19
23
|
private readonly gregorian = new GregorianDriver();
|
|
20
24
|
private readonly YEAR_OFFSET = 10000;
|
|
21
25
|
|
|
22
|
-
// ── CalendarDriver interface ──────────────────────────────────────────
|
|
23
|
-
|
|
24
26
|
getComponentsFromDate(date: Date): Partial<TPSComponents> {
|
|
25
27
|
const greg = this.gregorian.getComponentsFromDate(date);
|
|
26
28
|
const fullYear = date.getUTCFullYear() + this.YEAR_OFFSET;
|
|
@@ -57,21 +59,20 @@ export class HoloceneDriver implements CalendarDriver {
|
|
|
57
59
|
|
|
58
60
|
getFromDate(date: Date): string {
|
|
59
61
|
const comp = this.getComponentsFromDate(date) as TPSComponents;
|
|
60
|
-
return
|
|
62
|
+
return buildTimePart(comp);
|
|
61
63
|
}
|
|
62
64
|
|
|
63
|
-
parseDate(input: string,
|
|
64
|
-
// Accept ISO-like: "12026-01-09" (Holocene year)
|
|
65
|
+
parseDate(input: string, _format?: string): Partial<TPSComponents> {
|
|
65
66
|
const m = input
|
|
66
67
|
.trim()
|
|
67
68
|
.match(
|
|
68
69
|
/^(\d{4,5})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?)?$/,
|
|
69
70
|
);
|
|
70
|
-
if (!m)
|
|
71
|
+
if (!m)
|
|
71
72
|
throw new Error(
|
|
72
73
|
`HoloceneDriver.parseDate: unsupported format "${input}"`,
|
|
73
74
|
);
|
|
74
|
-
|
|
75
|
+
|
|
75
76
|
const result: Partial<TPSComponents> = {
|
|
76
77
|
calendar: this.code,
|
|
77
78
|
year: parseInt(m[1], 10),
|
|
@@ -86,9 +87,8 @@ export class HoloceneDriver implements CalendarDriver {
|
|
|
86
87
|
return result;
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
format(components: Partial<TPSComponents>,
|
|
90
|
+
format(components: Partial<TPSComponents>, _format?: string): string {
|
|
90
91
|
const pad = (n?: number, w = 2) => String(n ?? 0).padStart(w, "0");
|
|
91
|
-
// Reconstruct full Holocene year from components
|
|
92
92
|
let holoYear: number;
|
|
93
93
|
if (components.millennium !== undefined) {
|
|
94
94
|
const m = components.millennium ?? 0;
|
|
@@ -117,7 +117,6 @@ export class HoloceneDriver implements CalendarDriver {
|
|
|
117
117
|
);
|
|
118
118
|
}
|
|
119
119
|
if (typeof input === "object") {
|
|
120
|
-
// Delegate day/month validation to Gregorian (same structure)
|
|
121
120
|
return this.gregorian.validate({
|
|
122
121
|
year: input.year,
|
|
123
122
|
month: input.month,
|
|
@@ -146,7 +145,7 @@ export class HoloceneDriver implements CalendarDriver {
|
|
|
146
145
|
],
|
|
147
146
|
isLunar: false,
|
|
148
147
|
monthsPerYear: 12,
|
|
149
|
-
epochYear: -10000,
|
|
148
|
+
epochYear: -10000,
|
|
150
149
|
};
|
|
151
150
|
}
|
|
152
151
|
}
|
package/src/drivers/julian.ts
CHANGED
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Conversion uses Julian Day Number algorithms.
|
|
11
11
|
*/
|
|
12
|
-
import { CalendarDriver, CalendarMetadata, TPSComponents
|
|
12
|
+
import { CalendarDriver, CalendarMetadata, TPSComponents } from "../types";
|
|
13
|
+
import { buildTimePart } from "../utils/tps-string";
|
|
14
|
+
import {
|
|
15
|
+
gregorianToJdn,
|
|
16
|
+
jdnToGregorian,
|
|
17
|
+
julianToJdn,
|
|
18
|
+
jdnToJulian,
|
|
19
|
+
} from "../utils/calendar";
|
|
13
20
|
|
|
14
21
|
export class JulianDriver implements CalendarDriver {
|
|
15
22
|
readonly code = "jul";
|
|
@@ -49,15 +56,13 @@ export class JulianDriver implements CalendarDriver {
|
|
|
49
56
|
31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
|
|
50
57
|
];
|
|
51
58
|
|
|
52
|
-
// ── CalendarDriver interface ──────────────────────────────────────────
|
|
53
|
-
|
|
54
59
|
getComponentsFromDate(date: Date): Partial<TPSComponents> {
|
|
55
|
-
const jdn =
|
|
60
|
+
const jdn = gregorianToJdn(
|
|
56
61
|
date.getUTCFullYear(),
|
|
57
62
|
date.getUTCMonth() + 1,
|
|
58
63
|
date.getUTCDate(),
|
|
59
64
|
);
|
|
60
|
-
const { jy, jm, jd } =
|
|
65
|
+
const { jy, jm, jd } = jdnToJulian(jdn);
|
|
61
66
|
|
|
62
67
|
return {
|
|
63
68
|
calendar: this.code,
|
|
@@ -85,8 +90,8 @@ export class JulianDriver implements CalendarDriver {
|
|
|
85
90
|
}
|
|
86
91
|
const jm = components.month ?? 1;
|
|
87
92
|
const jd = components.day ?? 1;
|
|
88
|
-
const jdn =
|
|
89
|
-
const { gy, gm, gd } =
|
|
93
|
+
const jdn = julianToJdn(fullYear, jm, jd);
|
|
94
|
+
const { gy, gm, gd } = jdnToGregorian(jdn);
|
|
90
95
|
|
|
91
96
|
return new Date(
|
|
92
97
|
Date.UTC(
|
|
@@ -103,17 +108,17 @@ export class JulianDriver implements CalendarDriver {
|
|
|
103
108
|
|
|
104
109
|
getFromDate(date: Date): string {
|
|
105
110
|
const comp = this.getComponentsFromDate(date) as TPSComponents;
|
|
106
|
-
return
|
|
111
|
+
return buildTimePart(comp);
|
|
107
112
|
}
|
|
108
113
|
|
|
109
|
-
parseDate(input: string,
|
|
114
|
+
parseDate(input: string, _format?: string): Partial<TPSComponents> {
|
|
110
115
|
const trimmed = input.trim();
|
|
111
116
|
const m = trimmed.match(
|
|
112
117
|
/^(\d{4})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?)?$/,
|
|
113
118
|
);
|
|
114
|
-
if (!m)
|
|
119
|
+
if (!m)
|
|
115
120
|
throw new Error(`JulianDriver.parseDate: unsupported format "${input}"`);
|
|
116
|
-
|
|
121
|
+
|
|
117
122
|
const fullYear = parseInt(m[1], 10);
|
|
118
123
|
const result: Partial<TPSComponents> = {
|
|
119
124
|
calendar: this.code,
|
|
@@ -131,7 +136,7 @@ export class JulianDriver implements CalendarDriver {
|
|
|
131
136
|
return result;
|
|
132
137
|
}
|
|
133
138
|
|
|
134
|
-
format(components: Partial<TPSComponents>,
|
|
139
|
+
format(components: Partial<TPSComponents>, _format?: string): string {
|
|
135
140
|
const pad = (n?: number, w = 2) => String(n ?? 0).padStart(w, "0");
|
|
136
141
|
let fullYear: number;
|
|
137
142
|
if (components.millennium !== undefined) {
|
|
@@ -164,7 +169,6 @@ export class JulianDriver implements CalendarDriver {
|
|
|
164
169
|
);
|
|
165
170
|
}
|
|
166
171
|
if (typeof input === "object") {
|
|
167
|
-
// Reconstruct full year for leap check
|
|
168
172
|
let fullYear: number;
|
|
169
173
|
if (input.millennium !== undefined) {
|
|
170
174
|
fullYear =
|
|
@@ -178,7 +182,7 @@ export class JulianDriver implements CalendarDriver {
|
|
|
178
182
|
if (month === undefined || day === undefined) return false;
|
|
179
183
|
if (month < 1 || month > 12 || day < 1) return false;
|
|
180
184
|
let maxDay = this.DAYS_IN_MONTH[month - 1];
|
|
181
|
-
if (month === 2 &&
|
|
185
|
+
if (month === 2 && fullYear % 4 === 0) maxDay = 29;
|
|
182
186
|
return day <= maxDay;
|
|
183
187
|
}
|
|
184
188
|
return false;
|
|
@@ -194,62 +198,4 @@ export class JulianDriver implements CalendarDriver {
|
|
|
194
198
|
epochYear: 1,
|
|
195
199
|
};
|
|
196
200
|
}
|
|
197
|
-
|
|
198
|
-
// ── Internal helpers ──────────────────────────────────────────────────
|
|
199
|
-
|
|
200
|
-
/** Julian leap year: every 4 years, no century exception */
|
|
201
|
-
private isLeapYear(year: number): boolean {
|
|
202
|
-
return year % 4 === 0;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// ── JDN algorithms ────────────────────────────────────────────────────
|
|
206
|
-
|
|
207
|
-
private julianToJdn(jy: number, jm: number, jd: number): number {
|
|
208
|
-
const a = Math.floor((14 - jm) / 12);
|
|
209
|
-
const y = jy + 4800 - a;
|
|
210
|
-
const m = jm + 12 * a - 3;
|
|
211
|
-
return (
|
|
212
|
-
jd + Math.floor((153 * m + 2) / 5) + 365 * y + Math.floor(y / 4) - 32083
|
|
213
|
-
);
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
private jdnToJulian(jdn: number): { jy: number; jm: number; jd: number } {
|
|
217
|
-
const c = jdn + 32082;
|
|
218
|
-
const d = Math.floor((4 * c + 3) / 1461);
|
|
219
|
-
const e = c - Math.floor((1461 * d) / 4);
|
|
220
|
-
const m = Math.floor((5 * e + 2) / 153);
|
|
221
|
-
const jd = e - Math.floor((153 * m + 2) / 5) + 1;
|
|
222
|
-
const jm = m + 3 - 12 * Math.floor(m / 10);
|
|
223
|
-
const jy = d - 4800 + Math.floor(m / 10);
|
|
224
|
-
return { jy, jm, jd };
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** Gregorian → JDN (for converting incoming Gregorian Date) */
|
|
228
|
-
private gregorianToJdn(gy: number, gm: number, gd: number): number {
|
|
229
|
-
const a = Math.floor((14 - gm) / 12);
|
|
230
|
-
const y = gy + 4800 - a;
|
|
231
|
-
const m = gm + 12 * a - 3;
|
|
232
|
-
return (
|
|
233
|
-
gd +
|
|
234
|
-
Math.floor((153 * m + 2) / 5) +
|
|
235
|
-
365 * y +
|
|
236
|
-
Math.floor(y / 4) -
|
|
237
|
-
Math.floor(y / 100) +
|
|
238
|
-
Math.floor(y / 400) -
|
|
239
|
-
32045
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private jdnToGregorian(jdn: number): { gy: number; gm: number; gd: number } {
|
|
244
|
-
const a = jdn + 32044;
|
|
245
|
-
const b = Math.floor((4 * a + 3) / 146097);
|
|
246
|
-
const c = a - Math.floor((146097 * b) / 4);
|
|
247
|
-
const d = Math.floor((4 * c + 3) / 1461);
|
|
248
|
-
const e = c - Math.floor((1461 * d) / 4);
|
|
249
|
-
const m = Math.floor((5 * e + 2) / 153);
|
|
250
|
-
const gd = e - Math.floor((153 * m + 2) / 5) + 1;
|
|
251
|
-
const gm = m + 3 - 12 * Math.floor(m / 10);
|
|
252
|
-
const gy = 100 * b + d - 4800 + Math.floor(m / 10);
|
|
253
|
-
return { gy, gm, gd };
|
|
254
|
-
}
|
|
255
201
|
}
|
package/src/drivers/persian.ts
CHANGED
|
@@ -9,7 +9,14 @@
|
|
|
9
9
|
*
|
|
10
10
|
* Conversion uses Julian Day Number algorithms based on jalaali-js.
|
|
11
11
|
*/
|
|
12
|
-
import { CalendarDriver, CalendarMetadata, TPSComponents
|
|
12
|
+
import { CalendarDriver, CalendarMetadata, TPSComponents } from "../types";
|
|
13
|
+
import { buildTimePart } from "../utils/tps-string";
|
|
14
|
+
import {
|
|
15
|
+
gregorianToJdn,
|
|
16
|
+
jdnToGregorian,
|
|
17
|
+
persianToJdn,
|
|
18
|
+
jdnToPersian,
|
|
19
|
+
} from "../utils/calendar";
|
|
13
20
|
|
|
14
21
|
export class PersianDriver implements CalendarDriver {
|
|
15
22
|
readonly code = "per";
|
|
@@ -60,15 +67,13 @@ export class PersianDriver implements CalendarDriver {
|
|
|
60
67
|
31, 31, 31, 31, 31, 31, 30, 30, 30, 30, 30, 29,
|
|
61
68
|
];
|
|
62
69
|
|
|
63
|
-
// ── CalendarDriver interface ──────────────────────────────────────────
|
|
64
|
-
|
|
65
70
|
getComponentsFromDate(date: Date): Partial<TPSComponents> {
|
|
66
|
-
const jdn =
|
|
71
|
+
const jdn = gregorianToJdn(
|
|
67
72
|
date.getUTCFullYear(),
|
|
68
73
|
date.getUTCMonth() + 1,
|
|
69
74
|
date.getUTCDate(),
|
|
70
75
|
);
|
|
71
|
-
const { jy, jm, jd } =
|
|
76
|
+
const { jy, jm, jd } = jdnToPersian(jdn);
|
|
72
77
|
|
|
73
78
|
return {
|
|
74
79
|
calendar: this.code,
|
|
@@ -85,7 +90,6 @@ export class PersianDriver implements CalendarDriver {
|
|
|
85
90
|
}
|
|
86
91
|
|
|
87
92
|
getDateFromComponents(components: Partial<TPSComponents>): Date {
|
|
88
|
-
// Reconstruct full Persian year from millennium/century/year if available
|
|
89
93
|
let jy: number;
|
|
90
94
|
if (components.millennium !== undefined) {
|
|
91
95
|
const m = components.millennium ?? 0;
|
|
@@ -97,8 +101,8 @@ export class PersianDriver implements CalendarDriver {
|
|
|
97
101
|
}
|
|
98
102
|
const jm = components.month ?? 1;
|
|
99
103
|
const jd = components.day ?? 1;
|
|
100
|
-
const jdn =
|
|
101
|
-
const { gy, gm, gd } =
|
|
104
|
+
const jdn = persianToJdn(jy, jm, jd);
|
|
105
|
+
const { gy, gm, gd } = jdnToGregorian(jdn);
|
|
102
106
|
|
|
103
107
|
return new Date(
|
|
104
108
|
Date.UTC(
|
|
@@ -115,13 +119,12 @@ export class PersianDriver implements CalendarDriver {
|
|
|
115
119
|
|
|
116
120
|
getFromDate(date: Date): string {
|
|
117
121
|
const comp = this.getComponentsFromDate(date) as TPSComponents;
|
|
118
|
-
return
|
|
122
|
+
return buildTimePart(comp);
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
parseDate(input: string, format?: string): Partial<TPSComponents> {
|
|
122
126
|
const trimmed = input.trim();
|
|
123
127
|
|
|
124
|
-
// Short format: 19/10/1404 or 1404/10/19
|
|
125
128
|
if (
|
|
126
129
|
format === "short" ||
|
|
127
130
|
(trimmed.includes("/") && trimmed.split("/")[0].length <= 2)
|
|
@@ -143,7 +146,6 @@ export class PersianDriver implements CalendarDriver {
|
|
|
143
146
|
};
|
|
144
147
|
}
|
|
145
148
|
|
|
146
|
-
// ISO-like: 1404-10-19 [HH:MM:SS]
|
|
147
149
|
const segments = trimmed.split(/[\s,T]+/);
|
|
148
150
|
const [parsedYear, month, day] = segments[0].split(/[-/]/).map(Number);
|
|
149
151
|
const result: Partial<TPSComponents> = { calendar: this.code };
|
|
@@ -165,7 +167,6 @@ export class PersianDriver implements CalendarDriver {
|
|
|
165
167
|
|
|
166
168
|
format(components: Partial<TPSComponents>, format?: string): string {
|
|
167
169
|
const pad = (n?: number) => String(n ?? 0).padStart(2, "0");
|
|
168
|
-
// Reconstruct full year from millennium/century/year
|
|
169
170
|
let fullYear: number;
|
|
170
171
|
if (components.millennium !== undefined) {
|
|
171
172
|
const m = components.millennium ?? 0;
|
|
@@ -176,9 +177,8 @@ export class PersianDriver implements CalendarDriver {
|
|
|
176
177
|
fullYear = components.year ?? 0;
|
|
177
178
|
}
|
|
178
179
|
|
|
179
|
-
if (format === "short")
|
|
180
|
+
if (format === "short")
|
|
180
181
|
return `${components.day}/${pad(components.month)}/${fullYear}`;
|
|
181
|
-
}
|
|
182
182
|
if (format === "long") {
|
|
183
183
|
const mn = this.MONTH_NAMES[(components.month ?? 1) - 1];
|
|
184
184
|
return `${components.day} ${mn} ${fullYear}`;
|
|
@@ -202,11 +202,17 @@ export class PersianDriver implements CalendarDriver {
|
|
|
202
202
|
comp = input;
|
|
203
203
|
}
|
|
204
204
|
const { year, month, day } = comp;
|
|
205
|
-
if (
|
|
206
|
-
if (
|
|
207
|
-
if (
|
|
208
|
-
|
|
209
|
-
|
|
205
|
+
if (year === undefined || year < 1) return false;
|
|
206
|
+
if (month === undefined || month < 1 || month > 12) return false;
|
|
207
|
+
if (day === undefined || day < 1) return false;
|
|
208
|
+
|
|
209
|
+
// leap check
|
|
210
|
+
const leapYears = [1, 5, 9, 13, 17, 22, 26, 30];
|
|
211
|
+
const cycle = ((year - 1) % 33) + 1;
|
|
212
|
+
const isLeap = leapYears.includes(cycle);
|
|
213
|
+
|
|
214
|
+
let max = this.DAYS_IN_MONTH[month - 1];
|
|
215
|
+
if (month === 12 && isLeap) max = 30;
|
|
210
216
|
return day <= max;
|
|
211
217
|
}
|
|
212
218
|
|
|
@@ -222,77 +228,4 @@ export class PersianDriver implements CalendarDriver {
|
|
|
222
228
|
epochYear: 622,
|
|
223
229
|
};
|
|
224
230
|
}
|
|
225
|
-
|
|
226
|
-
// ── Internal: leap year (33-year cycle) ───────────────────────────────
|
|
227
|
-
|
|
228
|
-
private isLeapYear(year: number): boolean {
|
|
229
|
-
const leapYears = [1, 5, 9, 13, 17, 22, 26, 30];
|
|
230
|
-
const cycle = ((year - 1) % 33) + 1;
|
|
231
|
-
return leapYears.includes(cycle);
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
// ── Internal: Julian Day Number algorithms ────────────────────────────
|
|
235
|
-
|
|
236
|
-
private gregorianToJdn(gy: number, gm: number, gd: number): number {
|
|
237
|
-
const a = Math.floor((14 - gm) / 12);
|
|
238
|
-
const y = gy + 4800 - a;
|
|
239
|
-
const m = gm + 12 * a - 3;
|
|
240
|
-
return (
|
|
241
|
-
gd +
|
|
242
|
-
Math.floor((153 * m + 2) / 5) +
|
|
243
|
-
365 * y +
|
|
244
|
-
Math.floor(y / 4) -
|
|
245
|
-
Math.floor(y / 100) +
|
|
246
|
-
Math.floor(y / 400) -
|
|
247
|
-
32045
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
private jdnToGregorian(jdn: number): { gy: number; gm: number; gd: number } {
|
|
252
|
-
const a = jdn + 32044;
|
|
253
|
-
const b = Math.floor((4 * a + 3) / 146097);
|
|
254
|
-
const c = a - Math.floor((146097 * b) / 4);
|
|
255
|
-
const d = Math.floor((4 * c + 3) / 1461);
|
|
256
|
-
const e = c - Math.floor((1461 * d) / 4);
|
|
257
|
-
const m = Math.floor((5 * e + 2) / 153);
|
|
258
|
-
const gd = e - Math.floor((153 * m + 2) / 5) + 1;
|
|
259
|
-
const gm = m + 3 - 12 * Math.floor(m / 10);
|
|
260
|
-
const gy = 100 * b + d - 4800 + Math.floor(m / 10);
|
|
261
|
-
return { gy, gm, gd };
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
private persianToJdn(jy: number, jm: number, jd: number): number {
|
|
265
|
-
const EPOCH = 1948320;
|
|
266
|
-
const epbase = jy - (jy >= 0 ? 474 : 473);
|
|
267
|
-
const epyear = 474 + (epbase % 2820);
|
|
268
|
-
return (
|
|
269
|
-
jd +
|
|
270
|
-
(jm <= 7 ? (jm - 1) * 31 : (jm - 1) * 30 + 6) +
|
|
271
|
-
Math.floor((epyear * 682 - 110) / 2816) +
|
|
272
|
-
(epyear - 1) * 365 +
|
|
273
|
-
Math.floor(epbase / 2820) * 1029983 +
|
|
274
|
-
EPOCH -
|
|
275
|
-
1
|
|
276
|
-
);
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
private jdnToPersian(jdn: number): { jy: number; jm: number; jd: number } {
|
|
280
|
-
const depoch = jdn - this.persianToJdn(475, 1, 1);
|
|
281
|
-
const cycle = Math.floor(depoch / 1029983);
|
|
282
|
-
const cyear = depoch % 1029983;
|
|
283
|
-
let ycycle: number;
|
|
284
|
-
if (cyear === 1029982) {
|
|
285
|
-
ycycle = 2820;
|
|
286
|
-
} else {
|
|
287
|
-
const aux1 = Math.floor(cyear / 366);
|
|
288
|
-
const aux2 = cyear % 366;
|
|
289
|
-
ycycle =
|
|
290
|
-
Math.floor((2134 * aux1 + 2816 * aux2 + 2815) / 1028522) + aux1 + 1;
|
|
291
|
-
}
|
|
292
|
-
const jy = ycycle + 2820 * cycle + 474;
|
|
293
|
-
const yday = jdn - this.persianToJdn(jy, 1, 1) + 1;
|
|
294
|
-
const jm = yday <= 186 ? Math.ceil(yday / 31) : Math.ceil((yday - 6) / 30);
|
|
295
|
-
const jd = jdn - this.persianToJdn(jy, jm, 1) + 1;
|
|
296
|
-
return { jy: jy <= 0 ? jy - 1 : jy, jm, jd };
|
|
297
|
-
}
|
|
298
231
|
}
|