chronos-ts 1.0.3 → 2.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/README.md CHANGED
@@ -1,513 +1,331 @@
1
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
2
 
28
- # or
29
- yarn add chronos-ts
3
+ [![npm version](https://img.shields.io/npm/v/chronos-ts.svg)](https://www.npmjs.com/package/chronos-ts)
4
+ [![npm downloads](https://img.shields.io/npm/dt/chronos-ts.svg)](https://www.npmjs.com/package/chronos-ts)
5
+ [![tests](https://img.shields.io/badge/tests-passing-brightgreen.svg)](https://github.com/hendurhance/chronos-ts/actions)
6
+ [![build](https://img.shields.io/badge/build-passing-brightgreen.svg)](https://github.com/hendurhance/chronos-ts/actions)
7
+ [![coverage](https://img.shields.io/badge/coverage-unknown-lightgrey.svg)](https://github.com/hendurhance/chronos-ts/actions)
30
8
 
31
- # or
9
+ **Chronos-ts** — named after the Greek god of time — is a comprehensive TypeScript library for date and time manipulation. Version 2.0 is a complete rewrite inspired by [Carbon PHP](https://carbon.nesbot.com/), bringing modern, intuitive date handling to TypeScript and JavaScript.
32
10
 
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.
11
+ > [!WARNING]
12
+ > This is a major rewrite (v2.0) and is not backward compatible with v1.x. Please refer to the [migration guide](MIGRATION.md) for details.
47
13
 
14
+ ## ✨ Features
48
15
 
49
- ## Usage
50
- ### Creating and Manipulating Periods
51
- ````typescript
52
- import { Period, Precision, Interval } from 'chronos-ts';
16
+ - 🎯 **Intuitive API** — Fluent, chainable methods for all date operations
17
+ - 📅 **Immutable by Default** — All operations return new instances
18
+ - 🌍 **Timezone Support** — Built-in timezone handling with DST awareness
19
+ - 🌐 **Internationalization** Extensible locale system with human-readable output
20
+ - ⏱️ **Intervals** — Powerful duration/interval handling (like CarbonInterval)
21
+ - 📆 **Periods** — Date range iteration with filtering and transformations
22
+ - 📋 **Period Collections** — Manage and analyze multiple date periods easily
23
+ - 📐 **Type-Safe** — Full TypeScript support with comprehensive types
24
+ - 🪶 **Zero Dependencies** — No external runtime dependencies
53
25
 
54
- // Create a period for the year 2023
55
- const year2023 = new Period('2023-01-01', '2023-12-31', Precision.DAY);
26
+ ## 📦 Installation
56
27
 
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);
28
+ ```bash
29
+ npm install chronos-ts
62
30
 
63
- // Check if periods overlap
64
- const periodsOverlap = year2023.overlapsWith(q2_2023); // true
31
+ # or
32
+ yarn add chronos-ts
65
33
 
66
- // Get the overlapping period
67
- const overlap = year2023.overlap(q2_2023);
68
- console.log(overlap?.getStartDate(), overlap?.getEndDate()); // 2023-04-01, 2023-06-30
34
+ # or
35
+ pnpm add chronos-ts
36
+ ```
69
37
 
70
- // Subtract a period
71
- const remainingPeriods = year2023.subtract(q2_2023);
72
- 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
38
+ ## 🚀 Quick Start
75
39
 
76
- // Create a period with an interval
77
- const weeklyPeriod = new Period('2023-01-01', '2023-12-31', Precision.WEEK, Interval.weeks(1));
40
+ ```typescript
41
+ import { Chronos, ChronosInterval, ChronosPeriod } from 'chronos-ts';
42
+
43
+ // Create dates
44
+ const now = Chronos.now();
45
+ const birthday = Chronos.create(1990, 6, 15);
46
+ const parsed = Chronos.parse('2024-03-15T10:30:00');
47
+
48
+ // Manipulate dates
49
+ const nextWeek = now.addWeeks(1);
50
+ const lastMonth = now.subtractMonths(1);
51
+ const startOfDay = now.startOf('day');
52
+
53
+ // Format dates
54
+ console.log(now.format('YYYY-MM-DD HH:mm:ss')); // "2024-03-15 14:30:45"
55
+ console.log(now.diffForHumans(birthday)); // "33 years ago"
56
+
57
+ // Work with intervals
58
+ const interval = ChronosInterval.create({ hours: 2, minutes: 30 });
59
+ console.log(interval.forHumans()); // "2 hours 30 minutes"
60
+
61
+ // Iterate over periods
62
+ const thisMonth = ChronosPeriod.thisMonth();
63
+ for (const day of thisMonth) {
64
+ console.log(day.format('YYYY-MM-DD'));
65
+ }
66
+ ```
78
67
 
79
- // Get dates in the interval
80
- const weeklyDates = weeklyPeriod.getDatesInInterval();
81
- console.log(weeklyDates?.length); // 53 (number of weeks in 2023)
68
+ ## 📖 API Reference
82
69
 
83
- // Renew a period
84
- const nextYear = year2023.renew();
85
- console.log(nextYear.getStartDate(), nextYear.getEndDate()); // 2024-01-01, 2024-12-31
70
+ For detailed documentation on all classes and methods, please refer to the [API Reference](API_REFERENCE.md).
86
71
 
87
- // Use fluent API
88
- const customPeriod = new Period('2023-01-01', '2023-12-31')
89
- .setPrecision(Precision.MONTH)
90
- .setInterval(Interval.months(3));
72
+ ### Core Classes
91
73
 
92
- console.log(customPeriod.getDatesInInterval()?.length); // 5 (Jan, Apr, Jul, Oct, Jan)
93
- ````
74
+ - **[Chronos](API_REFERENCE.md#chronos)**: The main class for date/time manipulation.
75
+ - **[ChronosInterval](API_REFERENCE.md#chronosinterval)**: Represents a duration of time.
76
+ - **[ChronosPeriod](API_REFERENCE.md#chronosperiod)**: Represents a date range or schedule.
77
+ - **[ChronosPeriodCollection](API_REFERENCE.md#chronosperiodcollection)**: Manages collections of periods.
78
+ - **[ChronosTimezone](API_REFERENCE.md#chronostimezone)**: Timezone utilities.
94
79
 
95
- ### Working with Intervals
96
- ``` typescript
97
- import { Interval } from 'chronos-ts';
80
+ ---
98
81
 
99
- const twoHours = Interval.hours(2);
100
- console.log(twoHours.getMinutesInterval()); // 120
82
+ ## 🌍 Localization
101
83
 
102
- const threeWeeks = Interval.weeks(3);
103
- console.log(threeWeeks.getMinutesInterval()); // 30240 (3 * 7 * 24 * 60)
84
+ Chronos-ts includes built-in support for English and Spanish, with an extensible locale system.
104
85
 
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
86
  ```typescript
110
- import { addToDate, subtractFromDate, formatDate, parseDate, getWeekNumber, isLeapYear, Precision } from 'chronos-ts';
111
-
112
- const today = new Date();
87
+ import { Chronos, registerLocale, getLocale } from 'chronos-ts';
88
+
89
+ // Use built-in locale
90
+ const date = Chronos.now().locale('es');
91
+ date.format('dddd, D [de] MMMM [de] YYYY');
92
+ // "viernes, 15 de marzo de 2024"
93
+
94
+ // Human-readable in Spanish
95
+ date.diffForHumans(Chronos.yesterday());
96
+ // "hace 1 día"
97
+
98
+ // Register custom locale
99
+ registerLocale({
100
+ code: 'de',
101
+ months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni',
102
+ 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'],
103
+ monthsShort: ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',
104
+ 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'],
105
+ weekdays: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch',
106
+ 'Donnerstag', 'Freitag', 'Samstag'],
107
+ weekdaysShort: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
108
+ weekdaysMin: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
109
+ relativeTime: {
110
+ future: 'in %s',
111
+ past: 'vor %s',
112
+ s: 'wenigen Sekunden',
113
+ ss: '%d Sekunden',
114
+ m: 'einer Minute',
115
+ mm: '%d Minuten',
116
+ h: 'einer Stunde',
117
+ hh: '%d Stunden',
118
+ d: 'einem Tag',
119
+ dd: '%d Tagen',
120
+ M: 'einem Monat',
121
+ MM: '%d Monaten',
122
+ y: 'einem Jahr',
123
+ yy: '%d Jahren',
124
+ },
125
+ });
126
+ ```
113
127
 
114
- // Add 3 months to today
115
- const futureDate = addToDate(today, 3, Precision.MONTH);
128
+ ---
116
129
 
117
- // Subtract 2 weeks from today
118
- const pastDate = subtractFromDate(today, 2, Precision.WEEK);
130
+ ## 🔧 Configuration
119
131
 
120
- // Format a date
121
- const formattedDate = formatDate(today, 'YYYY-MM-DD HH:mm:ss');
132
+ ```typescript
133
+ import { Chronos } from 'chronos-ts';
122
134
 
123
- // Parse a date string
124
- const parsedDate = parseDate('2023-06-15 14:30:00', 'YYYY-MM-DD HH:mm:ss');
135
+ // Set global configuration
136
+ Chronos.configure({
137
+ defaultTimezone: 'America/New_York',
138
+ defaultLocale: 'en',
139
+ });
125
140
 
126
- // Get the week number
127
- const weekNumber = getWeekNumber(today);
141
+ // Set test/mock time (useful for testing)
142
+ Chronos.setTestNow(Chronos.create(2024, 1, 1));
143
+ const now = Chronos.now(); // Returns 2024-01-01
128
144
 
129
- // Check if it's a leap year
130
- const isLeap = isLeapYear(2024); // true
145
+ // Reset test time
146
+ Chronos.setTestNow(null);
131
147
  ```
132
148
 
149
+ ---
133
150
 
151
+ ## 📚 Real-World Examples
134
152
 
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
- ```
153
+ ### Event Planning
144
154
 
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
155
  ```typescript
190
- enum Precision {
191
- MINUTE = 'minute',
192
- HOUR = 'hour',
193
- DAY = 'day',
194
- WEEK = 'week',
195
- MONTH = 'month',
196
- YEAR = 'year',
197
- }
198
- ```
199
- ### Utility Function
200
- The package includes various utility functions for working with dates:
201
-
202
- - `getDatesWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of dates within the specified interval.
203
- - `getWeeksWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of weeks within the specified interval.
204
- - `getDaysWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of days within the specified interval.
205
- - `getHoursWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of hours within the specified interval.
206
- - `getMinutesWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of minutes within the specified interval.
207
- - `getMonthsWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of months within the specified interval.
208
- - `getYearsWithInterval(start: Date, end: Date, interval: Interval): Date[]` - Returns an array of years within the specified interval.
209
- - `addToDate(date: Date, amount: number, unit: Precision): Date` - Adds the specified amount of time to a date.
210
- - `subtractFromDate(date: Date, amount: number, unit: Precision): Date` - Subtracts the specified amount of time from a date.
211
- - `isSameDay(date1: Date, date2: Date): boolean` - Checks if two dates are on the same day.
212
- - `getWeekNumber(date: Date): number` - Returns the week number of a date.
213
- - `getQuarter(date: Date): number` - Returns the quarter of a date.
214
- - `isLeapYear(year: number): boolean` - Checks if a year is a leap year.
215
- - `getDaysInMonth(year: number, month: number): number` - Returns the number of days in a month.
216
- - `formatDate(date: Date, format: string): string`
217
- - `parseDate(dateString: string, format: string): Date` - Parses a date string using the specified format.
218
- - `range(start: number, end: number, step: number = 1): number[]` - Returns an array of numbers within the specified range.
219
-
220
- ### Real-world Scenarios
221
- **1. Event Planning and Management**
222
- The `Period` class can be used to represent events, while the `Interval` class can help manage recurring events.
223
- ``` typescript
224
- import { Period, Interval, Precision } from 'chronos-ts';
225
-
226
- // Create an event
227
- const conference = new Period('2023-09-15 09:00', '2023-09-17 18:00', Precision.HOUR);
228
-
229
- // Check if a specific time is during the conference
230
- const isDuringConference = conference.contains('2023-09-16 14:30'); // true
231
-
232
- // Create a recurring weekly meeting
233
- const weeklyMeeting = new Period('2023-01-01 10:00', '2023-12-31 11:00', Precision.HOUR, Interval.weeks(1));
234
-
235
- // Get all meeting dates
236
- const meetingDates = weeklyMeeting.getDatesInInterval();
237
-
238
- // Check for conflicts with the conference
239
- const conflictingMeetings = meetingDates?.filter(date => conference.contains(date)) || [];
240
- console.log(`There are ${conflictingMeetings.length} conflicting meetings during the conference.`);
241
- ```
242
-
243
- **2. Financial Reporting Periods**
244
- Use the `Period` class to represent financial quarters and calculate year-to-date periods.
245
- ``` typescript
246
- import { Period, Precision } from 'chronos-ts';
156
+ import { Chronos, ChronosPeriod, ChronosInterval } from 'chronos-ts';
247
157
 
248
- const q1_2023 = new Period('2023-01-01', '2023-03-31', Precision.DAY);
249
- const q2_2023 = new Period('2023-04-01', '2023-06-30', Precision.DAY);
250
- const q3_2023 = new Period('2023-07-01', '2023-09-30', Precision.DAY);
251
- const q4_2023 = new Period('2023-10-01', '2023-12-31', Precision.DAY);
252
-
253
- // Calculate Year-to-Date period
254
- const ytd = (currentQuarter: Period): Period => {
255
- return new Period('2023-01-01', currentQuarter.endDate, Precision.DAY);
158
+ // Conference dates
159
+ const conference = {
160
+ start: Chronos.create(2024, 6, 15, 9, 0),
161
+ end: Chronos.create(2024, 6, 17, 18, 0),
256
162
  };
257
163
 
258
- const q3YTD = ytd(q3_2023);
259
- console.log(`Q3 YTD period: ${q3YTD.getStartDate().toDateString()} - ${q3YTD.getEndDate().toDateString()}`);
260
-
261
- // Calculate quarter-over-quarter growth
262
- const calculateQoQGrowth = (currentQuarter: number, previousQuarter: number): string => {
263
- const growth = (currentQuarter - previousQuarter) / previousQuarter * 100;
264
- return `${growth.toFixed(2)}%`;
265
- };
266
-
267
- const q2Revenue = 1000000;
268
- const q3Revenue = 1200000;
269
- console.log(`Q3 QoQ Growth: ${calculateQoQGrowth(q3Revenue, q2Revenue)}`);
164
+ // Check if a session conflicts
165
+ const session = Chronos.create(2024, 6, 16, 14, 0);
166
+ const isDuringConference = session.isBetween(conference.start, conference.end);
167
+
168
+ // Create weekly recurring meeting
169
+ const meetings = ChronosPeriod
170
+ .create(
171
+ Chronos.now(),
172
+ Chronos.now().addMonths(3),
173
+ { weeks: 1 }
174
+ )
175
+ .filter(date => date.dayOfWeek === 1) // Mondays only
176
+ .toArray();
270
177
  ```
271
178
 
272
- **3. Employee Leave Management**
273
- Use the `Period` class to manage employee leave requests and calculate leave balances.
274
- ``` typescript
275
- import { Period, Precision } from 'chronos-ts';
276
-
277
- // Create a leave request period
278
- const leaveRequest = new Period('2023-09-15', '2023-09-17', Precision.DAY);
179
+ ### Subscription Management
279
180
 
280
- // Check if the leave request overlaps with existing leave periods
281
- const existingLeaves = [
282
- new Period('2023-09-10', '2023-09-14', Precision.DAY),
283
- new Period('2023-09-18', '2023-09-20', Precision.DAY),
284
- ];
285
-
286
- const overlappingLeaves = existingLeaves.filter(leave => leaveRequest.overlapsWith(leave));
287
- console.log(`There are ${overlappingLeaves.length} overlapping leave requests.`);
288
-
289
- // Calculate remaining leave balance
290
- const totalLeaveDays = 20;
291
- const usedLeaveDays = existingLeaves.reduce((total, leave) => total + leave.length(), 0);
292
- const remainingLeaveDays = totalLeaveDays - usedLeaveDays;
293
- console.log(`Remaining leave days: ${remainingLeaveDays}`);
181
+ ```typescript
182
+ import { Chronos, ChronosInterval } from 'chronos-ts';
294
183
 
295
- // Renew leave balance for the next year
296
- const nextYearLeaveBalance = remainingLeaveDays > 0 ? new Period('2024-01-01', '2024-12-31') : null;
297
- console.log(`Next year's leave balance: ${nextYearLeaveBalance ? nextYearLeaveBalance.length() : 0} days`);
184
+ class Subscription {
185
+ constructor(
186
+ public startDate: Chronos,
187
+ public interval: ChronosInterval
188
+ ) {}
298
189
 
299
- ```
190
+ get nextBillingDate(): Chronos {
191
+ return this.startDate.add(this.interval);
192
+ }
300
193
 
301
- **4. Project Timeline Management**
302
- Use the Period class to manage project timelines and track overlapping tasks.
303
- ``` typescript
304
- import { Period, Precision } from 'chronos-ts';
305
-
306
- const projectTimeline = new Period('2023-01-01', '2023-12-31', Precision.DAY);
307
-
308
- const tasks = [
309
- new Period('2023-01-15', '2023-03-31', Precision.DAY),
310
- new Period('2023-03-01', '2023-05-15', Precision.DAY),
311
- new Period('2023-05-01', '2023-08-31', Precision.DAY),
312
- new Period('2023-08-15', '2023-11-30', Precision.DAY),
313
- ];
314
-
315
- // Find overlapping tasks
316
- const findOverlappingTasks = (tasks: Period[]): [Period, Period][] => {
317
- const overlaps: [Period, Period][] = [];
318
- for (let i = 0; i < tasks.length; i++) {
319
- for (let j = i + 1; j < tasks.length; j++) {
320
- if (tasks[i].overlapsWith(tasks[j])) {
321
- overlaps.push([tasks[i], tasks[j]]);
322
- }
323
- }
194
+ isActive(): boolean {
195
+ return Chronos.now().isBefore(this.nextBillingDate);
324
196
  }
325
- return overlaps;
326
- };
327
197
 
328
- const overlappingTasks = findOverlappingTasks(tasks);
329
- console.log(`There are ${overlappingTasks.length} overlapping tasks in the project.`);
198
+ daysUntilRenewal(): number {
199
+ return this.nextBillingDate.diffInDays(Chronos.now());
200
+ }
201
+ }
330
202
 
331
- // Calculate project progress
332
- const calculateProgress = (currentDate: Date): number => {
333
- const daysPassed = new Period(projectTimeline.getStartDate(), currentDate, Precision.DAY).getDaysInInterval();
334
- const totalDays = projectTimeline.getDaysInInterval();
335
- return (daysPassed / totalDays) * 100;
336
- };
203
+ const monthly = new Subscription(
204
+ Chronos.now(),
205
+ ChronosInterval.months(1)
206
+ );
337
207
 
338
- const currentProgress = calculateProgress(new Date('2023-06-15'));
339
- console.log(`Project progress: ${currentProgress.toFixed(2)}%`);
208
+ console.log(`Days until renewal: ${monthly.daysUntilRenewal()}`);
340
209
  ```
341
210
 
342
- **5. Subscription Billing Cycle Management**
343
- Use the Period class to manage subscription periods and calculate renewal dates.
344
- ``` typescript
345
- import { Period, Precision, addToDate } from 'chronos-ts';
211
+ ### Work Schedule
346
212
 
347
- class Subscription {
348
- constructor(public startDate: Date, public plan: 'monthly' | 'annual') {}
213
+ ```typescript
214
+ import { Chronos, ChronosPeriod } from 'chronos-ts';
349
215
 
350
- getCurrentPeriod(): Period {
351
- const endDate = addToDate(this.startDate, 1, this.plan === 'monthly' ? Precision.MONTH : Precision.YEAR);
352
- return new Period(this.startDate, endDate, Precision.DAY);
353
- }
216
+ // Get working days this month
217
+ const workingDays = ChronosPeriod
218
+ .thisMonth()
219
+ .filterWeekdays()
220
+ .toArray();
354
221
 
355
- isActive(date: Date = new Date()): boolean {
356
- return this.getCurrentPeriod().contains(date);
357
- }
222
+ console.log(`Working days this month: ${workingDays.length}`);
358
223
 
359
- getRenewalDate(): Date {
360
- return this.getCurrentPeriod().getEndDate();
361
- }
224
+ // Calculate hours worked
225
+ const clockIn = Chronos.create(2024, 3, 15, 9, 0);
226
+ const clockOut = Chronos.create(2024, 3, 15, 17, 30);
362
227
 
363
- renew(): void {
364
- this.startDate = this.getRenewalDate();
365
- }
366
- }
228
+ const hoursWorked = clockOut.diffInHours(clockIn);
229
+ const overtime = Math.max(0, hoursWorked - 8);
367
230
 
368
- const monthlySubscription = new Subscription(new Date('2023-01-01'), 'monthly');
369
- console.log(`Monthly subscription active: ${monthlySubscription.isActive()}`);
370
- console.log(`Monthly subscription renewal date: ${monthlySubscription.getRenewalDate().toDateString()}`);
231
+ console.log(`Hours worked: ${hoursWorked}`);
232
+ console.log(`Overtime: ${overtime}`);
233
+ ```
371
234
 
372
- const annualSubscription = new Subscription(new Date('2023-01-01'), 'annual');
373
- console.log(`Annual subscription active: ${annualSubscription.isActive()}`);
374
- console.log(`Annual subscription renewal date: ${annualSubscription.getRenewalDate().toDateString()}`);
235
+ ### Age Calculator
375
236
 
376
- // Check if a subscription will be active on a future date
377
- const futureDate = new Date('2023-12-15');
378
- console.log(`Monthly subscription active on ${futureDate.toDateString()}: ${monthlySubscription.isActive(futureDate)}`);
379
- console.log(`Annual subscription active on ${futureDate.toDateString()}: ${annualSubscription.isActive(futureDate)}`);
237
+ ```typescript
238
+ import { Chronos } from 'chronos-ts';
239
+
240
+ function getAge(birthDate: Chronos): { years: number; months: number; days: number } {
241
+ const now = Chronos.now();
242
+
243
+ return {
244
+ years: now.diffInYears(birthDate),
245
+ months: now.diffInMonths(birthDate) % 12,
246
+ days: now.diffInDays(birthDate.addYears(now.diffInYears(birthDate))) % 30,
247
+ };
248
+ }
380
249
 
381
- // Renew a subscription
382
- monthlySubscription.renew();
383
- const renewedPeriod = monthlySubscription.getCurrentPeriod();
384
- console.log(`Monthly subscription renewed. New period: ${renewedPeriod.getStartDate().toDateString()} - ${renewedPeriod.getEndDate().toDateString()}`);
250
+ const birthday = Chronos.create(1990, 6, 15);
251
+ const age = getAge(birthday);
252
+ console.log(`Age: ${age.years} years, ${age.months} months, ${age.days} days`);
385
253
  ```
386
254
 
387
- **6. Employee Shift Management**
255
+ ---
388
256
 
389
- Use the `Period` and `Interval` classes to manage employee shifts and calculate overtime.
257
+ ## 🧪 Testing
390
258
 
391
259
  ```typescript
392
- import { Period, Interval, Precision, addToDate } from 'chronos-ts';
260
+ import { Chronos } from 'chronos-ts';
261
+
262
+ describe('MyFeature', () => {
263
+ beforeEach(() => {
264
+ // Freeze time for consistent tests
265
+ Chronos.setTestNow(Chronos.create(2024, 1, 15, 12, 0, 0));
266
+ });
267
+
268
+ afterEach(() => {
269
+ // Reset to real time
270
+ Chronos.setTestNow(null);
271
+ });
272
+
273
+ it('should calculate correct deadline', () => {
274
+ const deadline = Chronos.now().addDays(30);
275
+ expect(deadline.format('YYYY-MM-DD')).toBe('2024-02-14');
276
+ });
277
+ });
278
+ ```
393
279
 
394
- class Shift extends Period {
395
- constructor(public employee: string, start: Date, end: Date) {
396
- super(start, end, Precision.MINUTE);
397
- }
280
+ ---
398
281
 
399
- getDuration(): number {
400
- return this.getHoursInInterval();
401
- }
282
+ ## 🆚 Comparison with Other Libraries
402
283
 
403
- isOvertime(): boolean {
404
- return this.getDuration() > 8;
405
- }
284
+ | Feature | Chronos-ts | Day.js | Moment.js | date-fns |
285
+ |---------|-----------|--------|-----------|----------|
286
+ | Immutable | ✅ | ✅ | ❌ | ✅ |
287
+ | TypeScript | ✅ Native | Plugin | Plugin | ✅ Native |
288
+ | Tree-shakeable | ✅ | ✅ | ❌ | ✅ |
289
+ | Intervals | ✅ | Plugin | ✅ | ❌ |
290
+ | Periods | ✅ | ❌ | ❌ | ❌ |
291
+ | Timezones | ✅ | Plugin | Plugin | ✅ |
292
+ | Zero deps | ✅ | ✅ | ❌ | ✅ |
293
+ | API Style | Fluent | Fluent | Fluent | Functional |
406
294
 
407
- getOvertimeHours(): number {
408
- return Math.max(0, this.getDuration() - 8);
409
- }
410
- }
295
+ ---
411
296
 
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
- };
297
+ ## 🤝 Contributing
424
298
 
425
- const employeeShifts = createWeekShifts('John Doe', new Date('2023-06-19'));
299
+ Contributions, issues, and feature requests are welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
426
300
 
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);
301
+ ```bash
302
+ # Clone the repository
303
+ git clone https://github.com/hendurhance/chronos-ts.git
430
304
 
431
- console.log(`Total hours worked: ${totalHours}`);
432
- console.log(`Overtime hours: ${overtimeHours}`);
305
+ # Install dependencies
306
+ npm install
433
307
 
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
- };
308
+ # Run tests
309
+ npm test
445
310
 
446
- console.log(`Shifts have conflicts: ${hasConflict(employeeShifts)}`);
311
+ # Build
312
+ npm run build
447
313
  ```
448
314
 
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[] = [];
315
+ ---
462
316
 
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.getStartDate() < min ? e.getStartDate() : min, this.events[0].getStartDate());
477
- const end = this.events.reduce((max, e) => e.getEndDate() > max ? e.getEndDate() : max, this.events[0].getEndDate());
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
- }
317
+ ## 📄 License
495
318
 
496
- console.log(`Itinerary duration: ${itinerary.getDuration()} days`);
319
+ This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details.
497
320
 
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
- ```
321
+ ---
505
322
 
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.
323
+ ## 🙏 Acknowledgements
508
324
 
509
- ## License 📝
510
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
325
+ - [Carbon PHP](https://carbon.nesbot.com/) — Primary API inspiration
326
+ - [Day.js](https://day.js.org/) Immutability patterns
327
+ - [Moment.js](https://momentjs.com/) — Format string patterns
328
+ - [Period](https://github.com/spatie/period) - Complex period comparisons
329
+ ---
511
330
 
512
- ## Acknowledgements 🙏
513
- - [spatie/period]((https://github.com/spatie/period)
331
+ Made with ❤️ by the Chronos-ts team