chronos-ts 1.0.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/LICENSE +22 -0
- package/README.md +513 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +9 -0
- package/dist/interval.d.ts +14 -0
- package/dist/interval.js +35 -0
- package/dist/period.d.ts +31 -0
- package/dist/period.js +168 -0
- package/dist/precision.d.ts +9 -0
- package/dist/precision.js +31 -0
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +168 -0
- package/package.json +68 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
The MIT License (MIT)
|
|
3
|
+
|
|
4
|
+
Copyright (c) 2024 Josiah Endurance
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
# Chronos-ts ⏰
|
|
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
|
+
|
|
4
|
+
## Table of Contents
|
|
5
|
+
- [Installation](#installation)
|
|
6
|
+
- [Overview](#overview)
|
|
7
|
+
- [Usage](#usage)
|
|
8
|
+
- [API Reference](#api-reference)
|
|
9
|
+
- [Classes](#classes)
|
|
10
|
+
- [Period](#period)
|
|
11
|
+
- [Interval](#interval)
|
|
12
|
+
- [Enums](#enums)
|
|
13
|
+
- [Precision](#precision)
|
|
14
|
+
- [Utility Function](#utility-function)
|
|
15
|
+
- [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)
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
```bash
|
|
26
|
+
npm install chronos-ts
|
|
27
|
+
|
|
28
|
+
# or
|
|
29
|
+
yarn add chronos-ts
|
|
30
|
+
|
|
31
|
+
# or
|
|
32
|
+
|
|
33
|
+
pnpm add chronos-ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Overview
|
|
37
|
+
This package provides an easy-to-use API for working with periods and intervals, along with precise handling of date and time logic. Inspired by the [spatie/period](https://github.com/spatie/period) package in PHP, this package provides support for both TypeScript and JavaScript environments, offering advanced time manipulation. It offers a user-friendly API for working with periods and intervals, providing precise date and time logic handling. Key features include:
|
|
38
|
+
- Flexible period creation and manipulation with customizable precision (minute to year)
|
|
39
|
+
- Interval generation for recurring events (e.g., "every 3 days")
|
|
40
|
+
- Comprehensive period comparisons (overlaps, contains, adjacent)
|
|
41
|
+
- Precise start/end date handling with various time units
|
|
42
|
+
- Advanced operations like symmetric differences and unions between periods
|
|
43
|
+
- Adjacent period detection for seamless time range management
|
|
44
|
+
- Extensive utility functions for common date operations and formatting
|
|
45
|
+
|
|
46
|
+
Whether you're building scheduling systems, financial applications, or any project requiring sophisticated time calculations, Chronos simplifies complex time-based operations in both TypeScript and JavaScript environments.
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
### Creating and Manipulating Periods
|
|
51
|
+
````typescript
|
|
52
|
+
import { Period, Precision, Interval } from 'chronos-ts';
|
|
53
|
+
|
|
54
|
+
// Create a period for the year 2023
|
|
55
|
+
const year2023 = new Period('2023-01-01', '2023-12-31', Precision.DAY);
|
|
56
|
+
|
|
57
|
+
// Check if a date is within the period
|
|
58
|
+
const isInPeriod = year2023.contains('2023-06-15'); // true
|
|
59
|
+
|
|
60
|
+
// Create a period for Q2 2023
|
|
61
|
+
const q2_2023 = new Period('2023-04-01', '2023-06-30', Precision.DAY);
|
|
62
|
+
|
|
63
|
+
// Check if periods overlap
|
|
64
|
+
const periodsOverlap = year2023.overlapsWith(q2_2023); // true
|
|
65
|
+
|
|
66
|
+
// Get the overlapping period
|
|
67
|
+
const overlap = year2023.overlap(q2_2023);
|
|
68
|
+
console.log(overlap?.startDate, overlap?.endDate); // 2023-04-01, 2023-06-30
|
|
69
|
+
|
|
70
|
+
// Subtract a period
|
|
71
|
+
const remainingPeriods = year2023.subtract(q2_2023);
|
|
72
|
+
console.log(remainingPeriods.length); // 2
|
|
73
|
+
console.log(remainingPeriods[0].startDate, remainingPeriods[0].endDate); // 2023-01-01, 2023-03-31
|
|
74
|
+
console.log(remainingPeriods[1].startDate, remainingPeriods[1].endDate); // 2023-07-01, 2023-12-31
|
|
75
|
+
|
|
76
|
+
// Create a period with an interval
|
|
77
|
+
const weeklyPeriod = new Period('2023-01-01', '2023-12-31', Precision.WEEK, Interval.weeks(1));
|
|
78
|
+
|
|
79
|
+
// Get dates in the interval
|
|
80
|
+
const weeklyDates = weeklyPeriod.getDatesInInterval();
|
|
81
|
+
console.log(weeklyDates?.length); // 53 (number of weeks in 2023)
|
|
82
|
+
|
|
83
|
+
// Renew a period
|
|
84
|
+
const nextYear = year2023.renew();
|
|
85
|
+
console.log(nextYear.startDate, nextYear.endDate); // 2024-01-01, 2024-12-31
|
|
86
|
+
|
|
87
|
+
// Use fluent API
|
|
88
|
+
const customPeriod = new Period('2023-01-01', '2023-12-31')
|
|
89
|
+
.setPrecision(Precision.MONTH)
|
|
90
|
+
.setInterval(Interval.months(3));
|
|
91
|
+
|
|
92
|
+
console.log(customPeriod.getDatesInInterval()?.length); // 5 (Jan, Apr, Jul, Oct, Jan)
|
|
93
|
+
````
|
|
94
|
+
|
|
95
|
+
### Working with Intervals
|
|
96
|
+
``` typescript
|
|
97
|
+
import { Interval } from 'chronos-ts';
|
|
98
|
+
|
|
99
|
+
const twoHours = Interval.hours(2);
|
|
100
|
+
console.log(twoHours.getMinutesInterval()); // 120
|
|
101
|
+
|
|
102
|
+
const threeWeeks = Interval.weeks(3);
|
|
103
|
+
console.log(threeWeeks.getMinutesInterval()); // 30240 (3 * 7 * 24 * 60)
|
|
104
|
+
|
|
105
|
+
const customInterval = new Interval(30, 2, 1, 0, 1); // 30 minutes, 2 hours, 1 day, 0 weeks, 1 month
|
|
106
|
+
console.log(customInterval.getMinutesInterval()); // 44310 (30 + 120 + 1440 + 43200)
|
|
107
|
+
```
|
|
108
|
+
### Using Utility Functions
|
|
109
|
+
```typescript
|
|
110
|
+
import { addToDate, subtractFromDate, formatDate, parseDate, getWeekNumber, isLeapYear, Precision } from 'chronos-ts';
|
|
111
|
+
|
|
112
|
+
const today = new Date();
|
|
113
|
+
|
|
114
|
+
// Add 3 months to today
|
|
115
|
+
const futureDate = addToDate(today, 3, Precision.MONTH);
|
|
116
|
+
|
|
117
|
+
// Subtract 2 weeks from today
|
|
118
|
+
const pastDate = subtractFromDate(today, 2, Precision.WEEK);
|
|
119
|
+
|
|
120
|
+
// Format a date
|
|
121
|
+
const formattedDate = formatDate(today, 'YYYY-MM-DD HH:mm:ss');
|
|
122
|
+
|
|
123
|
+
// Parse a date string
|
|
124
|
+
const parsedDate = parseDate('2023-06-15 14:30:00', 'YYYY-MM-DD HH:mm:ss');
|
|
125
|
+
|
|
126
|
+
// Get the week number
|
|
127
|
+
const weekNumber = getWeekNumber(today);
|
|
128
|
+
|
|
129
|
+
// Check if it's a leap year
|
|
130
|
+
const isLeap = isLeapYear(2024); // true
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
## API Reference
|
|
136
|
+
### Classes
|
|
137
|
+
#### Period
|
|
138
|
+
The `Period` class represents a time period with a start date, end date, precision, and optional interval.
|
|
139
|
+
|
|
140
|
+
##### Constructor
|
|
141
|
+
```typescript
|
|
142
|
+
constructor(start: string | Date, end: string | Date, precision: Precision = Precision.DAY, interval: Interval | null = null)
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
##### Methods
|
|
146
|
+
- `contains(date: string | Date): boolean`: Checks if the given date is within the period.
|
|
147
|
+
- `overlapsWith(other: Period): boolean`: Checks if this period overlaps with another period.
|
|
148
|
+
- `isAdjacentTo(other: Period): boolean`: Checks if this period is adjacent to another period.
|
|
149
|
+
- `getDatesInInterval(): Date[] | null`: Returns an array of dates within the period based on the interval.
|
|
150
|
+
- `getMinutesInInterval(): number`: Returns the number of minutes in the period.
|
|
151
|
+
- `getHoursInInterval(): number`: Returns the number of hours in the period.
|
|
152
|
+
- `getDaysInInterval(): number`: Returns the number of days in the period.
|
|
153
|
+
- `getWeeksInInterval(): number`: Returns the number of weeks in the period.
|
|
154
|
+
- `getMonthsInInterval(): number`: Returns the number of months in the period.
|
|
155
|
+
- `getYearsInInterval(): number`: Returns the number of years in the period.
|
|
156
|
+
- `length(): number`: Returns the length of the period in the specified precision.
|
|
157
|
+
- `overlap(other: Period): Period | null`: Returns the overlapping period with another period, if any.
|
|
158
|
+
- `subtract(other: Period): Period[]`: Subtracts another period from this period.
|
|
159
|
+
- `gap(other: Period): Period | null`: Returns the gap between this period and another period, if any.
|
|
160
|
+
- `symmetricDifference(other: Period): Period[]`: Returns the symmetric difference between this period and another period.
|
|
161
|
+
- `renew(): Period`: Creates a new period of the same length immediately following this period.
|
|
162
|
+
- `union(other: Period): Period[]`: Returns the union of this period with another period.
|
|
163
|
+
|
|
164
|
+
##### Fluent API Methods
|
|
165
|
+
|
|
166
|
+
`setStart(start: string | Date): this`: Sets the start date of the period.
|
|
167
|
+
`setEnd(end: string | Date): this`: Sets the end date of the period.
|
|
168
|
+
`setPrecision(precision: Precision): this`: Sets the precision of the period.
|
|
169
|
+
`setInterval(interval: Interval): this`: Sets the interval of the period.
|
|
170
|
+
|
|
171
|
+
#### Interval
|
|
172
|
+
The `Interval` class represents a time interval with minutes, hours, days, weeks, and months.
|
|
173
|
+
Static Methods
|
|
174
|
+
|
|
175
|
+
- `minutes(minutes: number): Interval:` Creates an interval with the specified number of minutes.
|
|
176
|
+
- `hours(hours: number): Interval`: Creates an interval with the specified number of hours.
|
|
177
|
+
- `days(days: number): Interval`: Creates an interval with the specified number of days.
|
|
178
|
+
- `weeks(weeks: number): Interval`: Creates an interval with the specified number of weeks.
|
|
179
|
+
- `months(months: number): Interval`: Creates an interval with the specified number of months.
|
|
180
|
+
|
|
181
|
+
##### Methods
|
|
182
|
+
|
|
183
|
+
`getMinutesInterval(): number`: Returns the total number of minutes in the interval.
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
#### Enums
|
|
187
|
+
##### Precision
|
|
188
|
+
The Precision enum represents different levels of time precision.
|
|
189
|
+
typescriptCopyenum Precision {
|
|
190
|
+
MINUTE = 'minute',
|
|
191
|
+
HOUR = 'hour',
|
|
192
|
+
DAY = 'day',
|
|
193
|
+
WEEK = 'week',
|
|
194
|
+
MONTH = 'month',
|
|
195
|
+
YEAR = 'year',
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
### Utility Function
|
|
199
|
+
The package includes various utility functions for working with dates:
|
|
200
|
+
|
|
201
|
+
- `getDatesWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of dates within the specified interval.
|
|
202
|
+
- `getWeeksWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of weeks within the specified interval.
|
|
203
|
+
- `getDaysWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of days within the specified interval.
|
|
204
|
+
- `getHoursWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of hours within the specified interval.
|
|
205
|
+
- `getMinutesWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of minutes within the specified interval.
|
|
206
|
+
- `getMonthsWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of months within the specified interval.
|
|
207
|
+
- `getYearsWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of years within the specified interval.
|
|
208
|
+
- `addToDate(date: Date, amount: number, unit: Precision): Date` - Adds the specified amount of time to a date.
|
|
209
|
+
- `subtractFromDate(date: Date, amount: number, unit: Precision): Date` - Subtracts the specified amount of time from a date.
|
|
210
|
+
- `isSameDay(date1: Date, date2: Date): boolean` - Checks if two dates are on the same day.
|
|
211
|
+
- `getWeekNumber(date: Date): number` - Returns the week number of a date.
|
|
212
|
+
- `getQuarter(date: Date): number` - Returns the quarter of a date.
|
|
213
|
+
- `isLeapYear(year: number): boolean` - Checks if a year is a leap year.
|
|
214
|
+
- `getDaysInMonth(year: number, month: number): number` - Returns the number of days in a month.
|
|
215
|
+
- `formatDate(date: Date, format: string): string`
|
|
216
|
+
- `parseDate(dateString: string, format: string): Date` - Parses a date string using the specified format.
|
|
217
|
+
- `range(start: number, end: number, step: number = 1): number[]` - Returns an array of numbers within the specified range.
|
|
218
|
+
|
|
219
|
+
### Real-world Scenarios
|
|
220
|
+
**1. Event Planning and Management**
|
|
221
|
+
The `Period` class can be used to represent events, while the `Interval` class can help manage recurring events.
|
|
222
|
+
``` typescript
|
|
223
|
+
import { Period, Interval, Precision } from 'chronos-ts';
|
|
224
|
+
|
|
225
|
+
// Create an event
|
|
226
|
+
const conference = new Period('2023-09-15 09:00', '2023-09-17 18:00', Precision.HOUR);
|
|
227
|
+
|
|
228
|
+
// Check if a specific time is during the conference
|
|
229
|
+
const isDuringConference = conference.contains('2023-09-16 14:30'); // true
|
|
230
|
+
|
|
231
|
+
// Create a recurring weekly meeting
|
|
232
|
+
const weeklyMeeting = new Period('2023-01-01 10:00', '2023-12-31 11:00', Precision.HOUR, Interval.weeks(1));
|
|
233
|
+
|
|
234
|
+
// Get all meeting dates
|
|
235
|
+
const meetingDates = weeklyMeeting.getDatesInInterval();
|
|
236
|
+
|
|
237
|
+
// Check for conflicts with the conference
|
|
238
|
+
const conflictingMeetings = meetingDates?.filter(date => conference.contains(date)) || [];
|
|
239
|
+
console.log(`There are ${conflictingMeetings.length} conflicting meetings during the conference.`);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
**2. Financial Reporting Periods**
|
|
243
|
+
Use the `Period` class to represent financial quarters and calculate year-to-date periods.
|
|
244
|
+
``` typescript
|
|
245
|
+
import { Period, Precision } from 'chronos-ts';
|
|
246
|
+
|
|
247
|
+
const q1_2023 = new Period('2023-01-01', '2023-03-31', Precision.DAY);
|
|
248
|
+
const q2_2023 = new Period('2023-04-01', '2023-06-30', Precision.DAY);
|
|
249
|
+
const q3_2023 = new Period('2023-07-01', '2023-09-30', Precision.DAY);
|
|
250
|
+
const q4_2023 = new Period('2023-10-01', '2023-12-31', Precision.DAY);
|
|
251
|
+
|
|
252
|
+
// Calculate Year-to-Date period
|
|
253
|
+
const ytd = (currentQuarter: Period): Period => {
|
|
254
|
+
return new Period('2023-01-01', currentQuarter.endDate, Precision.DAY);
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const q3YTD = ytd(q3_2023);
|
|
258
|
+
console.log(`Q3 YTD period: ${q3YTD.startDate.toDateString()} - ${q3YTD.endDate.toDateString()}`);
|
|
259
|
+
|
|
260
|
+
// Calculate quarter-over-quarter growth
|
|
261
|
+
const calculateQoQGrowth = (currentQuarter: number, previousQuarter: number): string => {
|
|
262
|
+
const growth = (currentQuarter - previousQuarter) / previousQuarter * 100;
|
|
263
|
+
return `${growth.toFixed(2)}%`;
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
const q2Revenue = 1000000;
|
|
267
|
+
const q3Revenue = 1200000;
|
|
268
|
+
console.log(`Q3 QoQ Growth: ${calculateQoQGrowth(q3Revenue, q2Revenue)}`);
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**3. Employee Leave Management**
|
|
272
|
+
Use the `Period` class to manage employee leave requests and calculate leave balances.
|
|
273
|
+
``` typescript
|
|
274
|
+
import { Period, Precision } from 'chronos-ts';
|
|
275
|
+
|
|
276
|
+
// Create a leave request period
|
|
277
|
+
const leaveRequest = new Period('2023-09-15', '2023-09-17', Precision.DAY);
|
|
278
|
+
|
|
279
|
+
// Check if the leave request overlaps with existing leave periods
|
|
280
|
+
const existingLeaves = [
|
|
281
|
+
new Period('2023-09-10', '2023-09-14', Precision.DAY),
|
|
282
|
+
new Period('2023-09-18', '2023-09-20', Precision.DAY),
|
|
283
|
+
];
|
|
284
|
+
|
|
285
|
+
const overlappingLeaves = existingLeaves.filter(leave => leaveRequest.overlapsWith(leave));
|
|
286
|
+
console.log(`There are ${overlappingLeaves.length} overlapping leave requests.`);
|
|
287
|
+
|
|
288
|
+
// Calculate remaining leave balance
|
|
289
|
+
const totalLeaveDays = 20;
|
|
290
|
+
const usedLeaveDays = existingLeaves.reduce((total, leave) => total + leave.length(), 0);
|
|
291
|
+
const remainingLeaveDays = totalLeaveDays - usedLeaveDays;
|
|
292
|
+
console.log(`Remaining leave days: ${remainingLeaveDays}`);
|
|
293
|
+
|
|
294
|
+
// Renew leave balance for the next year
|
|
295
|
+
const nextYearLeaveBalance = remainingLeaveDays > 0 ? new Period('2024-01-01', '2024-12-31') : null;
|
|
296
|
+
console.log(`Next year's leave balance: ${nextYearLeaveBalance ? nextYearLeaveBalance.length() : 0} days`);
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
**4. Project Timeline Management**
|
|
301
|
+
Use the Period class to manage project timelines and track overlapping tasks.
|
|
302
|
+
``` typescript
|
|
303
|
+
import { Period, Precision } from 'chronos-ts';
|
|
304
|
+
|
|
305
|
+
const projectTimeline = new Period('2023-01-01', '2023-12-31', Precision.DAY);
|
|
306
|
+
|
|
307
|
+
const tasks = [
|
|
308
|
+
new Period('2023-01-15', '2023-03-31', Precision.DAY),
|
|
309
|
+
new Period('2023-03-01', '2023-05-15', Precision.DAY),
|
|
310
|
+
new Period('2023-05-01', '2023-08-31', Precision.DAY),
|
|
311
|
+
new Period('2023-08-15', '2023-11-30', Precision.DAY),
|
|
312
|
+
];
|
|
313
|
+
|
|
314
|
+
// Find overlapping tasks
|
|
315
|
+
const findOverlappingTasks = (tasks: Period[]): [Period, Period][] => {
|
|
316
|
+
const overlaps: [Period, Period][] = [];
|
|
317
|
+
for (let i = 0; i < tasks.length; i++) {
|
|
318
|
+
for (let j = i + 1; j < tasks.length; j++) {
|
|
319
|
+
if (tasks[i].overlapsWith(tasks[j])) {
|
|
320
|
+
overlaps.push([tasks[i], tasks[j]]);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return overlaps;
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
const overlappingTasks = findOverlappingTasks(tasks);
|
|
328
|
+
console.log(`There are ${overlappingTasks.length} overlapping tasks in the project.`);
|
|
329
|
+
|
|
330
|
+
// Calculate project progress
|
|
331
|
+
const calculateProgress = (currentDate: Date): number => {
|
|
332
|
+
const daysPassed = projectTimeline.startDate.getMinutesInInterval() / (24 * 60);
|
|
333
|
+
const totalDays = projectTimeline.getDaysInInterval();
|
|
334
|
+
return (daysPassed / totalDays) * 100;
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
const currentProgress = calculateProgress(new Date('2023-06-15'));
|
|
338
|
+
console.log(`Project progress: ${currentProgress.toFixed(2)}%`);
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
**5. Subscription Billing Cycle Management**
|
|
342
|
+
Use the Period class to manage subscription periods and calculate renewal dates.
|
|
343
|
+
``` typescript
|
|
344
|
+
import { Period, Precision, addToDate } from 'chronos-ts';
|
|
345
|
+
|
|
346
|
+
class Subscription {
|
|
347
|
+
constructor(public startDate: Date, public plan: 'monthly' | 'annual') {}
|
|
348
|
+
|
|
349
|
+
getCurrentPeriod(): Period {
|
|
350
|
+
const endDate = addToDate(this.startDate, 1, this.plan === 'monthly' ? Precision.MONTH : Precision.YEAR);
|
|
351
|
+
return new Period(this.startDate, endDate, Precision.DAY);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
isActive(date: Date = new Date()): boolean {
|
|
355
|
+
return this.getCurrentPeriod().contains(date);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
getRenewalDate(): Date {
|
|
359
|
+
return this.getCurrentPeriod().endDate;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
renew(): void {
|
|
363
|
+
this.startDate = this.getRenewalDate();
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const monthlySubscription = new Subscription(new Date('2023-01-01'), 'monthly');
|
|
368
|
+
console.log(`Monthly subscription active: ${monthlySubscription.isActive()}`);
|
|
369
|
+
console.log(`Monthly subscription renewal date: ${monthlySubscription.getRenewalDate().toDateString()}`);
|
|
370
|
+
|
|
371
|
+
const annualSubscription = new Subscription(new Date('2023-01-01'), 'annual');
|
|
372
|
+
console.log(`Annual subscription active: ${annualSubscription.isActive()}`);
|
|
373
|
+
console.log(`Annual subscription renewal date: ${annualSubscription.getRenewalDate().toDateString()}`);
|
|
374
|
+
|
|
375
|
+
const annualSubscription = new Subscription(new Date('2023-01-01'), 'annual');
|
|
376
|
+
console.log(`Annual subscription active: ${annualSubscription.isActive()}`);
|
|
377
|
+
console.log(`Annual subscription renewal date: ${annualSubscription.getRenewalDate().toDateString()}`);
|
|
378
|
+
// Check if a subscription will be active on a future date
|
|
379
|
+
const futureDate = new Date('2023-12-15');
|
|
380
|
+
console.log(`Monthly subscription active on ${futureDate.toDateString()}: ${monthlySubscription.isActive(futureDate)}`);
|
|
381
|
+
console.log(`Annual subscription active on ${futureDate.toDateString()}: ${annualSubscription.isActive(futureDate)}`);
|
|
382
|
+
// Renew a subscription
|
|
383
|
+
monthlySubscription.renew();
|
|
384
|
+
console.log(`Monthly subscription renewed. New period: ${monthlySubscription.getCurrentPeriod().startDate.toDateString()} - ${monthlySubscription.getCurrentPeriod().endDate.toDateString()})`;
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
**6. Employee Shift Management**
|
|
388
|
+
|
|
389
|
+
Use the `Period` and `Interval` classes to manage employee shifts and calculate overtime.
|
|
390
|
+
|
|
391
|
+
```typescript
|
|
392
|
+
import { Period, Interval, Precision, addToDate } from 'chronos-ts';
|
|
393
|
+
|
|
394
|
+
class Shift extends Period {
|
|
395
|
+
constructor(public employee: string, start: Date, end: Date) {
|
|
396
|
+
super(start, end, Precision.MINUTE);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
getDuration(): number {
|
|
400
|
+
return this.getHoursInInterval();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
isOvertime(): boolean {
|
|
404
|
+
return this.getDuration() > 8;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
getOvertimeHours(): number {
|
|
408
|
+
return Math.max(0, this.getDuration() - 8);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
// Create a week's worth of shifts for an employee
|
|
413
|
+
const createWeekShifts = (employee: string, startDate: Date): Shift[] => {
|
|
414
|
+
const shifts: Shift[] = [];
|
|
415
|
+
for (let i = 0; i < 5; i++) { // Assuming 5-day work week
|
|
416
|
+
const shiftStart = addToDate(startDate, i, Precision.DAY);
|
|
417
|
+
shiftStart.setHours(9, 0, 0, 0); // 9 AM start
|
|
418
|
+
const shiftEnd = new Date(shiftStart);
|
|
419
|
+
shiftEnd.setHours(17, 0, 0, 0); // 5 PM end
|
|
420
|
+
shifts.push(new Shift(employee, shiftStart, shiftEnd));
|
|
421
|
+
}
|
|
422
|
+
return shifts;
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
const employeeShifts = createWeekShifts('John Doe', new Date('2023-06-19'));
|
|
426
|
+
|
|
427
|
+
// Calculate total hours worked and overtime
|
|
428
|
+
const totalHours = employeeShifts.reduce((sum, shift) => sum + shift.getDuration(), 0);
|
|
429
|
+
const overtimeHours = employeeShifts.reduce((sum, shift) => sum + shift.getOvertimeHours(), 0);
|
|
430
|
+
|
|
431
|
+
console.log(`Total hours worked: ${totalHours}`);
|
|
432
|
+
console.log(`Overtime hours: ${overtimeHours}`);
|
|
433
|
+
|
|
434
|
+
// Check for conflicting shifts
|
|
435
|
+
const hasConflict = (shifts: Shift[]): boolean => {
|
|
436
|
+
for (let i = 0; i < shifts.length; i++) {
|
|
437
|
+
for (let j = i + 1; j < shifts.length; j++) {
|
|
438
|
+
if (shifts[i].overlapsWith(shifts[j])) {
|
|
439
|
+
return true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
return false;
|
|
444
|
+
};
|
|
445
|
+
|
|
446
|
+
console.log(`Shifts have conflicts: ${hasConflict(employeeShifts)}`);
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
**7. Travel Itinerary Planning**
|
|
450
|
+
Use the `Period` class to manage travel itineraries and check for scheduling conflicts.
|
|
451
|
+
``` typescript
|
|
452
|
+
import { Period, Precision, addToDate } from 'chronos-ts';
|
|
453
|
+
|
|
454
|
+
class TravelEvent extends Period {
|
|
455
|
+
constructor(public description: string, start: Date, end: Date) {
|
|
456
|
+
super(start, end, Precision.MINUTE);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
class Itinerary {
|
|
461
|
+
events: TravelEvent[] = [];
|
|
462
|
+
|
|
463
|
+
addEvent(event: TravelEvent): void {
|
|
464
|
+
if (this.hasConflict(event)) {
|
|
465
|
+
throw new Error('Event conflicts with existing events');
|
|
466
|
+
}
|
|
467
|
+
this.events.push(event);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
hasConflict(newEvent: TravelEvent): boolean {
|
|
471
|
+
return this.events.some(event => event.overlapsWith(newEvent));
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
getDuration(): number {
|
|
475
|
+
if (this.events.length === 0) return 0;
|
|
476
|
+
const start = this.events.reduce((min, e) => e.startDate < min ? e.startDate : min, this.events[0].startDate);
|
|
477
|
+
const end = this.events.reduce((max, e) => e.endDate > max ? e.endDate : max, this.events[0].endDate);
|
|
478
|
+
return new Period(start, end, Precision.HOUR).getDaysInInterval();
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Create a travel itinerary
|
|
483
|
+
const itinerary = new Itinerary();
|
|
484
|
+
|
|
485
|
+
// Add events to the itinerary
|
|
486
|
+
try {
|
|
487
|
+
itinerary.addEvent(new TravelEvent('Flight to Paris', new Date('2023-07-01 10:00'), new Date('2023-07-01 12:00')));
|
|
488
|
+
itinerary.addEvent(new TravelEvent('Hotel Check-in', new Date('2023-07-01 14:00'), new Date('2023-07-01 15:00')));
|
|
489
|
+
itinerary.addEvent(new TravelEvent('Eiffel Tower Visit', new Date('2023-07-02 10:00'), new Date('2023-07-02 13:00')));
|
|
490
|
+
itinerary.addEvent(new TravelEvent('Louvre Museum', new Date('2023-07-03 09:00'), new Date('2023-07-03 12:00')));
|
|
491
|
+
itinerary.addEvent(new TravelEvent('Flight to Rome', new Date('2023-07-04 15:00'), new Date('2023-07-04 17:00')));
|
|
492
|
+
} catch (error) {
|
|
493
|
+
console.error('Error creating itinerary:', error.message);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
console.log(`Itinerary duration: ${itinerary.getDuration()} days`);
|
|
497
|
+
|
|
498
|
+
// Try to add a conflicting event
|
|
499
|
+
try {
|
|
500
|
+
itinerary.addEvent(new TravelEvent('Conflicting Event', new Date('2023-07-01 11:00'), new Date('2023-07-01 13:00')));
|
|
501
|
+
} catch (error) {
|
|
502
|
+
console.log('Caught conflicting event:', error.message);
|
|
503
|
+
}
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
## Contributing 🤝
|
|
507
|
+
Contributions, issues and feature requests are welcome. After cloning & setting up project locally, you can just submit a PR to this repo and it will be deployed once it's accepted. There is a list of TODOs in the [TODO.md](TODO.md) file. The project is still in its early stages, so there are plenty of opportunities to contribute.
|
|
508
|
+
|
|
509
|
+
## License 📝
|
|
510
|
+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
|
|
511
|
+
|
|
512
|
+
## Acknowledgements 🙏
|
|
513
|
+
- [spatie/period]((https://github.com/spatie/period)
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Interval = exports.Precision = exports.Period = void 0;
|
|
4
|
+
var period_1 = require("./period");
|
|
5
|
+
Object.defineProperty(exports, "Period", { enumerable: true, get: function () { return period_1.Period; } });
|
|
6
|
+
var precision_1 = require("./precision");
|
|
7
|
+
Object.defineProperty(exports, "Precision", { enumerable: true, get: function () { return precision_1.Precision; } });
|
|
8
|
+
var interval_1 = require("./interval");
|
|
9
|
+
Object.defineProperty(exports, "Interval", { enumerable: true, get: function () { return interval_1.Interval; } });
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export declare class Interval {
|
|
2
|
+
private minutes;
|
|
3
|
+
private hours;
|
|
4
|
+
private days;
|
|
5
|
+
private weeks;
|
|
6
|
+
private months;
|
|
7
|
+
private constructor();
|
|
8
|
+
static minutes(minutes: number): Interval;
|
|
9
|
+
static hours(hours: number): Interval;
|
|
10
|
+
static days(days: number): Interval;
|
|
11
|
+
static weeks(weeks: number): Interval;
|
|
12
|
+
static months(months: number): Interval;
|
|
13
|
+
getMinutesInterval(): number;
|
|
14
|
+
}
|
package/dist/interval.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Interval = void 0;
|
|
4
|
+
class Interval {
|
|
5
|
+
constructor(minutes = 0, hours = 0, days = 0, weeks = 0, months = 0) {
|
|
6
|
+
this.minutes = minutes;
|
|
7
|
+
this.hours = hours;
|
|
8
|
+
this.days = days;
|
|
9
|
+
this.weeks = weeks;
|
|
10
|
+
this.months = months;
|
|
11
|
+
}
|
|
12
|
+
static minutes(minutes) {
|
|
13
|
+
return new Interval(minutes);
|
|
14
|
+
}
|
|
15
|
+
static hours(hours) {
|
|
16
|
+
return new Interval(0, hours);
|
|
17
|
+
}
|
|
18
|
+
static days(days) {
|
|
19
|
+
return new Interval(0, 0, days);
|
|
20
|
+
}
|
|
21
|
+
static weeks(weeks) {
|
|
22
|
+
return new Interval(0, 0, 0, weeks);
|
|
23
|
+
}
|
|
24
|
+
static months(months) {
|
|
25
|
+
return new Interval(0, 0, 0, 0, months);
|
|
26
|
+
}
|
|
27
|
+
getMinutesInterval() {
|
|
28
|
+
return (this.minutes +
|
|
29
|
+
this.hours * 60 +
|
|
30
|
+
this.days * 24 * 60 +
|
|
31
|
+
this.weeks * 7 * 24 * 60 +
|
|
32
|
+
this.months * 30 * 24 * 60);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.Interval = Interval;
|
package/dist/period.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Interval } from './interval';
|
|
2
|
+
import { Precision } from './precision';
|
|
3
|
+
export declare class Period {
|
|
4
|
+
private startDate;
|
|
5
|
+
private endDate;
|
|
6
|
+
private precision;
|
|
7
|
+
private interval;
|
|
8
|
+
constructor(start: string | Date, end: string | Date, precision?: Precision, interval?: Interval | null);
|
|
9
|
+
contains(date: string | Date): boolean;
|
|
10
|
+
overlapsWith(other: Period): boolean;
|
|
11
|
+
isAdjacentTo(other: Period): boolean;
|
|
12
|
+
getDatesInInterval(): Date[] | null;
|
|
13
|
+
getMinutesInInterval(): number;
|
|
14
|
+
getHoursInInterval(): number;
|
|
15
|
+
getDaysInInterval(): number;
|
|
16
|
+
getWeeksInInterval(): number;
|
|
17
|
+
getMonthsInInterval(): number;
|
|
18
|
+
getYearsInInterval(): number;
|
|
19
|
+
length(): number;
|
|
20
|
+
overlap(other: Period): Period | null;
|
|
21
|
+
subtract(other: Period): Period[];
|
|
22
|
+
gap(other: Period): Period | null;
|
|
23
|
+
symmetricDifference(other: Period): Period[];
|
|
24
|
+
private mergeAdjacentPeriods;
|
|
25
|
+
renew(): Period;
|
|
26
|
+
union(other: Period): Period[];
|
|
27
|
+
setStart(start: string | Date): this;
|
|
28
|
+
setEnd(end: string | Date): this;
|
|
29
|
+
setPrecision(precision: Precision): this;
|
|
30
|
+
setInterval(interval: Interval): this;
|
|
31
|
+
}
|
package/dist/period.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Period = void 0;
|
|
4
|
+
const precision_1 = require("./precision");
|
|
5
|
+
const utils_1 = require("./utils");
|
|
6
|
+
class Period {
|
|
7
|
+
constructor(start, end, precision = precision_1.Precision.DAY, interval = null) {
|
|
8
|
+
this.startDate = new Date(start);
|
|
9
|
+
this.endDate = new Date(end);
|
|
10
|
+
this.precision = precision;
|
|
11
|
+
this.interval = interval;
|
|
12
|
+
}
|
|
13
|
+
contains(date) {
|
|
14
|
+
const checkDate = new Date(date);
|
|
15
|
+
return checkDate >= this.startDate && checkDate <= this.endDate;
|
|
16
|
+
}
|
|
17
|
+
overlapsWith(other) {
|
|
18
|
+
return this.startDate < other.endDate && this.endDate > other.startDate;
|
|
19
|
+
}
|
|
20
|
+
isAdjacentTo(other) {
|
|
21
|
+
const thisPrecisionMs = (0, precision_1.getPrecisionInMilliseconds)(this.precision);
|
|
22
|
+
const otherPrecisionMs = (0, precision_1.getPrecisionInMilliseconds)(other.precision);
|
|
23
|
+
const maxPrecisionMs = Math.max(thisPrecisionMs, otherPrecisionMs);
|
|
24
|
+
const thisEndTime = this.endDate.getTime();
|
|
25
|
+
const otherStartTime = other.startDate.getTime();
|
|
26
|
+
const thisStartTime = this.startDate.getTime();
|
|
27
|
+
const otherEndTime = other.endDate.getTime();
|
|
28
|
+
// Check for overlap
|
|
29
|
+
if (thisStartTime <= otherEndTime && otherStartTime <= thisEndTime) {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
// Check if the gap between periods is within one precision unit
|
|
33
|
+
const gap = Math.abs(otherStartTime - thisEndTime);
|
|
34
|
+
const reverseGap = Math.abs(thisStartTime - otherEndTime);
|
|
35
|
+
return ((gap > 0 && gap <= maxPrecisionMs) ||
|
|
36
|
+
(reverseGap > 0 && reverseGap <= maxPrecisionMs));
|
|
37
|
+
}
|
|
38
|
+
getDatesInInterval() {
|
|
39
|
+
if (this.interval) {
|
|
40
|
+
return (0, utils_1.getDatesWithInterval)(this.startDate, this.endDate, this.interval);
|
|
41
|
+
}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
getMinutesInInterval() {
|
|
45
|
+
return Math.floor((this.endDate.getTime() - this.startDate.getTime()) / (1000 * 60));
|
|
46
|
+
}
|
|
47
|
+
getHoursInInterval() {
|
|
48
|
+
return Math.floor(this.getMinutesInInterval() / 60);
|
|
49
|
+
}
|
|
50
|
+
getDaysInInterval() {
|
|
51
|
+
return Math.floor(this.getHoursInInterval() / 24);
|
|
52
|
+
}
|
|
53
|
+
getWeeksInInterval() {
|
|
54
|
+
return Math.floor(this.getDaysInInterval() / 7);
|
|
55
|
+
}
|
|
56
|
+
getMonthsInInterval() {
|
|
57
|
+
let months = (this.endDate.getFullYear() - this.startDate.getFullYear()) * 12;
|
|
58
|
+
months += this.endDate.getMonth() - this.startDate.getMonth();
|
|
59
|
+
// Adjust for month boundaries
|
|
60
|
+
if (this.endDate.getDate() < this.startDate.getDate()) {
|
|
61
|
+
months--;
|
|
62
|
+
}
|
|
63
|
+
return months;
|
|
64
|
+
}
|
|
65
|
+
getYearsInInterval() {
|
|
66
|
+
return Math.floor(this.getMonthsInInterval() / 12);
|
|
67
|
+
}
|
|
68
|
+
length() {
|
|
69
|
+
return Math.floor((this.endDate.getTime() - this.startDate.getTime()) /
|
|
70
|
+
(0, precision_1.getPrecisionInMilliseconds)(this.precision));
|
|
71
|
+
}
|
|
72
|
+
overlap(other) {
|
|
73
|
+
if (!this.overlapsWith(other)) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const start = new Date(Math.max(this.startDate.getTime(), other.startDate.getTime()));
|
|
77
|
+
const end = new Date(Math.min(this.endDate.getTime(), other.endDate.getTime()));
|
|
78
|
+
return new Period(start, end, this.precision);
|
|
79
|
+
}
|
|
80
|
+
subtract(other) {
|
|
81
|
+
if (!this.overlapsWith(other)) {
|
|
82
|
+
return [this];
|
|
83
|
+
}
|
|
84
|
+
const periods = [];
|
|
85
|
+
if (this.startDate < other.startDate) {
|
|
86
|
+
const endDate = new Date(other.startDate.getTime() - 1);
|
|
87
|
+
periods.push(new Period(this.startDate, endDate, this.precision));
|
|
88
|
+
}
|
|
89
|
+
if (this.endDate > other.endDate) {
|
|
90
|
+
const startDate = new Date(other.endDate.getTime() + 1);
|
|
91
|
+
periods.push(new Period(startDate, this.endDate, this.precision));
|
|
92
|
+
}
|
|
93
|
+
return periods;
|
|
94
|
+
}
|
|
95
|
+
gap(other) {
|
|
96
|
+
if (this.overlapsWith(other) || this.isAdjacentTo(other)) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
const precisionMs = (0, precision_1.getPrecisionInMilliseconds)(this.precision);
|
|
100
|
+
const start = this.endDate < other.startDate ? this.endDate : other.endDate;
|
|
101
|
+
const end = this.startDate > other.startDate ? this.startDate : other.startDate;
|
|
102
|
+
return new Period(new Date(start.getTime() + precisionMs), new Date(end.getTime() - precisionMs), this.precision);
|
|
103
|
+
}
|
|
104
|
+
symmetricDifference(other) {
|
|
105
|
+
const periods = [];
|
|
106
|
+
const overlapPeriod = this.overlap(other);
|
|
107
|
+
if (!overlapPeriod) {
|
|
108
|
+
// No overlap, return both periods
|
|
109
|
+
return [this, other].sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
110
|
+
}
|
|
111
|
+
const thisSubtracted = this.subtract(overlapPeriod);
|
|
112
|
+
const otherSubtracted = other.subtract(overlapPeriod);
|
|
113
|
+
periods.push(...thisSubtracted, ...otherSubtracted);
|
|
114
|
+
// Sort periods and merge adjacent ones
|
|
115
|
+
return this.mergeAdjacentPeriods(periods);
|
|
116
|
+
}
|
|
117
|
+
mergeAdjacentPeriods(periods) {
|
|
118
|
+
if (periods.length <= 1)
|
|
119
|
+
return periods;
|
|
120
|
+
periods.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
121
|
+
const merged = [periods[0]];
|
|
122
|
+
for (let i = 1; i < periods.length; i++) {
|
|
123
|
+
const current = periods[i];
|
|
124
|
+
const previous = merged[merged.length - 1];
|
|
125
|
+
if (previous.isAdjacentTo(current) || previous.overlapsWith(current)) {
|
|
126
|
+
merged[merged.length - 1] = new Period(previous.startDate, current.endDate > previous.endDate
|
|
127
|
+
? current.endDate
|
|
128
|
+
: previous.endDate, this.precision);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
merged.push(current);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return merged;
|
|
135
|
+
}
|
|
136
|
+
renew() {
|
|
137
|
+
const length = this.length();
|
|
138
|
+
const newStart = new Date(this.endDate.getTime() + (0, precision_1.getPrecisionInMilliseconds)(this.precision));
|
|
139
|
+
const newEnd = new Date(newStart.getTime() + length * (0, precision_1.getPrecisionInMilliseconds)(this.precision));
|
|
140
|
+
return new Period(newStart, newEnd, this.precision, this.interval);
|
|
141
|
+
}
|
|
142
|
+
union(other) {
|
|
143
|
+
if (this.overlapsWith(other) || this.isAdjacentTo(other)) {
|
|
144
|
+
const start = new Date(Math.min(this.startDate.getTime(), other.startDate.getTime()));
|
|
145
|
+
const end = new Date(Math.max(this.endDate.getTime(), other.endDate.getTime()));
|
|
146
|
+
return [new Period(start, end, this.precision)];
|
|
147
|
+
}
|
|
148
|
+
return [this, other].sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
149
|
+
}
|
|
150
|
+
// Fluent API methods
|
|
151
|
+
setStart(start) {
|
|
152
|
+
this.startDate = new Date(start);
|
|
153
|
+
return this;
|
|
154
|
+
}
|
|
155
|
+
setEnd(end) {
|
|
156
|
+
this.endDate = new Date(end);
|
|
157
|
+
return this;
|
|
158
|
+
}
|
|
159
|
+
setPrecision(precision) {
|
|
160
|
+
this.precision = precision;
|
|
161
|
+
return this;
|
|
162
|
+
}
|
|
163
|
+
setInterval(interval) {
|
|
164
|
+
this.interval = interval;
|
|
165
|
+
return this;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
exports.Period = Period;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.Precision = void 0;
|
|
4
|
+
exports.getPrecisionInMilliseconds = getPrecisionInMilliseconds;
|
|
5
|
+
var Precision;
|
|
6
|
+
(function (Precision) {
|
|
7
|
+
Precision["MINUTE"] = "minute";
|
|
8
|
+
Precision["HOUR"] = "hour";
|
|
9
|
+
Precision["DAY"] = "day";
|
|
10
|
+
Precision["WEEK"] = "week";
|
|
11
|
+
Precision["MONTH"] = "month";
|
|
12
|
+
Precision["YEAR"] = "year";
|
|
13
|
+
})(Precision || (exports.Precision = Precision = {}));
|
|
14
|
+
function getPrecisionInMilliseconds(precision) {
|
|
15
|
+
switch (precision) {
|
|
16
|
+
case Precision.MINUTE:
|
|
17
|
+
return 1000 * 60;
|
|
18
|
+
case Precision.HOUR:
|
|
19
|
+
return 1000 * 60 * 60;
|
|
20
|
+
case Precision.DAY:
|
|
21
|
+
return 1000 * 60 * 60 * 24;
|
|
22
|
+
case Precision.WEEK:
|
|
23
|
+
return 1000 * 60 * 60 * 24 * 7;
|
|
24
|
+
case Precision.MONTH:
|
|
25
|
+
return 1000 * 60 * 60 * 24 * 30;
|
|
26
|
+
case Precision.YEAR:
|
|
27
|
+
return 1000 * 60 * 60 * 24 * 365;
|
|
28
|
+
default:
|
|
29
|
+
throw new Error('Unsupported precision');
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Interval } from './interval';
|
|
2
|
+
import { Precision } from './precision';
|
|
3
|
+
export declare function getDatesWithInterval(start: Date, end: Date, interval: Interval): Date[];
|
|
4
|
+
export declare function getWeeksWithInterval(start: Date, end: Date, interval: Interval): Date[];
|
|
5
|
+
export declare function getDaysWithInterval(start: Date, end: Date, interval: Interval): Date[];
|
|
6
|
+
export declare function getHoursWithInterval(start: Date, end: Date, interval: Interval): Date[];
|
|
7
|
+
export declare function getMinutesWithInterval(start: Date, end: Date, interval: Interval): Date[];
|
|
8
|
+
export declare function getMonthsWithInterval(start: Date, end: Date, interval: Interval): Date[];
|
|
9
|
+
export declare function getYearsWithInterval(start: Date, end: Date, interval: Interval): Date[];
|
|
10
|
+
export declare function addToDate(date: Date, amount: number, unit: Precision): Date;
|
|
11
|
+
export declare function subtractFromDate(date: Date, amount: number, unit: Precision): Date;
|
|
12
|
+
export declare function isSameDay(date1: Date, date2: Date): boolean;
|
|
13
|
+
export declare function getWeekNumber(date: Date): number;
|
|
14
|
+
export declare function getQuarter(date: Date): number;
|
|
15
|
+
export declare function isLeapYear(year: number): boolean;
|
|
16
|
+
export declare function getDaysInMonth(year: number, month: number): number;
|
|
17
|
+
export declare function formatDate(date: Date, format: string): string;
|
|
18
|
+
export declare function parseDate(dateString: string, format: string): Date;
|
|
19
|
+
export declare function range(start: number, end: number, step?: number): number[];
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getDatesWithInterval = getDatesWithInterval;
|
|
4
|
+
exports.getWeeksWithInterval = getWeeksWithInterval;
|
|
5
|
+
exports.getDaysWithInterval = getDaysWithInterval;
|
|
6
|
+
exports.getHoursWithInterval = getHoursWithInterval;
|
|
7
|
+
exports.getMinutesWithInterval = getMinutesWithInterval;
|
|
8
|
+
exports.getMonthsWithInterval = getMonthsWithInterval;
|
|
9
|
+
exports.getYearsWithInterval = getYearsWithInterval;
|
|
10
|
+
exports.addToDate = addToDate;
|
|
11
|
+
exports.subtractFromDate = subtractFromDate;
|
|
12
|
+
exports.isSameDay = isSameDay;
|
|
13
|
+
exports.getWeekNumber = getWeekNumber;
|
|
14
|
+
exports.getQuarter = getQuarter;
|
|
15
|
+
exports.isLeapYear = isLeapYear;
|
|
16
|
+
exports.getDaysInMonth = getDaysInMonth;
|
|
17
|
+
exports.formatDate = formatDate;
|
|
18
|
+
exports.parseDate = parseDate;
|
|
19
|
+
exports.range = range;
|
|
20
|
+
const precision_1 = require("./precision");
|
|
21
|
+
function getDatesWithInterval(start, end, interval) {
|
|
22
|
+
const dates = [];
|
|
23
|
+
const current = new Date(start);
|
|
24
|
+
while (current <= end) {
|
|
25
|
+
dates.push(new Date(current));
|
|
26
|
+
current.setMinutes(current.getMinutes() + interval.getMinutesInterval());
|
|
27
|
+
}
|
|
28
|
+
return dates;
|
|
29
|
+
}
|
|
30
|
+
function getWeeksWithInterval(start, end, interval) {
|
|
31
|
+
const weeks = [];
|
|
32
|
+
let current = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), start.getUTCDate()));
|
|
33
|
+
// Move to the start of the week (Sunday)
|
|
34
|
+
current.setUTCDate(current.getUTCDate() - current.getUTCDay());
|
|
35
|
+
while (current <= end) {
|
|
36
|
+
weeks.push(new Date(current));
|
|
37
|
+
// Add the interval in minutes, converted to milliseconds
|
|
38
|
+
current = new Date(current.getTime() + interval.getMinutesInterval() * 60000);
|
|
39
|
+
// Ensure we're at the start of the week
|
|
40
|
+
current.setUTCDate(current.getUTCDate() - current.getUTCDay());
|
|
41
|
+
}
|
|
42
|
+
return weeks;
|
|
43
|
+
}
|
|
44
|
+
function getDaysWithInterval(start, end, interval) {
|
|
45
|
+
const days = [];
|
|
46
|
+
const current = new Date(start);
|
|
47
|
+
current.setUTCHours(0, 0, 0, 0);
|
|
48
|
+
while (current <= end) {
|
|
49
|
+
days.push(new Date(current));
|
|
50
|
+
current.setUTCDate(current.getUTCDate() + interval.getMinutesInterval() / (24 * 60));
|
|
51
|
+
}
|
|
52
|
+
return days;
|
|
53
|
+
}
|
|
54
|
+
function getHoursWithInterval(start, end, interval) {
|
|
55
|
+
const hours = [];
|
|
56
|
+
const current = new Date(start);
|
|
57
|
+
current.setMinutes(0, 0, 0);
|
|
58
|
+
while (current <= end) {
|
|
59
|
+
hours.push(new Date(current));
|
|
60
|
+
current.setHours(current.getHours() + interval.getMinutesInterval() / 60);
|
|
61
|
+
}
|
|
62
|
+
return hours;
|
|
63
|
+
}
|
|
64
|
+
function getMinutesWithInterval(start, end, interval) {
|
|
65
|
+
const minutes = [];
|
|
66
|
+
const current = new Date(start);
|
|
67
|
+
current.setSeconds(0, 0);
|
|
68
|
+
while (current <= end) {
|
|
69
|
+
minutes.push(new Date(current));
|
|
70
|
+
current.setMinutes(current.getMinutes() + interval.getMinutesInterval());
|
|
71
|
+
}
|
|
72
|
+
return minutes;
|
|
73
|
+
}
|
|
74
|
+
function getMonthsWithInterval(start, end, interval) {
|
|
75
|
+
const months = [];
|
|
76
|
+
const current = new Date(Date.UTC(start.getUTCFullYear(), start.getUTCMonth(), 1));
|
|
77
|
+
while (current <= end) {
|
|
78
|
+
months.push(new Date(current));
|
|
79
|
+
current.setUTCMonth(current.getUTCMonth() + interval.getMinutesInterval() / (30 * 24 * 60));
|
|
80
|
+
}
|
|
81
|
+
return months;
|
|
82
|
+
}
|
|
83
|
+
function getYearsWithInterval(start, end, interval) {
|
|
84
|
+
const years = [];
|
|
85
|
+
const current = new Date(Date.UTC(start.getUTCFullYear(), 0, 1));
|
|
86
|
+
while (current <= end) {
|
|
87
|
+
years.push(new Date(current));
|
|
88
|
+
// Calculate the number of years to add based on the interval
|
|
89
|
+
const yearsToAdd = Math.max(1, Math.round(interval.getMinutesInterval() / 525600)); // 525600 minutes in a year
|
|
90
|
+
current.setUTCFullYear(current.getUTCFullYear() + yearsToAdd);
|
|
91
|
+
}
|
|
92
|
+
return years;
|
|
93
|
+
}
|
|
94
|
+
function addToDate(date, amount, unit) {
|
|
95
|
+
const newDate = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), date.getUTCHours(), date.getUTCMinutes(), date.getUTCSeconds(), date.getUTCMilliseconds()));
|
|
96
|
+
switch (unit) {
|
|
97
|
+
case precision_1.Precision.MINUTE:
|
|
98
|
+
newDate.setUTCMinutes(newDate.getUTCMinutes() + amount);
|
|
99
|
+
break;
|
|
100
|
+
case precision_1.Precision.HOUR:
|
|
101
|
+
newDate.setUTCHours(newDate.getUTCHours() + amount);
|
|
102
|
+
break;
|
|
103
|
+
case precision_1.Precision.DAY:
|
|
104
|
+
newDate.setUTCDate(newDate.getUTCDate() + amount);
|
|
105
|
+
break;
|
|
106
|
+
case precision_1.Precision.WEEK:
|
|
107
|
+
newDate.setUTCDate(newDate.getUTCDate() + amount * 7);
|
|
108
|
+
break;
|
|
109
|
+
case precision_1.Precision.MONTH:
|
|
110
|
+
newDate.setUTCMonth(newDate.getUTCMonth() + amount);
|
|
111
|
+
if (newDate.getUTCDate() !== date.getUTCDate()) {
|
|
112
|
+
newDate.setUTCDate(0); // Set to last day of previous month
|
|
113
|
+
}
|
|
114
|
+
break;
|
|
115
|
+
case precision_1.Precision.YEAR:
|
|
116
|
+
newDate.setUTCFullYear(newDate.getUTCFullYear() + amount);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
return newDate;
|
|
120
|
+
}
|
|
121
|
+
function subtractFromDate(date, amount, unit) {
|
|
122
|
+
return addToDate(date, -amount, unit);
|
|
123
|
+
}
|
|
124
|
+
function isSameDay(date1, date2) {
|
|
125
|
+
return (date1.getFullYear() === date2.getFullYear() &&
|
|
126
|
+
date1.getMonth() === date2.getMonth() &&
|
|
127
|
+
date1.getDate() === date2.getDate());
|
|
128
|
+
}
|
|
129
|
+
function getWeekNumber(date) {
|
|
130
|
+
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
131
|
+
const dayNum = d.getUTCDay() || 7;
|
|
132
|
+
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
|
133
|
+
const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
|
|
134
|
+
return Math.ceil(((d.getTime() - yearStart.getTime()) / 86400000 + 1) / 7);
|
|
135
|
+
}
|
|
136
|
+
function getQuarter(date) {
|
|
137
|
+
return Math.floor(date.getMonth() / 3) + 1;
|
|
138
|
+
}
|
|
139
|
+
function isLeapYear(year) {
|
|
140
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
141
|
+
}
|
|
142
|
+
function getDaysInMonth(year, month) {
|
|
143
|
+
return new Date(year, month + 1, 0).getDate();
|
|
144
|
+
}
|
|
145
|
+
function formatDate(date, format) {
|
|
146
|
+
const pad = (n) => n.toString().padStart(2, '0');
|
|
147
|
+
const map = {
|
|
148
|
+
YYYY: date.getFullYear().toString(),
|
|
149
|
+
MM: pad(date.getMonth() + 1),
|
|
150
|
+
DD: pad(date.getDate()),
|
|
151
|
+
HH: pad(date.getHours()),
|
|
152
|
+
mm: pad(date.getMinutes()),
|
|
153
|
+
ss: pad(date.getSeconds()),
|
|
154
|
+
};
|
|
155
|
+
return format.replace(/YYYY|MM|DD|HH|mm|ss/gi, (matched) => map[matched]);
|
|
156
|
+
}
|
|
157
|
+
function parseDate(dateString, format) {
|
|
158
|
+
const map = {};
|
|
159
|
+
const formatParts = format.match(/YYYY|MM|DD|HH|mm|ss/gi) || [];
|
|
160
|
+
const dateParts = dateString.match(/\d+/g) || [];
|
|
161
|
+
formatParts.forEach((part, index) => {
|
|
162
|
+
map[part] = parseInt(dateParts[index]);
|
|
163
|
+
});
|
|
164
|
+
return new Date(map['YYYY'] || 0, (map['MM'] || 1) - 1, map['DD'] || 1, map['HH'] || 0, map['mm'] || 0, map['ss'] || 0);
|
|
165
|
+
}
|
|
166
|
+
function range(start, end, step = 1) {
|
|
167
|
+
return Array.from({ length: Math.floor((end - start) / step) + 1 }, (_, i) => start + i * step);
|
|
168
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "chronos-ts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A comprehensive TypeScript package for handling time periods, intervals, and date-related operations.",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"build": "tsc",
|
|
9
|
+
"test": "jest",
|
|
10
|
+
"format": "prettier --write 'src/**/*.{ts,js,json,md}'",
|
|
11
|
+
"lint": "eslint 'src/**/*.{ts,js}'",
|
|
12
|
+
"prepare": "npm run build",
|
|
13
|
+
"prepublishOnly": "npm run format && npm run lint && npm test",
|
|
14
|
+
"postpublish": "git push --follow-tags"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"time",
|
|
18
|
+
"date",
|
|
19
|
+
"period",
|
|
20
|
+
"interval",
|
|
21
|
+
"duration",
|
|
22
|
+
"range",
|
|
23
|
+
"typescript",
|
|
24
|
+
"calendar",
|
|
25
|
+
"datetime",
|
|
26
|
+
"time-management",
|
|
27
|
+
"date-range",
|
|
28
|
+
"date-manipulation",
|
|
29
|
+
"time-utils",
|
|
30
|
+
"date-utils",
|
|
31
|
+
"temporal",
|
|
32
|
+
"scheduling",
|
|
33
|
+
"time-period",
|
|
34
|
+
"date-interval",
|
|
35
|
+
"time-arithmetic",
|
|
36
|
+
"date-arithmetic"
|
|
37
|
+
],
|
|
38
|
+
"files": [
|
|
39
|
+
"dist/**",
|
|
40
|
+
"README.md"
|
|
41
|
+
],
|
|
42
|
+
"repository": {
|
|
43
|
+
"type": "git",
|
|
44
|
+
"url": "https://github.com/hendurhance/chronos-ts.git"
|
|
45
|
+
},
|
|
46
|
+
"author": "Josiah Endurance",
|
|
47
|
+
"homepage": "https://github.com/hendurhance/chronos-ts#readme",
|
|
48
|
+
"contributors": [
|
|
49
|
+
{
|
|
50
|
+
"name": "Josiah Endurance",
|
|
51
|
+
"email": "hendurhance.dev@gmail.com"
|
|
52
|
+
}
|
|
53
|
+
],
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@eslint/js": "^9.10.0",
|
|
57
|
+
"@types/eslint__js": "^8.42.3",
|
|
58
|
+
"@types/jest": "^29.5.13",
|
|
59
|
+
"@types/node": "^22.5.5",
|
|
60
|
+
"eslint": "^9.10.0",
|
|
61
|
+
"globals": "^15.9.0",
|
|
62
|
+
"jest": "^29.7.0",
|
|
63
|
+
"prettier": "^3.3.3",
|
|
64
|
+
"ts-jest": "^29.2.5",
|
|
65
|
+
"typescript": "^5.6.2",
|
|
66
|
+
"typescript-eslint": "^8.6.0"
|
|
67
|
+
}
|
|
68
|
+
}
|