@nextera.one/tps-standard 0.5.2 → 0.5.3
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/dist/drivers/gregorian.d.ts +1 -1
- package/dist/drivers/gregorian.js +50 -35
- package/dist/drivers/gregorian.js.map +1 -1
- package/dist/drivers/hijri.d.ts +42 -0
- package/dist/drivers/hijri.js +281 -0
- package/dist/drivers/hijri.js.map +1 -0
- package/dist/drivers/holocene.d.ts +25 -0
- package/dist/drivers/holocene.js +132 -0
- package/dist/drivers/holocene.js.map +1 -0
- package/dist/drivers/julian.d.ts +33 -0
- package/dist/drivers/julian.js +225 -0
- package/dist/drivers/julian.js.map +1 -0
- package/dist/drivers/persian.d.ts +33 -0
- package/dist/drivers/persian.js +269 -0
- package/dist/drivers/persian.js.map +1 -0
- package/dist/drivers/tps.d.ts +4 -4
- package/dist/drivers/tps.js +58 -44
- package/dist/drivers/tps.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +37 -63
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
- package/src/drivers/gregorian.ts +71 -38
- package/src/drivers/hijri.ts +322 -0
- package/src/drivers/holocene.ts +152 -0
- package/src/drivers/julian.ts +255 -0
- package/src/drivers/persian.ts +298 -0
- package/src/drivers/tps.ts +82 -51
- package/src/index.ts +38 -78
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hijri (Islamic) Calendar Driver — Tabular / Arithmetic variant
|
|
3
|
+
*
|
|
4
|
+
* Calendar characteristics:
|
|
5
|
+
* - Lunar calendar with 12 months of alternating 30/29 days
|
|
6
|
+
* - 30-year cycle with 11 leap years (Dhul Hijjah gains a day)
|
|
7
|
+
* - Year 1 AH ≈ 16 July 622 CE (Julian)
|
|
8
|
+
* - Average year ≈ 354.36667 days
|
|
9
|
+
*
|
|
10
|
+
* This uses the Tabular Islamic Calendar (civil/Type II-A) algorithm
|
|
11
|
+
* based on the formulas from Meeus "Astronomical Algorithms".
|
|
12
|
+
*/
|
|
13
|
+
import { CalendarDriver, CalendarMetadata, TPSComponents, TPS } from "../index";
|
|
14
|
+
|
|
15
|
+
export class HijriDriver implements CalendarDriver {
|
|
16
|
+
readonly code = "hij";
|
|
17
|
+
readonly name = "Hijri (Islamic Calendar)";
|
|
18
|
+
|
|
19
|
+
private readonly MONTH_NAMES = [
|
|
20
|
+
"Muharram",
|
|
21
|
+
"Safar",
|
|
22
|
+
"Rabi' al-Awwal",
|
|
23
|
+
"Rabi' al-Thani",
|
|
24
|
+
"Jumada al-Ula",
|
|
25
|
+
"Jumada al-Thani",
|
|
26
|
+
"Rajab",
|
|
27
|
+
"Sha'ban",
|
|
28
|
+
"Ramadan",
|
|
29
|
+
"Shawwal",
|
|
30
|
+
"Dhu al-Qi'dah",
|
|
31
|
+
"Dhu al-Hijjah",
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
private readonly MONTH_NAMES_SHORT = [
|
|
35
|
+
"Muh",
|
|
36
|
+
"Saf",
|
|
37
|
+
"Rab I",
|
|
38
|
+
"Rab II",
|
|
39
|
+
"Jum I",
|
|
40
|
+
"Jum II",
|
|
41
|
+
"Raj",
|
|
42
|
+
"Sha",
|
|
43
|
+
"Ram",
|
|
44
|
+
"Shaw",
|
|
45
|
+
"Dhu Q",
|
|
46
|
+
"Dhu H",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
private readonly DAY_NAMES = [
|
|
50
|
+
"al-Ahad",
|
|
51
|
+
"al-Ithnayn",
|
|
52
|
+
"ath-Thulatha",
|
|
53
|
+
"al-Arbi'a",
|
|
54
|
+
"al-Khamis",
|
|
55
|
+
"al-Jumu'ah",
|
|
56
|
+
"as-Sabt",
|
|
57
|
+
];
|
|
58
|
+
|
|
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
|
+
getComponentsFromDate(date: Date): Partial<TPSComponents> {
|
|
67
|
+
const { hy, hm, hd } = this.gregorianToHijri(
|
|
68
|
+
date.getUTCFullYear(),
|
|
69
|
+
date.getUTCMonth() + 1,
|
|
70
|
+
date.getUTCDate(),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
calendar: this.code,
|
|
75
|
+
millennium: Math.floor(hy / 1000) + 1,
|
|
76
|
+
century: Math.floor((hy % 1000) / 100) + 1,
|
|
77
|
+
year: hy % 100,
|
|
78
|
+
month: hm,
|
|
79
|
+
day: hd,
|
|
80
|
+
hour: date.getUTCHours(),
|
|
81
|
+
minute: date.getUTCMinutes(),
|
|
82
|
+
second: date.getUTCSeconds(),
|
|
83
|
+
millisecond: date.getUTCMilliseconds(),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
getDateFromComponents(components: Partial<TPSComponents>): Date {
|
|
88
|
+
let hy: number;
|
|
89
|
+
if (components.millennium !== undefined) {
|
|
90
|
+
const m = components.millennium ?? 0;
|
|
91
|
+
const c = components.century ?? 1;
|
|
92
|
+
const y = components.year ?? 0;
|
|
93
|
+
hy = (m - 1) * 1000 + (c - 1) * 100 + y;
|
|
94
|
+
} else {
|
|
95
|
+
hy = components.year ?? 1;
|
|
96
|
+
}
|
|
97
|
+
const hm = components.month ?? 1;
|
|
98
|
+
const hd = components.day ?? 1;
|
|
99
|
+
const { gy, gm, gd } = this.hijriToGregorian(hy, hm, hd);
|
|
100
|
+
|
|
101
|
+
return new Date(
|
|
102
|
+
Date.UTC(
|
|
103
|
+
gy,
|
|
104
|
+
gm - 1,
|
|
105
|
+
gd,
|
|
106
|
+
components.hour ?? 0,
|
|
107
|
+
components.minute ?? 0,
|
|
108
|
+
Math.floor(components.second ?? 0),
|
|
109
|
+
components.millisecond ?? 0,
|
|
110
|
+
),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
getFromDate(date: Date): string {
|
|
115
|
+
const comp = this.getComponentsFromDate(date) as TPSComponents;
|
|
116
|
+
return TPS.buildTimePart(comp);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
parseDate(input: string, format?: string): Partial<TPSComponents> {
|
|
120
|
+
const trimmed = input.trim();
|
|
121
|
+
|
|
122
|
+
if (
|
|
123
|
+
format === "short" ||
|
|
124
|
+
(trimmed.includes("/") && trimmed.split("/")[0].length <= 2)
|
|
125
|
+
) {
|
|
126
|
+
const [day, month, year] = trimmed.split("/").map(Number);
|
|
127
|
+
return {
|
|
128
|
+
calendar: this.code,
|
|
129
|
+
millennium: Math.floor(year / 1000) + 1,
|
|
130
|
+
century: Math.floor((year % 1000) / 100) + 1,
|
|
131
|
+
year: year % 100,
|
|
132
|
+
month,
|
|
133
|
+
day,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const segments = trimmed.split(/[\s,T]+/);
|
|
138
|
+
const [fullYear, month, day] = segments[0].split("-").map(Number);
|
|
139
|
+
const result: Partial<TPSComponents> = {
|
|
140
|
+
calendar: this.code,
|
|
141
|
+
millennium: Math.floor(fullYear / 1000) + 1,
|
|
142
|
+
century: Math.floor((fullYear % 1000) / 100) + 1,
|
|
143
|
+
year: fullYear % 100,
|
|
144
|
+
month,
|
|
145
|
+
day,
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
if (segments[1]) {
|
|
149
|
+
const [h, m, s] = segments[1].split(":").map(Number);
|
|
150
|
+
result.hour = h ?? 0;
|
|
151
|
+
result.minute = m ?? 0;
|
|
152
|
+
result.second = s ?? 0;
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
format(components: Partial<TPSComponents>, format?: string): string {
|
|
158
|
+
const pad = (n?: number) => String(n ?? 0).padStart(2, "0");
|
|
159
|
+
let fullYear: number;
|
|
160
|
+
if (components.millennium !== undefined) {
|
|
161
|
+
const m = components.millennium ?? 0;
|
|
162
|
+
const c = components.century ?? 1;
|
|
163
|
+
const y = components.year ?? 0;
|
|
164
|
+
fullYear = (m - 1) * 1000 + (c - 1) * 100 + y;
|
|
165
|
+
} else {
|
|
166
|
+
fullYear = components.year ?? 0;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (format === "short") {
|
|
170
|
+
return `${components.day}/${pad(components.month)}/${fullYear}`;
|
|
171
|
+
}
|
|
172
|
+
if (format === "long") {
|
|
173
|
+
const mn = this.MONTH_NAMES[(components.month ?? 1) - 1];
|
|
174
|
+
return `${components.day} ${mn} ${fullYear}`;
|
|
175
|
+
}
|
|
176
|
+
let out = `${fullYear}-${pad(components.month)}-${pad(components.day)}`;
|
|
177
|
+
if (components.hour !== undefined) {
|
|
178
|
+
out += ` ${pad(components.hour)}:${pad(components.minute)}:${pad(Math.floor(components.second ?? 0))}`;
|
|
179
|
+
}
|
|
180
|
+
return out;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
validate(input: string | Partial<TPSComponents>): boolean {
|
|
184
|
+
let comp: Partial<TPSComponents>;
|
|
185
|
+
if (typeof input === "string") {
|
|
186
|
+
try {
|
|
187
|
+
comp = this.parseDate(input);
|
|
188
|
+
} catch {
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
} else {
|
|
192
|
+
comp = input;
|
|
193
|
+
}
|
|
194
|
+
const { year, month, day } = comp;
|
|
195
|
+
// Reconstruct full year for leap check
|
|
196
|
+
let fullYear: number;
|
|
197
|
+
if (comp.millennium !== undefined) {
|
|
198
|
+
fullYear =
|
|
199
|
+
((comp.millennium ?? 0) - 1) * 1000 +
|
|
200
|
+
((comp.century ?? 1) - 1) * 100 +
|
|
201
|
+
(year ?? 0);
|
|
202
|
+
} else {
|
|
203
|
+
fullYear = year ?? 0;
|
|
204
|
+
}
|
|
205
|
+
if (fullYear < 1) return false;
|
|
206
|
+
if (!month || month < 1 || month > 12) return false;
|
|
207
|
+
if (!day || day < 1) return false;
|
|
208
|
+
const maxDays = this.daysInMonth(fullYear, month);
|
|
209
|
+
return day <= maxDays;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
getMetadata(): CalendarMetadata {
|
|
213
|
+
return {
|
|
214
|
+
name: "Hijri (Islamic Calendar)",
|
|
215
|
+
monthNames: this.MONTH_NAMES,
|
|
216
|
+
monthNamesShort: this.MONTH_NAMES_SHORT,
|
|
217
|
+
dayNames: this.DAY_NAMES,
|
|
218
|
+
dayNamesShort: this.DAY_NAMES.map((d) => d.slice(0, 3)),
|
|
219
|
+
isLunar: true,
|
|
220
|
+
monthsPerYear: 12,
|
|
221
|
+
epochYear: 1,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
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
|
+
}
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Holocene (Human Era) Calendar Driver
|
|
3
|
+
*
|
|
4
|
+
* Calendar characteristics:
|
|
5
|
+
* - Adds 10,000 to the Gregorian year (year 1 CE = 10,001 HE)
|
|
6
|
+
* - Same months, days, and leap year rules as Gregorian
|
|
7
|
+
* - Proposed by Cesare Emiliani in 1993 to encompass all of human history
|
|
8
|
+
* - Also called Human Era (HE) calendar
|
|
9
|
+
*
|
|
10
|
+
* This is a thin wrapper around GregorianDriver with a year offset.
|
|
11
|
+
*/
|
|
12
|
+
import { CalendarDriver, CalendarMetadata, TPSComponents, TPS } from "../index";
|
|
13
|
+
import { GregorianDriver } from "./gregorian";
|
|
14
|
+
|
|
15
|
+
export class HoloceneDriver implements CalendarDriver {
|
|
16
|
+
readonly code = "holo";
|
|
17
|
+
readonly name = "Holocene (Human Era)";
|
|
18
|
+
|
|
19
|
+
private readonly gregorian = new GregorianDriver();
|
|
20
|
+
private readonly YEAR_OFFSET = 10000;
|
|
21
|
+
|
|
22
|
+
// ── CalendarDriver interface ──────────────────────────────────────────
|
|
23
|
+
|
|
24
|
+
getComponentsFromDate(date: Date): Partial<TPSComponents> {
|
|
25
|
+
const greg = this.gregorian.getComponentsFromDate(date);
|
|
26
|
+
const fullYear = date.getUTCFullYear() + this.YEAR_OFFSET;
|
|
27
|
+
|
|
28
|
+
return {
|
|
29
|
+
...greg,
|
|
30
|
+
calendar: this.code,
|
|
31
|
+
millennium: Math.floor(fullYear / 1000) + 1,
|
|
32
|
+
century: Math.floor((fullYear % 1000) / 100) + 1,
|
|
33
|
+
year: fullYear % 100,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
getDateFromComponents(components: Partial<TPSComponents>): Date {
|
|
38
|
+
const m = components.millennium ?? 0;
|
|
39
|
+
const c = components.century ?? 1;
|
|
40
|
+
const y = components.year ?? 0;
|
|
41
|
+
const holoYear = (m - 1) * 1000 + (c - 1) * 100 + y;
|
|
42
|
+
const gregYear = holoYear - this.YEAR_OFFSET;
|
|
43
|
+
|
|
44
|
+
return new Date(
|
|
45
|
+
Date.UTC(
|
|
46
|
+
gregYear,
|
|
47
|
+
(components.month ?? 1) - 1,
|
|
48
|
+
components.day ?? 1,
|
|
49
|
+
components.hour ?? 0,
|
|
50
|
+
components.minute ?? 0,
|
|
51
|
+
Math.floor(components.second ?? 0),
|
|
52
|
+
components.millisecond ??
|
|
53
|
+
Math.round(((components.second ?? 0) % 1) * 1000),
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getFromDate(date: Date): string {
|
|
59
|
+
const comp = this.getComponentsFromDate(date) as TPSComponents;
|
|
60
|
+
return TPS.buildTimePart(comp);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
parseDate(input: string, format?: string): Partial<TPSComponents> {
|
|
64
|
+
// Accept ISO-like: "12026-01-09" (Holocene year)
|
|
65
|
+
const m = input
|
|
66
|
+
.trim()
|
|
67
|
+
.match(
|
|
68
|
+
/^(\d{4,5})-(\d{2})-(\d{2})(?:[ T](\d{2}):(\d{2}):(\d{2})(?:\.(\d+))?)?$/,
|
|
69
|
+
);
|
|
70
|
+
if (!m) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`HoloceneDriver.parseDate: unsupported format "${input}"`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const result: Partial<TPSComponents> = {
|
|
76
|
+
calendar: this.code,
|
|
77
|
+
year: parseInt(m[1], 10),
|
|
78
|
+
month: parseInt(m[2], 10),
|
|
79
|
+
day: parseInt(m[3], 10),
|
|
80
|
+
};
|
|
81
|
+
if (m[4] !== undefined) result.hour = parseInt(m[4], 10);
|
|
82
|
+
if (m[5] !== undefined) result.minute = parseInt(m[5], 10);
|
|
83
|
+
if (m[6] !== undefined) result.second = parseInt(m[6], 10);
|
|
84
|
+
if (m[7] !== undefined)
|
|
85
|
+
result.millisecond = parseInt((m[7] + "000").slice(0, 3), 10);
|
|
86
|
+
return result;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
format(components: Partial<TPSComponents>, format?: string): string {
|
|
90
|
+
const pad = (n?: number, w = 2) => String(n ?? 0).padStart(w, "0");
|
|
91
|
+
// Reconstruct full Holocene year from components
|
|
92
|
+
let holoYear: number;
|
|
93
|
+
if (components.millennium !== undefined) {
|
|
94
|
+
const m = components.millennium ?? 0;
|
|
95
|
+
const c = components.century ?? 1;
|
|
96
|
+
const y = components.year ?? 0;
|
|
97
|
+
holoYear = (m - 1) * 1000 + (c - 1) * 100 + y;
|
|
98
|
+
} else {
|
|
99
|
+
holoYear = components.year ?? 0;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
let out = `${String(holoYear).padStart(5, "0")}-${pad(components.month)}-${pad(components.day)}`;
|
|
103
|
+
if (
|
|
104
|
+
components.hour !== undefined ||
|
|
105
|
+
components.minute !== undefined ||
|
|
106
|
+
components.second !== undefined
|
|
107
|
+
) {
|
|
108
|
+
out += `T${pad(components.hour)}:${pad(components.minute)}:${pad(Math.floor(components.second ?? 0))}`;
|
|
109
|
+
}
|
|
110
|
+
return out;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
validate(input: string | Partial<TPSComponents>): boolean {
|
|
114
|
+
if (typeof input === "string") {
|
|
115
|
+
return /^\d{4,5}-\d{2}-\d{2}(?:[ T]\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?)?$/.test(
|
|
116
|
+
input.trim(),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (typeof input === "object") {
|
|
120
|
+
// Delegate day/month validation to Gregorian (same structure)
|
|
121
|
+
return this.gregorian.validate({
|
|
122
|
+
year: input.year,
|
|
123
|
+
month: input.month,
|
|
124
|
+
day: input.day,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
getMetadata(): CalendarMetadata {
|
|
131
|
+
return {
|
|
132
|
+
name: "Holocene (Human Era)",
|
|
133
|
+
monthNames: [
|
|
134
|
+
"January",
|
|
135
|
+
"February",
|
|
136
|
+
"March",
|
|
137
|
+
"April",
|
|
138
|
+
"May",
|
|
139
|
+
"June",
|
|
140
|
+
"July",
|
|
141
|
+
"August",
|
|
142
|
+
"September",
|
|
143
|
+
"October",
|
|
144
|
+
"November",
|
|
145
|
+
"December",
|
|
146
|
+
],
|
|
147
|
+
isLunar: false,
|
|
148
|
+
monthsPerYear: 12,
|
|
149
|
+
epochYear: -10000, // 10001 BCE
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
}
|