chronos-ts 1.0.2 → 1.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/README.md CHANGED
@@ -2,24 +2,36 @@
2
2
  Chronos-ts from the name is inspired by the Greek god of time, Chronos. It is a comprehensive TypeScript package for handling time periods, intervals, and date-related operations.
3
3
 
4
4
  ## Table of Contents
5
- - [Installation](#installation)
6
- - [Overview](#overview)
7
- - [Usage](#usage)
8
- - [API Reference](#api-reference)
5
+ - [Chronos-ts ⏰](#chronos-ts-)
6
+ - [Table of Contents](#table-of-contents)
7
+ - [Installation](#installation)
8
+ - [Overview](#overview)
9
+ - [Usage](#usage)
10
+ - [Creating and Manipulating Periods](#creating-and-manipulating-periods)
11
+ - [Working with Intervals](#working-with-intervals)
12
+ - [Using Utility Functions](#using-utility-functions)
13
+ - [API Reference](#api-reference)
9
14
  - [Classes](#classes)
10
- - [Period](#period)
11
- - [Interval](#interval)
12
- - [Enums](#enums)
13
- - [Precision](#precision)
15
+ - [Period](#period)
16
+ - [Constructor](#constructor)
17
+ - [Methods](#methods)
18
+ - [Fluent API Methods](#fluent-api-methods)
19
+ - [Interval](#interval)
20
+ - [Methods](#methods-1)
21
+ - [Enums](#enums)
22
+ - [Precision](#precision)
14
23
  - [Utility Function](#utility-function)
15
24
  - [Real-world Scenarios](#real-world-scenarios)
16
- - [Event Planning and Management](#event-planning-and-management)
17
- - [Financial Reporting Periods](#financial-reporting-periods)
18
- - [Employee Leave Management](#employee-leave-management)
19
- - [Project Timeline Management](#project-timeline-management)
20
- - [Subscription Billing Cycle Management](#subscription-billing-cycle-management)
21
- - [Employee Shift Management](#employee-shift-management)
22
- - [Travel Itinerary Planning](#travel-itinerary-planning)
25
+ - [**1. Event Planning and Management**](#1-event-planning-and-management)
26
+ - [**2. Financial Reporting Periods**](#2-financial--reporting-periods)
27
+ - [**3. Employee Leave Management**](#3-employee-leave-management)
28
+ - [**4. Project Timeline Management**](#4--project-timeline-management)
29
+ - [**5. Subscription Billing Cycle Management**](#5-subscription-billing-cycle-management)
30
+ - [**6. Employee Shift Management**](#6-employee-shift-management)
31
+ - [**7. Travel Itinerary Planning**](#7-travel-itinerary-planning)
32
+ - [Contributing 🤝](#contributing-)
33
+ - [License 📝](#license-)
34
+ - [Acknowledgements 🙏](#acknowledgements-)
23
35
 
24
36
  ## Installation
25
37
  ```bash
@@ -48,7 +60,7 @@ Whether you're building scheduling systems, financial applications, or any proje
48
60
 
49
61
  ## Usage
50
62
  ### Creating and Manipulating Periods
51
- ````typescript
63
+ ```typescript
52
64
  import { Period, Precision, Interval } from 'chronos-ts';
53
65
 
54
66
  // Create a period for the year 2023
@@ -70,8 +82,8 @@ console.log(overlap?.getStartDate(), overlap?.getEndDate()); // 2023-04-01, 2023
70
82
  // Subtract a period
71
83
  const remainingPeriods = year2023.subtract(q2_2023);
72
84
  console.log(remainingPeriods.length); // 2
73
- console.log(remainingPeriods[0].getStartDate(), remainingPeriods[0].getEndDate()); // 2023-01-01, 2023-03-31
74
- console.log(remainingPeriods[1].getStartDate(), remainingPeriods[1].getEndDate()); // 2023-07-01, 2023-12-31
85
+ console.log(remainingPeriods[0].getStartDate(), remainingPeriods[0].getEndDate()); // 2023-01-01, 2023-04-01
86
+ console.log(remainingPeriods[1].getStartDate(), remainingPeriods[1].getEndDate()); // 2023-06-30, 2023-12-31
75
87
 
76
88
  // Create a period with an interval
77
89
  const weeklyPeriod = new Period('2023-01-01', '2023-12-31', Precision.WEEK, Interval.weeks(1));
@@ -80,7 +92,7 @@ const weeklyPeriod = new Period('2023-01-01', '2023-12-31', Precision.WEEK, Inte
80
92
  const weeklyDates = weeklyPeriod.getDatesInInterval();
81
93
  console.log(weeklyDates?.length); // 53 (number of weeks in 2023)
82
94
 
83
- // Renew a period
95
+ // Renew a period - FIXED: Use getter methods
84
96
  const nextYear = year2023.renew();
85
97
  console.log(nextYear.getStartDate(), nextYear.getEndDate()); // 2024-01-01, 2024-12-31
86
98
 
@@ -90,7 +102,7 @@ const customPeriod = new Period('2023-01-01', '2023-12-31')
90
102
  .setInterval(Interval.months(3));
91
103
 
92
104
  console.log(customPeriod.getDatesInInterval()?.length); // 5 (Jan, Apr, Jul, Oct, Jan)
93
- ````
105
+ ```
94
106
 
95
107
  ### Working with Intervals
96
108
  ``` typescript
@@ -218,7 +230,7 @@ The package includes various utility functions for working with dates:
218
230
  - `range(start: number, end: number, step: number = 1): number[]` - Returns an array of numbers within the specified range.
219
231
 
220
232
  ### Real-world Scenarios
221
- **1. Event Planning and Management**
233
+ #### **1. Event Planning and Management**
222
234
  The `Period` class can be used to represent events, while the `Interval` class can help manage recurring events.
223
235
  ``` typescript
224
236
  import { Period, Interval, Precision } from 'chronos-ts';
@@ -240,7 +252,7 @@ const conflictingMeetings = meetingDates?.filter(date => conference.contains(dat
240
252
  console.log(`There are ${conflictingMeetings.length} conflicting meetings during the conference.`);
241
253
  ```
242
254
 
243
- **2. Financial Reporting Periods**
255
+ #### **2. Financial Reporting Periods**
244
256
  Use the `Period` class to represent financial quarters and calculate year-to-date periods.
245
257
  ``` typescript
246
258
  import { Period, Precision } from 'chronos-ts';
@@ -250,9 +262,9 @@ const q2_2023 = new Period('2023-04-01', '2023-06-30', Precision.DAY);
250
262
  const q3_2023 = new Period('2023-07-01', '2023-09-30', Precision.DAY);
251
263
  const q4_2023 = new Period('2023-10-01', '2023-12-31', Precision.DAY);
252
264
 
253
- // Calculate Year-to-Date period
265
+ // Calculate Year-to-Date period - FIXED: Use getter method
254
266
  const ytd = (currentQuarter: Period): Period => {
255
- return new Period('2023-01-01', currentQuarter.endDate, Precision.DAY);
267
+ return new Period('2023-01-01', currentQuarter.getEndDate(), Precision.DAY);
256
268
  };
257
269
 
258
270
  const q3YTD = ytd(q3_2023);
@@ -269,7 +281,7 @@ const q3Revenue = 1200000;
269
281
  console.log(`Q3 QoQ Growth: ${calculateQoQGrowth(q3Revenue, q2Revenue)}`);
270
282
  ```
271
283
 
272
- **3. Employee Leave Management**
284
+ #### **3. Employee Leave Management**
273
285
  Use the `Period` class to manage employee leave requests and calculate leave balances.
274
286
  ``` typescript
275
287
  import { Period, Precision } from 'chronos-ts';
@@ -298,7 +310,7 @@ console.log(`Next year's leave balance: ${nextYearLeaveBalance ? nextYearLeaveBa
298
310
 
299
311
  ```
300
312
 
301
- **4. Project Timeline Management**
313
+ #### **4. Project Timeline Management**
302
314
  Use the Period class to manage project timelines and track overlapping tasks.
303
315
  ``` typescript
304
316
  import { Period, Precision } from 'chronos-ts';
@@ -339,7 +351,7 @@ const currentProgress = calculateProgress(new Date('2023-06-15'));
339
351
  console.log(`Project progress: ${currentProgress.toFixed(2)}%`);
340
352
  ```
341
353
 
342
- **5. Subscription Billing Cycle Management**
354
+ #### **5. Subscription Billing Cycle Management**
343
355
  Use the Period class to manage subscription periods and calculate renewal dates.
344
356
  ``` typescript
345
357
  import { Period, Precision, addToDate } from 'chronos-ts';
@@ -384,7 +396,7 @@ const renewedPeriod = monthlySubscription.getCurrentPeriod();
384
396
  console.log(`Monthly subscription renewed. New period: ${renewedPeriod.getStartDate().toDateString()} - ${renewedPeriod.getEndDate().toDateString()}`);
385
397
  ```
386
398
 
387
- **6. Employee Shift Management**
399
+ #### **6. Employee Shift Management**
388
400
 
389
401
  Use the `Period` and `Interval` classes to manage employee shifts and calculate overtime.
390
402
 
@@ -446,7 +458,7 @@ const hasConflict = (shifts: Shift[]): boolean => {
446
458
  console.log(`Shifts have conflicts: ${hasConflict(employeeShifts)}`);
447
459
  ```
448
460
 
449
- **7. Travel Itinerary Planning**
461
+ #### **7. Travel Itinerary Planning**
450
462
  Use the `Period` class to manage travel itineraries and check for scheduling conflicts.
451
463
  ``` typescript
452
464
  import { Period, Precision, addToDate } from 'chronos-ts';
@@ -510,4 +522,4 @@ Contributions, issues and feature requests are welcome. After cloning & setting
510
522
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
511
523
 
512
524
  ## Acknowledgements 🙏
513
- - [spatie/period]((https://github.com/spatie/period)
525
+ - [spatie/period](https://github.com/spatie/period)
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
1
  export { Period } from './period';
2
2
  export { Precision } from './precision';
3
3
  export { Interval } from './interval';
4
+ export * from './utils';
package/dist/index.js CHANGED
@@ -1,4 +1,18 @@
1
1
  "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
2
16
  Object.defineProperty(exports, "__esModule", { value: true });
3
17
  exports.Interval = exports.Precision = exports.Period = void 0;
4
18
  var period_1 = require("./period");
@@ -7,3 +21,4 @@ var precision_1 = require("./precision");
7
21
  Object.defineProperty(exports, "Precision", { enumerable: true, get: function () { return precision_1.Precision; } });
8
22
  var interval_1 = require("./interval");
9
23
  Object.defineProperty(exports, "Interval", { enumerable: true, get: function () { return interval_1.Interval; } });
24
+ __exportStar(require("./utils"), exports);
package/dist/period.d.ts CHANGED
@@ -12,8 +12,17 @@ export declare class Period {
12
12
  * @param end - The end date of the period, either as a string or a Date object.
13
13
  * @param precision - The precision level of the period, defaulting to Precision.DAY.
14
14
  * @param interval - An optional interval associated with the period, defaulting to null.
15
+ * @throws {Error} If start date is after end date or if dates are invalid.
15
16
  */
16
17
  constructor(start: string | Date, end: string | Date, precision?: Precision, interval?: Interval | null);
18
+ /**
19
+ * Validates and converts a date input to a Date object.
20
+ *
21
+ * @param date - The date to validate, either as a string or Date object.
22
+ * @returns A valid Date object.
23
+ * @throws {Error} If the date is invalid.
24
+ */
25
+ private validateDate;
17
26
  /**
18
27
  * Retrieves the start date of the period.
19
28
  *
@@ -159,6 +168,7 @@ export declare class Period {
159
168
  *
160
169
  * @param start - The start date, which can be a string or a Date object.
161
170
  * @returns The current instance for method chaining.
171
+ * @throws {Error} If the new start date is after the current end date.
162
172
  */
163
173
  setStart(start: string | Date): this;
164
174
  /**
@@ -166,6 +176,7 @@ export declare class Period {
166
176
  *
167
177
  * @param end - The end date as a string or Date object.
168
178
  * @returns The current instance for method chaining.
179
+ * @throws {Error} If the new end date is before the current start date.
169
180
  */
170
181
  setEnd(end: string | Date): this;
171
182
  /**
package/dist/period.js CHANGED
@@ -11,13 +11,31 @@ class Period {
11
11
  * @param end - The end date of the period, either as a string or a Date object.
12
12
  * @param precision - The precision level of the period, defaulting to Precision.DAY.
13
13
  * @param interval - An optional interval associated with the period, defaulting to null.
14
+ * @throws {Error} If start date is after end date or if dates are invalid.
14
15
  */
15
16
  constructor(start, end, precision = precision_1.Precision.DAY, interval = null) {
16
- this.startDate = new Date(start);
17
- this.endDate = new Date(end);
17
+ this.startDate = this.validateDate(start);
18
+ this.endDate = this.validateDate(end);
19
+ if (this.startDate > this.endDate) {
20
+ throw new Error('Start date must be before or equal to end date');
21
+ }
18
22
  this.precision = precision;
19
23
  this.interval = interval;
20
24
  }
25
+ /**
26
+ * Validates and converts a date input to a Date object.
27
+ *
28
+ * @param date - The date to validate, either as a string or Date object.
29
+ * @returns A valid Date object.
30
+ * @throws {Error} If the date is invalid.
31
+ */
32
+ validateDate(date) {
33
+ const result = new Date(date);
34
+ if (isNaN(result.getTime())) {
35
+ throw new Error(`Invalid date: ${date}`);
36
+ }
37
+ return result;
38
+ }
21
39
  /**
22
40
  * Retrieves the start date of the period.
23
41
  *
@@ -41,7 +59,7 @@ class Period {
41
59
  * @returns `true` if the date is within the period, `false` otherwise.
42
60
  */
43
61
  contains(date) {
44
- const checkDate = new Date(date);
62
+ const checkDate = this.validateDate(date);
45
63
  return checkDate >= this.startDate && checkDate <= this.endDate;
46
64
  }
47
65
  /**
@@ -138,7 +156,7 @@ class Period {
138
156
  if (this.endDate.getDate() < this.startDate.getDate()) {
139
157
  months--;
140
158
  }
141
- return months;
159
+ return Math.max(0, months);
142
160
  }
143
161
  /**
144
162
  * Calculates the number of whole years in the interval.
@@ -184,13 +202,13 @@ class Period {
184
202
  return [this];
185
203
  }
186
204
  const periods = [];
205
+ // Add period before the overlap (if any)
187
206
  if (this.startDate < other.startDate) {
188
- const endDate = new Date(other.startDate.getTime() - 1);
189
- periods.push(new Period(this.startDate, endDate, this.precision));
207
+ periods.push(new Period(this.startDate, other.startDate, this.precision));
190
208
  }
209
+ // Add period after the overlap (if any)
191
210
  if (this.endDate > other.endDate) {
192
- const startDate = new Date(other.endDate.getTime() + 1);
193
- periods.push(new Period(startDate, this.endDate, this.precision));
211
+ periods.push(new Period(other.endDate, this.endDate, this.precision));
194
212
  }
195
213
  return periods;
196
214
  }
@@ -206,10 +224,20 @@ class Period {
206
224
  if (this.overlapsWith(other) || this.isAdjacentTo(other)) {
207
225
  return null;
208
226
  }
209
- const precisionMs = (0, precision_1.getPrecisionInMilliseconds)(this.precision);
210
- const start = this.endDate < other.startDate ? this.endDate : other.endDate;
211
- const end = this.startDate > other.startDate ? this.startDate : other.startDate;
212
- return new Period(new Date(start.getTime() + precisionMs), new Date(end.getTime() - precisionMs), this.precision);
227
+ let start, end;
228
+ if (this.endDate < other.startDate) {
229
+ start = this.endDate;
230
+ end = other.startDate;
231
+ }
232
+ else {
233
+ start = other.endDate;
234
+ end = this.startDate;
235
+ }
236
+ // Ensure start is before end for valid Period creation
237
+ if (start >= end) {
238
+ return null;
239
+ }
240
+ return new Period(start, end, this.precision);
213
241
  }
214
242
  /**
215
243
  * Computes the symmetric difference between this period and another period.
@@ -261,8 +289,9 @@ class Period {
261
289
  */
262
290
  renew() {
263
291
  const length = this.length();
264
- const newStart = new Date(this.endDate.getTime() + (0, precision_1.getPrecisionInMilliseconds)(this.precision));
265
- const newEnd = new Date(newStart.getTime() + length * (0, precision_1.getPrecisionInMilliseconds)(this.precision));
292
+ const precisionMs = (0, precision_1.getPrecisionInMilliseconds)(this.precision);
293
+ const newStart = new Date(this.endDate.getTime() + precisionMs);
294
+ const newEnd = new Date(newStart.getTime() + length * precisionMs);
266
295
  return new Period(newStart, newEnd, this.precision, this.interval);
267
296
  }
268
297
  /**
@@ -287,9 +316,14 @@ class Period {
287
316
  *
288
317
  * @param start - The start date, which can be a string or a Date object.
289
318
  * @returns The current instance for method chaining.
319
+ * @throws {Error} If the new start date is after the current end date.
290
320
  */
291
321
  setStart(start) {
292
- this.startDate = new Date(start);
322
+ const newStartDate = this.validateDate(start);
323
+ if (newStartDate > this.endDate) {
324
+ throw new Error('Start date must be before or equal to end date');
325
+ }
326
+ this.startDate = newStartDate;
293
327
  return this;
294
328
  }
295
329
  /**
@@ -297,9 +331,14 @@ class Period {
297
331
  *
298
332
  * @param end - The end date as a string or Date object.
299
333
  * @returns The current instance for method chaining.
334
+ * @throws {Error} If the new end date is before the current start date.
300
335
  */
301
336
  setEnd(end) {
302
- this.endDate = new Date(end);
337
+ const newEndDate = this.validateDate(end);
338
+ if (newEndDate < this.startDate) {
339
+ throw new Error('End date must be after or equal to start date');
340
+ }
341
+ this.endDate = newEndDate;
303
342
  return this;
304
343
  }
305
344
  /**
package/dist/utils.d.ts CHANGED
@@ -169,6 +169,7 @@ export declare function formatDate(date: Date, format: string): string;
169
169
  * @param dateString - The date string to parse.
170
170
  * @param format - The format of the date string. Supported format parts are YYYY, MM, DD, HH, mm, ss.
171
171
  * @returns A Date object representing the parsed date.
172
+ * @throws {Error} If the date string doesn't match the specified format.
172
173
  *
173
174
  * @example
174
175
  * ```typescript
@@ -184,5 +185,6 @@ export declare function parseDate(dateString: string, format: string): Date;
184
185
  * @param end - The ending number of the range.
185
186
  * @param step - The step between each number in the range. Defaults to 1.
186
187
  * @returns An array of numbers from start to end, incremented by step.
188
+ * @throws {Error} If step is zero or has the wrong sign.
187
189
  */
188
190
  export declare function range(start: number, end: number, step?: number): number[];
package/dist/utils.js CHANGED
@@ -47,15 +47,14 @@ function getDatesWithInterval(start, end, interval) {
47
47
  */
48
48
  function getWeeksWithInterval(start, end, interval) {
49
49
  const weeks = [];
50
- let current = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate()));
50
+ const current = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate()));
51
51
  // Move to the start of the week (Sunday)
52
52
  current.setUTCDate(current.getUTCDate() - current.getUTCDay());
53
+ const weekIncrement = Math.max(1, Math.round(interval.getMinutesInterval() / (7 * 24 * 60)));
53
54
  while (current <= end) {
54
55
  weeks.push(new Date(current));
55
- // Add the interval in minutes, converted to milliseconds
56
- current = new Date(current.getTime() + interval.getMinutesInterval() * 60000);
57
- // Ensure we're at the start of the week
58
- current.setUTCDate(current.getUTCDate() - current.getUTCDay());
56
+ // Add weeks based on the interval
57
+ current.setUTCDate(current.getUTCDate() + weekIncrement * 7);
59
58
  }
60
59
  return weeks;
61
60
  }
@@ -72,9 +71,10 @@ function getDaysWithInterval(start, end, interval) {
72
71
  const days = [];
73
72
  const current = new Date(start);
74
73
  current.setUTCHours(0, 0, 0, 0);
74
+ const dayIncrement = Math.max(1, Math.round(interval.getMinutesInterval() / (24 * 60)));
75
75
  while (current <= end) {
76
76
  days.push(new Date(current));
77
- current.setUTCDate(current.getUTCDate() + interval.getMinutesInterval() / (24 * 60));
77
+ current.setUTCDate(current.getUTCDate() + dayIncrement);
78
78
  }
79
79
  return days;
80
80
  }
@@ -91,9 +91,10 @@ function getHoursWithInterval(start, end, interval) {
91
91
  const hours = [];
92
92
  const current = new Date(start);
93
93
  current.setMinutes(0, 0, 0);
94
+ const hourIncrement = Math.max(1, Math.round(interval.getMinutesInterval() / 60));
94
95
  while (current <= end) {
95
96
  hours.push(new Date(current));
96
- current.setHours(current.getHours() + interval.getMinutesInterval() / 60);
97
+ current.setHours(current.getHours() + hourIncrement);
97
98
  }
98
99
  return hours;
99
100
  }
@@ -110,9 +111,10 @@ function getMinutesWithInterval(start, end, interval) {
110
111
  const minutes = [];
111
112
  const current = new Date(start);
112
113
  current.setSeconds(0, 0);
114
+ const minuteIncrement = Math.max(1, interval.getMinutesInterval());
113
115
  while (current <= end) {
114
116
  minutes.push(new Date(current));
115
- current.setMinutes(current.getMinutes() + interval.getMinutesInterval());
117
+ current.setMinutes(current.getMinutes() + minuteIncrement);
116
118
  }
117
119
  return minutes;
118
120
  }
@@ -128,9 +130,11 @@ function getMinutesWithInterval(start, end, interval) {
128
130
  function getMonthsWithInterval(start, end, interval) {
129
131
  const months = [];
130
132
  const current = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), 1));
133
+ // Use approximation of 30 days per month for interval calculation
134
+ const monthIncrement = Math.max(1, Math.round(interval.getMinutesInterval() / (30 * 24 * 60)));
131
135
  while (current <= end) {
132
136
  months.push(new Date(current));
133
- current.setUTCMonth(current.getUTCMonth() + interval.getMinutesInterval() / (30 * 24 * 60));
137
+ current.setUTCMonth(current.getUTCMonth() + monthIncrement);
134
138
  }
135
139
  return months;
136
140
  }
@@ -146,10 +150,10 @@ function getMonthsWithInterval(start, end, interval) {
146
150
  function getYearsWithInterval(start, end, interval) {
147
151
  const years = [];
148
152
  const current = new Date(Date.UTC(start.getUTCFullYear(), 0, 1));
153
+ // Calculate the number of years to add based on the interval
154
+ const yearsToAdd = Math.max(1, Math.round(interval.getMinutesInterval() / 525600)); // 525600 minutes in a year
149
155
  while (current <= end) {
150
156
  years.push(new Date(current));
151
- // Calculate the number of years to add based on the interval
152
- const yearsToAdd = Math.max(1, Math.round(interval.getMinutesInterval() / 525600)); // 525600 minutes in a year
153
157
  current.setUTCFullYear(current.getUTCFullYear() + yearsToAdd);
154
158
  }
155
159
  return years;
@@ -187,13 +191,20 @@ function addToDate(date, amount, unit) {
187
191
  break;
188
192
  case precision_1.Precision.MONTH:
189
193
  newDate.setUTCMonth(newDate.getUTCMonth() + amount);
194
+ // Handle month overflow for dates that don't exist in the target month
190
195
  if (newDate.getUTCDate() !== date.getUTCDate()) {
191
196
  newDate.setUTCDate(0); // Set to last day of previous month
192
197
  }
193
198
  break;
194
199
  case precision_1.Precision.YEAR:
195
200
  newDate.setUTCFullYear(newDate.getUTCFullYear() + amount);
201
+ // Handle leap year edge case (Feb 29 -> Feb 28)
202
+ if (newDate.getUTCMonth() !== date.getUTCMonth()) {
203
+ newDate.setUTCDate(0); // Set to last day of previous month
204
+ }
196
205
  break;
206
+ default:
207
+ throw new Error(`Unsupported precision unit: ${unit}`);
197
208
  }
198
209
  return newDate;
199
210
  }
@@ -291,7 +302,10 @@ function getDaysInMonth(year, month) {
291
302
  * @returns The formatted date string.
292
303
  */
293
304
  function formatDate(date, format) {
294
- const pad = (n) => n.toString().padStart(2, '0');
305
+ const pad = (n) => {
306
+ const str = n.toString();
307
+ return str.length < 2 ? '0' + str : str;
308
+ };
295
309
  const map = {
296
310
  YYYY: date.getFullYear().toString(),
297
311
  MM: pad(date.getMonth() + 1),
@@ -308,6 +322,7 @@ function formatDate(date, format) {
308
322
  * @param dateString - The date string to parse.
309
323
  * @param format - The format of the date string. Supported format parts are YYYY, MM, DD, HH, mm, ss.
310
324
  * @returns A Date object representing the parsed date.
325
+ * @throws {Error} If the date string doesn't match the specified format.
311
326
  *
312
327
  * @example
313
328
  * ```typescript
@@ -316,13 +331,27 @@ function formatDate(date, format) {
316
331
  * ```
317
332
  */
318
333
  function parseDate(dateString, format) {
319
- const map = {};
320
334
  const formatParts = format.match(/YYYY|MM|DD|HH|mm|ss/gi) || [];
321
335
  const dateParts = dateString.match(/\d+/g) || [];
336
+ if (formatParts.length !== dateParts.length) {
337
+ throw new Error(`Date string "${dateString}" does not match the specified format "${format}"`);
338
+ }
339
+ const map = {};
322
340
  formatParts.forEach((part, index) => {
323
- map[part] = parseInt(dateParts[index]);
341
+ map[part] = parseInt(dateParts[index], 10);
324
342
  });
325
- return new Date(map['YYYY'] || 0, (map['MM'] || 1) - 1, map['DD'] || 1, map['HH'] || 0, map['mm'] || 0, map['ss'] || 0);
343
+ const year = map['YYYY'] || 0;
344
+ const month = (map['MM'] || 1) - 1; // JavaScript months are 0-indexed
345
+ const day = map['DD'] || 1;
346
+ const hour = map['HH'] || 0;
347
+ const minute = map['mm'] || 0;
348
+ const second = map['ss'] || 0;
349
+ const result = new Date(year, month, day, hour, minute, second);
350
+ // Validate the resulting date
351
+ if (isNaN(result.getTime())) {
352
+ throw new Error(`Invalid date components parsed from "${dateString}"`);
353
+ }
354
+ return result;
326
355
  }
327
356
  /**
328
357
  * Generates an array of numbers within a specified range.
@@ -331,7 +360,15 @@ function parseDate(dateString, format) {
331
360
  * @param end - The ending number of the range.
332
361
  * @param step - The step between each number in the range. Defaults to 1.
333
362
  * @returns An array of numbers from start to end, incremented by step.
363
+ * @throws {Error} If step is zero or has the wrong sign.
334
364
  */
335
365
  function range(start, end, step = 1) {
336
- return Array.from({ length: Math.floor((end - start) / step) + 1 }, (_, i) => start + i * step);
366
+ if (step === 0) {
367
+ throw new Error('Step cannot be zero');
368
+ }
369
+ if ((end > start && step < 0) || (end < start && step > 0)) {
370
+ throw new Error('Step direction must match the range direction');
371
+ }
372
+ const length = Math.floor(Math.abs(end - start) / Math.abs(step)) + 1;
373
+ return Array.from({ length }, (_, i) => start + i * step);
337
374
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chronos-ts",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "A comprehensive TypeScript package for handling time periods, intervals, and date-related operations.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",