@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.
Files changed (105) hide show
  1. package/CHANGELOG.md +88 -0
  2. package/README.md +133 -56
  3. package/dist/date.d.ts +54 -0
  4. package/dist/date.js +174 -0
  5. package/dist/date.js.map +1 -0
  6. package/dist/driver-manager.d.ts +34 -0
  7. package/dist/driver-manager.js +53 -0
  8. package/dist/driver-manager.js.map +1 -0
  9. package/dist/drivers/chinese.d.ts +25 -0
  10. package/dist/drivers/chinese.js +485 -0
  11. package/dist/drivers/chinese.js.map +1 -0
  12. package/dist/drivers/gregorian.d.ts +3 -5
  13. package/dist/drivers/gregorian.js +26 -19
  14. package/dist/drivers/gregorian.js.map +1 -1
  15. package/dist/drivers/hijri.d.ts +1 -16
  16. package/dist/drivers/hijri.js +9 -102
  17. package/dist/drivers/hijri.js.map +1 -1
  18. package/dist/drivers/holocene.d.ts +6 -3
  19. package/dist/drivers/holocene.js +7 -20
  20. package/dist/drivers/holocene.js.map +1 -1
  21. package/dist/drivers/julian.d.ts +3 -10
  22. package/dist/drivers/julian.js +11 -71
  23. package/dist/drivers/julian.js.map +1 -1
  24. package/dist/drivers/persian.d.ts +1 -6
  25. package/dist/drivers/persian.js +17 -92
  26. package/dist/drivers/persian.js.map +1 -1
  27. package/dist/drivers/tps.d.ts +11 -28
  28. package/dist/drivers/tps.js +8 -58
  29. package/dist/drivers/tps.js.map +1 -1
  30. package/dist/drivers/unix.d.ts +5 -6
  31. package/dist/drivers/unix.js +10 -32
  32. package/dist/drivers/unix.js.map +1 -1
  33. package/dist/esm/date.js +170 -0
  34. package/dist/esm/date.js.map +1 -0
  35. package/dist/esm/driver-manager.js +49 -0
  36. package/dist/esm/driver-manager.js.map +1 -0
  37. package/dist/esm/drivers/chinese.js +481 -0
  38. package/dist/esm/drivers/chinese.js.map +1 -0
  39. package/dist/esm/drivers/gregorian.js +160 -0
  40. package/dist/esm/drivers/gregorian.js.map +1 -0
  41. package/dist/esm/drivers/hijri.js +184 -0
  42. package/dist/esm/drivers/hijri.js.map +1 -0
  43. package/dist/esm/drivers/holocene.js +115 -0
  44. package/dist/esm/drivers/holocene.js.map +1 -0
  45. package/dist/esm/drivers/julian.js +161 -0
  46. package/dist/esm/drivers/julian.js.map +1 -0
  47. package/dist/esm/drivers/persian.js +190 -0
  48. package/dist/esm/drivers/persian.js.map +1 -0
  49. package/dist/esm/drivers/tps.js +181 -0
  50. package/dist/esm/drivers/tps.js.map +1 -0
  51. package/dist/esm/drivers/unix.js +50 -0
  52. package/dist/esm/drivers/unix.js.map +1 -0
  53. package/dist/esm/index.js +873 -0
  54. package/dist/esm/index.js.map +1 -0
  55. package/dist/esm/types.js +28 -0
  56. package/dist/esm/types.js.map +1 -0
  57. package/dist/esm/uid.js +221 -0
  58. package/dist/esm/uid.js.map +1 -0
  59. package/dist/esm/utils/calendar.js +126 -0
  60. package/dist/esm/utils/calendar.js.map +1 -0
  61. package/dist/esm/utils/env.js +76 -0
  62. package/dist/esm/utils/env.js.map +1 -0
  63. package/dist/esm/utils/timezone.js +168 -0
  64. package/dist/esm/utils/timezone.js.map +1 -0
  65. package/dist/esm/utils/tps-string.js +160 -0
  66. package/dist/esm/utils/tps-string.js.map +1 -0
  67. package/dist/index.d.ts +84 -466
  68. package/dist/index.js +430 -1095
  69. package/dist/index.js.map +1 -1
  70. package/dist/types.d.ts +103 -0
  71. package/dist/types.js +31 -0
  72. package/dist/types.js.map +1 -0
  73. package/dist/uid.d.ts +48 -0
  74. package/dist/uid.js +225 -0
  75. package/dist/uid.js.map +1 -0
  76. package/dist/utils/calendar.d.ts +55 -0
  77. package/dist/utils/calendar.js +136 -0
  78. package/dist/utils/calendar.js.map +1 -0
  79. package/dist/utils/env.d.ts +12 -0
  80. package/dist/utils/env.js +79 -0
  81. package/dist/utils/env.js.map +1 -0
  82. package/dist/utils/timezone.d.ts +32 -0
  83. package/dist/utils/timezone.js +173 -0
  84. package/dist/utils/timezone.js.map +1 -0
  85. package/dist/utils/tps-string.d.ts +12 -0
  86. package/dist/utils/tps-string.js +164 -0
  87. package/dist/utils/tps-string.js.map +1 -0
  88. package/package.json +20 -5
  89. package/src/date.ts +243 -0
  90. package/src/driver-manager.ts +54 -0
  91. package/src/drivers/chinese.ts +542 -0
  92. package/src/drivers/gregorian.ts +29 -27
  93. package/src/drivers/hijri.ts +13 -113
  94. package/src/drivers/holocene.ts +11 -12
  95. package/src/drivers/julian.ts +18 -72
  96. package/src/drivers/persian.ts +25 -92
  97. package/src/drivers/tps.ts +16 -55
  98. package/src/drivers/unix.ts +12 -33
  99. package/src/index.ts +384 -1556
  100. package/src/types.ts +131 -0
  101. package/src/uid.ts +308 -0
  102. package/src/utils/calendar.ts +161 -0
  103. package/src/utils/env.ts +88 -0
  104. package/src/utils/timezone.ts +182 -0
  105. package/src/utils/tps-string.ts +166 -0
@@ -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, TPS } from "../index";
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 } = this.gregorianToHijri(
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 } = this.hijriToGregorian(hy, hm, hd);
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 TPS.buildTimePart(comp);
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
- const maxDays = this.daysInMonth(fullYear, month);
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
  }
@@ -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, TPS } from "../index";
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 TPS.buildTimePart(comp);
62
+ return buildTimePart(comp);
61
63
  }
62
64
 
63
- parseDate(input: string, format?: string): Partial<TPSComponents> {
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>, format?: string): string {
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, // 10001 BCE
148
+ epochYear: -10000,
150
149
  };
151
150
  }
152
151
  }
@@ -9,7 +9,14 @@
9
9
  *
10
10
  * Conversion uses Julian Day Number algorithms.
11
11
  */
12
- import { CalendarDriver, CalendarMetadata, TPSComponents, TPS } from "../index";
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 = this.gregorianToJdn(
60
+ const jdn = gregorianToJdn(
56
61
  date.getUTCFullYear(),
57
62
  date.getUTCMonth() + 1,
58
63
  date.getUTCDate(),
59
64
  );
60
- const { jy, jm, jd } = this.jdnToJulian(jdn);
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 = this.julianToJdn(fullYear, jm, jd);
89
- const { gy, gm, gd } = this.jdnToGregorian(jdn);
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 TPS.buildTimePart(comp);
111
+ return buildTimePart(comp);
107
112
  }
108
113
 
109
- parseDate(input: string, format?: string): Partial<TPSComponents> {
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>, format?: string): string {
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 && this.isLeapYear(fullYear)) maxDay = 29;
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
  }
@@ -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, TPS } from "../index";
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 = this.gregorianToJdn(
71
+ const jdn = gregorianToJdn(
67
72
  date.getUTCFullYear(),
68
73
  date.getUTCMonth() + 1,
69
74
  date.getUTCDate(),
70
75
  );
71
- const { jy, jm, jd } = this.jdnToPersian(jdn);
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 = this.persianToJdn(jy, jm, jd);
101
- const { gy, gm, gd } = this.jdnToGregorian(jdn);
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 TPS.buildTimePart(comp);
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 (!year || year < 1) return false;
206
- if (!month || month < 1 || month > 12) return false;
207
- if (!day || day < 1) return false;
208
- let max = this.DAYS_IN_MONTH[(month ?? 1) - 1];
209
- if (month === 12 && this.isLeapYear(year)) max = 30;
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
  }