chronos-ts 2.0.0 → 2.0.2
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/core/period.d.ts +1 -0
- package/dist/core/period.js +67 -12
- package/dist/core/timezone.d.ts +1 -0
- package/dist/core/timezone.js +14 -6
- package/dist/utils/index.js +4 -2
- package/package.json +1 -1
package/dist/core/period.d.ts
CHANGED
|
@@ -322,6 +322,7 @@ export declare class ChronosPeriod implements Iterable<Chronos> {
|
|
|
322
322
|
union(other: ChronosPeriod): ChronosPeriod | null;
|
|
323
323
|
/**
|
|
324
324
|
* Get the difference between two periods
|
|
325
|
+
* Returns the parts of this period that don't overlap with the other period
|
|
325
326
|
*/
|
|
326
327
|
diff(other: ChronosPeriod): ChronosPeriod[];
|
|
327
328
|
/**
|
package/dist/core/period.js
CHANGED
|
@@ -55,6 +55,32 @@ class ChronosPeriod {
|
|
|
55
55
|
interval instanceof interval_1.ChronosInterval
|
|
56
56
|
? interval
|
|
57
57
|
: interval_1.ChronosInterval.create(interval || { days: 1 });
|
|
58
|
+
// Validate interval is not zero to prevent infinite loops
|
|
59
|
+
if (this._interval.isZero()) {
|
|
60
|
+
throw new Error('ChronosPeriod: Interval cannot be zero');
|
|
61
|
+
}
|
|
62
|
+
// Validate interval is not negative
|
|
63
|
+
if (this._interval.isNegative()) {
|
|
64
|
+
throw new Error('ChronosPeriod: Interval cannot be negative');
|
|
65
|
+
}
|
|
66
|
+
// Warn about potentially large iterations (optional safety check)
|
|
67
|
+
if (this._end !== null) {
|
|
68
|
+
const durationMs = Math.abs(this._end.valueOf() - this._start.valueOf());
|
|
69
|
+
const intervalMs = Math.abs(this._interval.totalMilliseconds());
|
|
70
|
+
if (intervalMs > 0) {
|
|
71
|
+
const estimatedIterations = durationMs / intervalMs;
|
|
72
|
+
// Warn if period would generate more than 1 million iterations
|
|
73
|
+
if (estimatedIterations > 1000000) {
|
|
74
|
+
console.warn(`ChronosPeriod: Large number of iterations detected (~${Math.floor(estimatedIterations).toLocaleString()}). ` +
|
|
75
|
+
`This may cause performance issues. Consider using a larger interval or setting a recurrence limit.`);
|
|
76
|
+
}
|
|
77
|
+
// Hard limit: throw error if more than 10 million iterations
|
|
78
|
+
if (estimatedIterations > 10000000) {
|
|
79
|
+
throw new Error(`ChronosPeriod: Period would generate ~${Math.floor(estimatedIterations).toLocaleString()} iterations, ` +
|
|
80
|
+
`which exceeds the safety limit of 10 million. Use a larger interval or set explicit recurrence limits.`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
58
84
|
this._recurrences = null;
|
|
59
85
|
this._options = {
|
|
60
86
|
excludeStart: (_a = options.excludeStart) !== null && _a !== void 0 ? _a : false,
|
|
@@ -372,10 +398,18 @@ class ChronosPeriod {
|
|
|
372
398
|
*/
|
|
373
399
|
setInterval(interval) {
|
|
374
400
|
const period = this._cloneForModification();
|
|
375
|
-
|
|
376
|
-
interval
|
|
377
|
-
|
|
378
|
-
|
|
401
|
+
const newInterval = interval instanceof interval_1.ChronosInterval
|
|
402
|
+
? interval
|
|
403
|
+
: interval_1.ChronosInterval.create(interval);
|
|
404
|
+
// Validate interval is not zero to prevent infinite loops
|
|
405
|
+
if (newInterval.isZero()) {
|
|
406
|
+
throw new Error('ChronosPeriod: Interval cannot be zero');
|
|
407
|
+
}
|
|
408
|
+
// Validate interval is not negative
|
|
409
|
+
if (newInterval.isNegative()) {
|
|
410
|
+
throw new Error('ChronosPeriod: Interval cannot be negative');
|
|
411
|
+
}
|
|
412
|
+
period._interval = newInterval;
|
|
379
413
|
return period;
|
|
380
414
|
}
|
|
381
415
|
/**
|
|
@@ -390,6 +424,10 @@ class ChronosPeriod {
|
|
|
390
424
|
* Set interval by unit
|
|
391
425
|
*/
|
|
392
426
|
every(amount, unit) {
|
|
427
|
+
// Validate amount is positive
|
|
428
|
+
if (amount <= 0) {
|
|
429
|
+
throw new Error('ChronosPeriod: Amount must be positive');
|
|
430
|
+
}
|
|
393
431
|
const normalizedUnit = (0, utils_1.normalizeUnit)(unit);
|
|
394
432
|
const duration = {};
|
|
395
433
|
switch (normalizedUnit) {
|
|
@@ -607,8 +645,11 @@ class ChronosPeriod {
|
|
|
607
645
|
* Get the last date in the period
|
|
608
646
|
*/
|
|
609
647
|
last() {
|
|
610
|
-
|
|
611
|
-
|
|
648
|
+
let lastDate = null;
|
|
649
|
+
for (const date of this) {
|
|
650
|
+
lastDate = date;
|
|
651
|
+
}
|
|
652
|
+
return lastDate;
|
|
612
653
|
}
|
|
613
654
|
/**
|
|
614
655
|
* Get a date at a specific index
|
|
@@ -628,7 +669,12 @@ class ChronosPeriod {
|
|
|
628
669
|
*/
|
|
629
670
|
contains(date) {
|
|
630
671
|
const target = chronos_1.Chronos.parse(date);
|
|
631
|
-
|
|
672
|
+
for (const d of this) {
|
|
673
|
+
if (d.isSame(target, 'day')) {
|
|
674
|
+
return true;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return false;
|
|
632
678
|
}
|
|
633
679
|
/**
|
|
634
680
|
* For each iteration
|
|
@@ -720,6 +766,7 @@ class ChronosPeriod {
|
|
|
720
766
|
}
|
|
721
767
|
/**
|
|
722
768
|
* Get the difference between two periods
|
|
769
|
+
* Returns the parts of this period that don't overlap with the other period
|
|
723
770
|
*/
|
|
724
771
|
diff(other) {
|
|
725
772
|
var _a, _b;
|
|
@@ -728,14 +775,14 @@ class ChronosPeriod {
|
|
|
728
775
|
}
|
|
729
776
|
const results = [];
|
|
730
777
|
const thisEnd = (_a = this._end) !== null && _a !== void 0 ? _a : this.last();
|
|
731
|
-
|
|
778
|
+
const otherEnd = (_b = other._end) !== null && _b !== void 0 ? _b : other.last();
|
|
779
|
+
// Before the other period starts - create gap from this start to other start
|
|
732
780
|
if (this._start.isBefore(other._start)) {
|
|
733
|
-
results.push(new ChronosPeriod(this._start, other._start
|
|
781
|
+
results.push(new ChronosPeriod(this._start, other._start, this._interval));
|
|
734
782
|
}
|
|
735
|
-
// After the other period ends
|
|
736
|
-
const otherEnd = (_b = other._end) !== null && _b !== void 0 ? _b : other.last();
|
|
783
|
+
// After the other period ends - create gap from other end to this end
|
|
737
784
|
if (otherEnd && thisEnd && thisEnd.isAfter(otherEnd)) {
|
|
738
|
-
results.push(new ChronosPeriod(otherEnd
|
|
785
|
+
results.push(new ChronosPeriod(otherEnd, thisEnd, this._interval));
|
|
739
786
|
}
|
|
740
787
|
return results;
|
|
741
788
|
}
|
|
@@ -832,6 +879,14 @@ class ChronosPeriod {
|
|
|
832
879
|
const splitInterval = interval instanceof interval_1.ChronosInterval
|
|
833
880
|
? interval
|
|
834
881
|
: interval_1.ChronosInterval.create(interval);
|
|
882
|
+
// Validate that the interval is not zero
|
|
883
|
+
if (splitInterval.isZero()) {
|
|
884
|
+
throw new Error('Cannot split by zero interval');
|
|
885
|
+
}
|
|
886
|
+
// Validate that the interval is positive
|
|
887
|
+
if (splitInterval.isNegative()) {
|
|
888
|
+
throw new Error('Cannot split by negative interval');
|
|
889
|
+
}
|
|
835
890
|
const chunks = [];
|
|
836
891
|
let current = this._start.clone();
|
|
837
892
|
while (current.isSameOrBefore(this._end)) {
|
package/dist/core/timezone.d.ts
CHANGED
package/dist/core/timezone.js
CHANGED
|
@@ -120,11 +120,13 @@ class ChronosTimezone {
|
|
|
120
120
|
*/
|
|
121
121
|
constructor(identifier = 'UTC') {
|
|
122
122
|
this._originalOffset = null;
|
|
123
|
+
this._extraMinutes = 0; // For non-whole-hour offsets like +05:30
|
|
123
124
|
this._cachedOffset = null;
|
|
124
125
|
this._cachedDate = null;
|
|
125
126
|
const normalized = this._normalizeIdentifier(identifier);
|
|
126
127
|
this._identifier = normalized.identifier;
|
|
127
128
|
this._originalOffset = normalized.originalOffset;
|
|
129
|
+
this._extraMinutes = normalized.extraMinutes;
|
|
128
130
|
}
|
|
129
131
|
/**
|
|
130
132
|
* Normalize timezone identifier
|
|
@@ -133,16 +135,21 @@ class ChronosTimezone {
|
|
|
133
135
|
// Handle UTC aliases
|
|
134
136
|
if (identifier.toUpperCase() === 'Z' ||
|
|
135
137
|
identifier.toUpperCase() === 'GMT') {
|
|
136
|
-
return { identifier: 'UTC', originalOffset: null };
|
|
138
|
+
return { identifier: 'UTC', originalOffset: null, extraMinutes: 0 };
|
|
137
139
|
}
|
|
138
140
|
// Handle offset strings like +05:30, -08:00
|
|
139
141
|
if (/^[+-]\d{2}:\d{2}$/.test(identifier)) {
|
|
140
142
|
// Store original offset and convert to Etc/GMT for internal use
|
|
141
143
|
const offsetHours = this._parseOffsetString(identifier);
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
+
const sign = offsetHours >= 0 ? 1 : -1;
|
|
145
|
+
const absHours = Math.abs(offsetHours);
|
|
146
|
+
const wholeHours = Math.floor(absHours);
|
|
147
|
+
// Calculate extra minutes for non-whole-hour offsets (e.g., +05:30 has 30 extra minutes)
|
|
148
|
+
const extraMinutes = Math.round((absHours - wholeHours) * 60) * sign;
|
|
149
|
+
const etcGmt = `Etc/GMT${offsetHours >= 0 ? '-' : '+'}${wholeHours}`;
|
|
150
|
+
return { identifier: etcGmt, originalOffset: identifier, extraMinutes };
|
|
144
151
|
}
|
|
145
|
-
return { identifier, originalOffset: null };
|
|
152
|
+
return { identifier, originalOffset: null, extraMinutes: 0 };
|
|
146
153
|
}
|
|
147
154
|
/**
|
|
148
155
|
* Parse offset string to hours
|
|
@@ -286,10 +293,11 @@ class ChronosTimezone {
|
|
|
286
293
|
const tzParts = this._parseIntlParts(tzFormatter.formatToParts(date));
|
|
287
294
|
const utcDate = new Date(Date.UTC(utcParts.year, utcParts.month - 1, utcParts.day, utcParts.hour, utcParts.minute));
|
|
288
295
|
const tzDate = new Date(Date.UTC(tzParts.year, tzParts.month - 1, tzParts.day, tzParts.hour, tzParts.minute));
|
|
289
|
-
|
|
296
|
+
// Add extra minutes for non-whole-hour offsets (e.g., +05:30)
|
|
297
|
+
return ((tzDate.getTime() - utcDate.getTime()) / 60000 + this._extraMinutes);
|
|
290
298
|
}
|
|
291
299
|
catch (_a) {
|
|
292
|
-
return
|
|
300
|
+
return this._extraMinutes;
|
|
293
301
|
}
|
|
294
302
|
}
|
|
295
303
|
/**
|
package/dist/utils/index.js
CHANGED
|
@@ -158,7 +158,8 @@ const UNIT_ALIASES = {
|
|
|
158
158
|
*/
|
|
159
159
|
function normalizeUnit(unit) {
|
|
160
160
|
var _a;
|
|
161
|
-
|
|
161
|
+
// Check original case first for case-sensitive short codes (M vs m, etc.)
|
|
162
|
+
const normalized = (_a = UNIT_ALIASES[unit]) !== null && _a !== void 0 ? _a : UNIT_ALIASES[unit.toLowerCase()];
|
|
162
163
|
if (!normalized) {
|
|
163
164
|
throw new Error(`Invalid time unit: ${unit}`);
|
|
164
165
|
}
|
|
@@ -237,7 +238,8 @@ function getDaysInYear(year) {
|
|
|
237
238
|
* Get the day of year (1-366)
|
|
238
239
|
*/
|
|
239
240
|
function getDayOfYear(date) {
|
|
240
|
-
|
|
241
|
+
// Use local time consistently (not UTC) to avoid timezone issues
|
|
242
|
+
const start = new Date(date.getFullYear(), 0, 0);
|
|
241
243
|
const diff = date.getTime() - start.getTime();
|
|
242
244
|
return Math.floor(diff / types_1.MILLISECONDS_PER_DAY);
|
|
243
245
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chronos-ts",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A comprehensive TypeScript library for date and time manipulation, inspired by Carbon PHP. Features immutable API, intervals, periods, timezones, and i18n support.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|