ts-time-utils 0.0.1
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 +343 -0
- package/dist/age.d.ts +49 -0
- package/dist/age.d.ts.map +1 -0
- package/dist/age.js +106 -0
- package/dist/calculate.d.ts +49 -0
- package/dist/calculate.d.ts.map +1 -0
- package/dist/calculate.js +179 -0
- package/dist/calendar.d.ts +82 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +154 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +17 -0
- package/dist/esm/age.d.ts +49 -0
- package/dist/esm/age.d.ts.map +1 -0
- package/dist/esm/age.js +106 -0
- package/dist/esm/calculate.d.ts +49 -0
- package/dist/esm/calculate.d.ts.map +1 -0
- package/dist/esm/calculate.js +179 -0
- package/dist/esm/calendar.d.ts +82 -0
- package/dist/esm/calendar.d.ts.map +1 -0
- package/dist/esm/calendar.js +154 -0
- package/dist/esm/constants.d.ts +35 -0
- package/dist/esm/constants.d.ts.map +1 -0
- package/dist/esm/constants.js +17 -0
- package/dist/esm/format.d.ts +25 -0
- package/dist/esm/format.d.ts.map +1 -0
- package/dist/esm/format.js +189 -0
- package/dist/esm/index.d.ts +17 -0
- package/dist/esm/index.d.ts.map +1 -0
- package/dist/esm/index.js +28 -0
- package/dist/esm/interval.d.ts +30 -0
- package/dist/esm/interval.d.ts.map +1 -0
- package/dist/esm/interval.js +86 -0
- package/dist/esm/parse.d.ts +31 -0
- package/dist/esm/parse.d.ts.map +1 -0
- package/dist/esm/parse.js +217 -0
- package/dist/esm/performance.d.ts +110 -0
- package/dist/esm/performance.d.ts.map +1 -0
- package/dist/esm/performance.js +222 -0
- package/dist/esm/rangePresets.d.ts +45 -0
- package/dist/esm/rangePresets.d.ts.map +1 -0
- package/dist/esm/rangePresets.js +124 -0
- package/dist/esm/timezone.d.ts +38 -0
- package/dist/esm/timezone.d.ts.map +1 -0
- package/dist/esm/timezone.js +99 -0
- package/dist/esm/validate.d.ts +62 -0
- package/dist/esm/validate.d.ts.map +1 -0
- package/dist/esm/validate.js +108 -0
- package/dist/esm/workingHours.d.ts +25 -0
- package/dist/esm/workingHours.d.ts.map +1 -0
- package/dist/esm/workingHours.js +107 -0
- package/dist/format.d.ts +25 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +189 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/interval.d.ts +30 -0
- package/dist/interval.d.ts.map +1 -0
- package/dist/interval.js +86 -0
- package/dist/parse.d.ts +31 -0
- package/dist/parse.d.ts.map +1 -0
- package/dist/parse.js +217 -0
- package/dist/performance.d.ts +110 -0
- package/dist/performance.d.ts.map +1 -0
- package/dist/performance.js +222 -0
- package/dist/rangePresets.d.ts +45 -0
- package/dist/rangePresets.d.ts.map +1 -0
- package/dist/rangePresets.js +124 -0
- package/dist/timezone.d.ts +38 -0
- package/dist/timezone.d.ts.map +1 -0
- package/dist/timezone.js +99 -0
- package/dist/validate.d.ts +62 -0
- package/dist/validate.d.ts.map +1 -0
- package/dist/validate.js +108 -0
- package/dist/workingHours.d.ts +25 -0
- package/dist/workingHours.d.ts.map +1 -0
- package/dist/workingHours.js +107 -0
- package/package.json +102 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { MILLISECONDS_PER_SECOND, MILLISECONDS_PER_MINUTE, MILLISECONDS_PER_HOUR, MILLISECONDS_PER_DAY, MILLISECONDS_PER_WEEK, MILLISECONDS_PER_MONTH, MILLISECONDS_PER_YEAR } from './constants.js';
|
|
2
|
+
/**
|
|
3
|
+
* Convert milliseconds to a human-readable duration.
|
|
4
|
+
* @param ms - milliseconds
|
|
5
|
+
* @param options - formatting options
|
|
6
|
+
*/
|
|
7
|
+
export function formatDuration(ms, options = {}) {
|
|
8
|
+
const { includeMs = false, short = false, maxUnits = 2, round = false } = options;
|
|
9
|
+
if (ms < 0)
|
|
10
|
+
return '0' + (short ? 's' : ' seconds');
|
|
11
|
+
const mathFn = round ? Math.round : Math.floor;
|
|
12
|
+
const parts = [];
|
|
13
|
+
let remaining = ms;
|
|
14
|
+
// Years
|
|
15
|
+
if (remaining >= MILLISECONDS_PER_YEAR) {
|
|
16
|
+
const years = mathFn(remaining / MILLISECONDS_PER_YEAR);
|
|
17
|
+
parts.push(years + (short ? 'y' : ` year${years !== 1 ? 's' : ''}`));
|
|
18
|
+
remaining %= MILLISECONDS_PER_YEAR;
|
|
19
|
+
}
|
|
20
|
+
// Months
|
|
21
|
+
if (remaining >= MILLISECONDS_PER_MONTH && parts.length < maxUnits) {
|
|
22
|
+
const months = mathFn(remaining / MILLISECONDS_PER_MONTH);
|
|
23
|
+
parts.push(months + (short ? 'mo' : ` month${months !== 1 ? 's' : ''}`));
|
|
24
|
+
remaining %= MILLISECONDS_PER_MONTH;
|
|
25
|
+
}
|
|
26
|
+
// Weeks
|
|
27
|
+
if (remaining >= MILLISECONDS_PER_WEEK && parts.length < maxUnits) {
|
|
28
|
+
const weeks = mathFn(remaining / MILLISECONDS_PER_WEEK);
|
|
29
|
+
parts.push(weeks + (short ? 'w' : ` week${weeks !== 1 ? 's' : ''}`));
|
|
30
|
+
remaining %= MILLISECONDS_PER_WEEK;
|
|
31
|
+
}
|
|
32
|
+
// Days
|
|
33
|
+
if (remaining >= MILLISECONDS_PER_DAY && parts.length < maxUnits) {
|
|
34
|
+
const days = mathFn(remaining / MILLISECONDS_PER_DAY);
|
|
35
|
+
parts.push(days + (short ? 'd' : ` day${days !== 1 ? 's' : ''}`));
|
|
36
|
+
remaining %= MILLISECONDS_PER_DAY;
|
|
37
|
+
}
|
|
38
|
+
// Hours
|
|
39
|
+
if (remaining >= MILLISECONDS_PER_HOUR && parts.length < maxUnits) {
|
|
40
|
+
const hours = mathFn(remaining / MILLISECONDS_PER_HOUR);
|
|
41
|
+
parts.push(hours + (short ? 'h' : ` hour${hours !== 1 ? 's' : ''}`));
|
|
42
|
+
remaining %= MILLISECONDS_PER_HOUR;
|
|
43
|
+
}
|
|
44
|
+
// Minutes
|
|
45
|
+
if (remaining >= MILLISECONDS_PER_MINUTE && parts.length < maxUnits) {
|
|
46
|
+
const minutes = mathFn(remaining / MILLISECONDS_PER_MINUTE);
|
|
47
|
+
parts.push(minutes + (short ? 'm' : ` minute${minutes !== 1 ? 's' : ''}`));
|
|
48
|
+
remaining %= MILLISECONDS_PER_MINUTE;
|
|
49
|
+
}
|
|
50
|
+
// Seconds
|
|
51
|
+
if (remaining >= MILLISECONDS_PER_SECOND && parts.length < maxUnits) {
|
|
52
|
+
const seconds = mathFn(remaining / MILLISECONDS_PER_SECOND);
|
|
53
|
+
parts.push(seconds + (short ? 's' : ` second${seconds !== 1 ? 's' : ''}`));
|
|
54
|
+
remaining %= MILLISECONDS_PER_SECOND;
|
|
55
|
+
}
|
|
56
|
+
// Milliseconds
|
|
57
|
+
if ((remaining > 0 || parts.length === 0) && includeMs && parts.length < maxUnits) {
|
|
58
|
+
const milliseconds = mathFn(remaining);
|
|
59
|
+
parts.push(milliseconds + (short ? 'ms' : ` millisecond${milliseconds !== 1 ? 's' : ''}`));
|
|
60
|
+
}
|
|
61
|
+
if (parts.length === 0) {
|
|
62
|
+
return '0' + (short ? 's' : ' seconds');
|
|
63
|
+
}
|
|
64
|
+
return short ? parts.join(' ') : parts.join(', ');
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Return a human-readable "time ago" string.
|
|
68
|
+
* @param date - past or future date
|
|
69
|
+
* @param options - formatting options
|
|
70
|
+
*/
|
|
71
|
+
export function timeAgo(date, options = {}) {
|
|
72
|
+
const now = new Date();
|
|
73
|
+
const diffMs = now.getTime() - date.getTime();
|
|
74
|
+
const { short = false } = options;
|
|
75
|
+
const isFuture = diffMs < 0;
|
|
76
|
+
const absDiffMs = Math.abs(diffMs);
|
|
77
|
+
if (absDiffMs < MILLISECONDS_PER_MINUTE) {
|
|
78
|
+
const seconds = Math.floor(absDiffMs / MILLISECONDS_PER_SECOND);
|
|
79
|
+
const unit = short ? 's' : ` second${seconds !== 1 ? 's' : ''}`;
|
|
80
|
+
return isFuture ? `in ${seconds}${unit}` : `${seconds}${unit} ago`;
|
|
81
|
+
}
|
|
82
|
+
if (absDiffMs < MILLISECONDS_PER_HOUR) {
|
|
83
|
+
const minutes = Math.floor(absDiffMs / MILLISECONDS_PER_MINUTE);
|
|
84
|
+
const unit = short ? 'm' : ` minute${minutes !== 1 ? 's' : ''}`;
|
|
85
|
+
return isFuture ? `in ${minutes}${unit}` : `${minutes}${unit} ago`;
|
|
86
|
+
}
|
|
87
|
+
if (absDiffMs < MILLISECONDS_PER_DAY) {
|
|
88
|
+
const hours = Math.floor(absDiffMs / MILLISECONDS_PER_HOUR);
|
|
89
|
+
const unit = short ? 'h' : ` hour${hours !== 1 ? 's' : ''}`;
|
|
90
|
+
return isFuture ? `in ${hours}${unit}` : `${hours}${unit} ago`;
|
|
91
|
+
}
|
|
92
|
+
if (absDiffMs < MILLISECONDS_PER_WEEK) {
|
|
93
|
+
const days = Math.floor(absDiffMs / MILLISECONDS_PER_DAY);
|
|
94
|
+
const unit = short ? 'd' : ` day${days !== 1 ? 's' : ''}`;
|
|
95
|
+
return isFuture ? `in ${days}${unit}` : `${days}${unit} ago`;
|
|
96
|
+
}
|
|
97
|
+
if (absDiffMs < MILLISECONDS_PER_MONTH) {
|
|
98
|
+
const weeks = Math.floor(absDiffMs / MILLISECONDS_PER_WEEK);
|
|
99
|
+
const unit = short ? 'w' : ` week${weeks !== 1 ? 's' : ''}`;
|
|
100
|
+
return isFuture ? `in ${weeks}${unit}` : `${weeks}${unit} ago`;
|
|
101
|
+
}
|
|
102
|
+
if (absDiffMs < MILLISECONDS_PER_YEAR) {
|
|
103
|
+
const months = Math.floor(absDiffMs / MILLISECONDS_PER_MONTH);
|
|
104
|
+
const unit = short ? 'mo' : ` month${months !== 1 ? 's' : ''}`;
|
|
105
|
+
return isFuture ? `in ${months}${unit}` : `${months}${unit} ago`;
|
|
106
|
+
}
|
|
107
|
+
const years = Math.floor(absDiffMs / MILLISECONDS_PER_YEAR);
|
|
108
|
+
const unit = short ? 'y' : ` year${years !== 1 ? 's' : ''}`;
|
|
109
|
+
return isFuture ? `in ${years}${unit}` : `${years}${unit} ago`;
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Format a date to a human-readable time string
|
|
113
|
+
* @param date - date to format
|
|
114
|
+
* @param format - format type
|
|
115
|
+
*/
|
|
116
|
+
export function formatTime(date, format = '24h') {
|
|
117
|
+
switch (format) {
|
|
118
|
+
case '12h':
|
|
119
|
+
return date.toLocaleTimeString('en-US', {
|
|
120
|
+
hour: 'numeric',
|
|
121
|
+
minute: '2-digit',
|
|
122
|
+
hour12: true
|
|
123
|
+
});
|
|
124
|
+
case 'iso':
|
|
125
|
+
return date.toISOString();
|
|
126
|
+
case '24h':
|
|
127
|
+
default:
|
|
128
|
+
return date.toTimeString().split(' ')[0].slice(0, 5); // HH:MM
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Parse a duration string like "1h 30m" into milliseconds
|
|
133
|
+
* @param duration - duration string (e.g., "1h 30m", "2d", "45s")
|
|
134
|
+
*/
|
|
135
|
+
export function parseDuration(duration) {
|
|
136
|
+
const regex = /(\d+(?:\.\d+)?)\s*([a-zA-Z]+)/g;
|
|
137
|
+
let totalMs = 0;
|
|
138
|
+
let match;
|
|
139
|
+
while ((match = regex.exec(duration)) !== null) {
|
|
140
|
+
const value = parseFloat(match[1]);
|
|
141
|
+
const unit = match[2].toLowerCase();
|
|
142
|
+
switch (unit) {
|
|
143
|
+
case 'ms':
|
|
144
|
+
case 'millisecond':
|
|
145
|
+
case 'milliseconds':
|
|
146
|
+
totalMs += value;
|
|
147
|
+
break;
|
|
148
|
+
case 's':
|
|
149
|
+
case 'sec':
|
|
150
|
+
case 'second':
|
|
151
|
+
case 'seconds':
|
|
152
|
+
totalMs += value * MILLISECONDS_PER_SECOND;
|
|
153
|
+
break;
|
|
154
|
+
case 'm':
|
|
155
|
+
case 'min':
|
|
156
|
+
case 'minute':
|
|
157
|
+
case 'minutes':
|
|
158
|
+
totalMs += value * MILLISECONDS_PER_MINUTE;
|
|
159
|
+
break;
|
|
160
|
+
case 'h':
|
|
161
|
+
case 'hr':
|
|
162
|
+
case 'hour':
|
|
163
|
+
case 'hours':
|
|
164
|
+
totalMs += value * MILLISECONDS_PER_HOUR;
|
|
165
|
+
break;
|
|
166
|
+
case 'd':
|
|
167
|
+
case 'day':
|
|
168
|
+
case 'days':
|
|
169
|
+
totalMs += value * MILLISECONDS_PER_DAY;
|
|
170
|
+
break;
|
|
171
|
+
case 'w':
|
|
172
|
+
case 'week':
|
|
173
|
+
case 'weeks':
|
|
174
|
+
totalMs += value * MILLISECONDS_PER_WEEK;
|
|
175
|
+
break;
|
|
176
|
+
case 'mo':
|
|
177
|
+
case 'month':
|
|
178
|
+
case 'months':
|
|
179
|
+
totalMs += value * MILLISECONDS_PER_MONTH;
|
|
180
|
+
break;
|
|
181
|
+
case 'y':
|
|
182
|
+
case 'year':
|
|
183
|
+
case 'years':
|
|
184
|
+
totalMs += value * MILLISECONDS_PER_YEAR;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
return totalMs;
|
|
189
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main entry point for ts-time-utils library
|
|
3
|
+
* Re-exports all functions from individual modules for convenience
|
|
4
|
+
*/
|
|
5
|
+
export { formatDuration, timeAgo, formatTime, parseDuration } from './format.js';
|
|
6
|
+
export { differenceInUnits, addTime, subtractTime, startOf, endOf, isBetween, businessDaysBetween } from './calculate.js';
|
|
7
|
+
export { isValidDate, isLeapYear, isPast, isFuture, isToday, isYesterday, isTomorrow, isSameDay, isWeekend, isWeekday, isValidTimeString, isValidISOString } from './validate.js';
|
|
8
|
+
export { calculateAge, getAgeInUnits, getLifeStage, getNextBirthday, getDaysUntilBirthday, isBirthday, type AgeResult } from './age.js';
|
|
9
|
+
export { getWeekNumber, getWeekOfMonth, getQuarter, getDayOfYear, getWeeksInYear, getDaysInMonth, getDaysInYear, getEaster, getMonthsInYear, getDaysInMonthArray, getWeekdaysInMonth, getFirstDayOfMonth, getLastDayOfMonth, getFirstDayOfYear, getLastDayOfYear } from './calendar.js';
|
|
10
|
+
export { parseDate, parseRelativeDate, parseTimeAgo, parseCustomFormat, parseManyFormats } from './parse.js';
|
|
11
|
+
export { sleep, timeout, debounce, throttle, retry, createStopwatch, measureTime, measureAsync, benchmark, Stopwatch, type BenchmarkResult } from './performance.js';
|
|
12
|
+
export { createInterval, isValidInterval, intervalDuration, intervalContains, intervalsOverlap, intervalIntersection, mergeIntervals, subtractInterval, splitIntervalByDay, totalIntervalCoverage, normalizeIntervals, type Interval } from './interval.js';
|
|
13
|
+
export { getTimezoneOffset, formatInTimeZone, getZonedTime, convertDateToZone, isValidTimeZone, COMMON_TIMEZONES, getLocalOffset, compareZoneOffsets, reinterpretAsZone, type ZonedTime } from './timezone.js';
|
|
14
|
+
export { DEFAULT_WORKING_HOURS, isWorkingDay, isWorkingTime, nextWorkingTime, workingTimeBetween, addWorkingHours, type WorkingHoursConfig } from './workingHours.js';
|
|
15
|
+
export { today, yesterday, tomorrow, lastNDays, nextNDays, thisWeek, lastWeek, nextWeek, thisMonth, lastMonth, nextMonth, thisYear, lastYear, nextYear, rollingWindowDays, quarterRange, lastQuarter, nextQuarter, RANGE_PRESETS, type DateRange } from './rangePresets.js';
|
|
16
|
+
export { MILLISECONDS_PER_SECOND, MILLISECONDS_PER_MINUTE, MILLISECONDS_PER_HOUR, MILLISECONDS_PER_DAY, MILLISECONDS_PER_WEEK, MILLISECONDS_PER_MONTH, MILLISECONDS_PER_YEAR, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK, type TimeUnit, type FormatOptions } from './constants.js';
|
|
17
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EACL,cAAc,EACd,OAAO,EACP,UAAU,EACV,aAAa,EACd,MAAM,aAAa,CAAC;AAGrB,OAAO,EACL,iBAAiB,EACjB,OAAO,EACP,YAAY,EACZ,OAAO,EACP,KAAK,EACL,SAAS,EACT,mBAAmB,EACpB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,WAAW,EACX,UAAU,EACV,MAAM,EACN,QAAQ,EACR,OAAO,EACP,WAAW,EACX,UAAU,EACV,SAAS,EACT,SAAS,EACT,SAAS,EACT,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,YAAY,EACZ,aAAa,EACb,YAAY,EACZ,eAAe,EACf,oBAAoB,EACpB,UAAU,EACV,KAAK,SAAS,EACf,MAAM,UAAU,CAAC;AAGlB,OAAO,EACL,aAAa,EACb,cAAc,EACd,UAAU,EACV,YAAY,EACZ,cAAc,EACd,cAAc,EACd,aAAa,EACb,SAAS,EACT,eAAe,EACf,mBAAmB,EACnB,kBAAkB,EAClB,kBAAkB,EAClB,iBAAiB,EACjB,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,SAAS,EACT,iBAAiB,EACjB,YAAY,EACZ,iBAAiB,EACjB,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,KAAK,EACL,OAAO,EACP,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,eAAe,EACf,WAAW,EACX,YAAY,EACZ,SAAS,EACT,SAAS,EACT,KAAK,eAAe,EACrB,MAAM,kBAAkB,CAAC;AAG1B,OAAO,EACL,cAAc,EACd,eAAe,EACf,gBAAgB,EAChB,gBAAgB,EAChB,gBAAgB,EAChB,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,KAAK,QAAQ,EACd,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,iBAAiB,EACjB,KAAK,SAAS,EACf,MAAM,eAAe,CAAC;AAGvB,OAAO,EACL,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,KAAK,EACL,SAAS,EACT,QAAQ,EACR,SAAS,EACT,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,SAAS,EACT,SAAS,EACT,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,QAAQ,EACR,iBAAiB,EACjB,YAAY,EACZ,WAAW,EACX,WAAW,EACX,aAAa,EACb,KAAK,SAAS,EACf,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EACL,uBAAuB,EACvB,uBAAuB,EACvB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,eAAe,EACf,gBAAgB,EAChB,KAAK,QAAQ,EACb,KAAK,aAAa,EACnB,MAAM,gBAAgB,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Main entry point for ts-time-utils library
|
|
3
|
+
* Re-exports all functions from individual modules for convenience
|
|
4
|
+
*/
|
|
5
|
+
// Format utilities
|
|
6
|
+
export { formatDuration, timeAgo, formatTime, parseDuration } from './format.js';
|
|
7
|
+
// Calculation utilities
|
|
8
|
+
export { differenceInUnits, addTime, subtractTime, startOf, endOf, isBetween, businessDaysBetween } from './calculate.js';
|
|
9
|
+
// Validation utilities
|
|
10
|
+
export { isValidDate, isLeapYear, isPast, isFuture, isToday, isYesterday, isTomorrow, isSameDay, isWeekend, isWeekday, isValidTimeString, isValidISOString } from './validate.js';
|
|
11
|
+
// Age utilities
|
|
12
|
+
export { calculateAge, getAgeInUnits, getLifeStage, getNextBirthday, getDaysUntilBirthday, isBirthday } from './age.js';
|
|
13
|
+
// Calendar utilities
|
|
14
|
+
export { getWeekNumber, getWeekOfMonth, getQuarter, getDayOfYear, getWeeksInYear, getDaysInMonth, getDaysInYear, getEaster, getMonthsInYear, getDaysInMonthArray, getWeekdaysInMonth, getFirstDayOfMonth, getLastDayOfMonth, getFirstDayOfYear, getLastDayOfYear } from './calendar.js';
|
|
15
|
+
// Parse utilities
|
|
16
|
+
export { parseDate, parseRelativeDate, parseTimeAgo, parseCustomFormat, parseManyFormats } from './parse.js';
|
|
17
|
+
// Performance utilities
|
|
18
|
+
export { sleep, timeout, debounce, throttle, retry, createStopwatch, measureTime, measureAsync, benchmark, Stopwatch } from './performance.js';
|
|
19
|
+
// Interval utilities
|
|
20
|
+
export { createInterval, isValidInterval, intervalDuration, intervalContains, intervalsOverlap, intervalIntersection, mergeIntervals, subtractInterval, splitIntervalByDay, totalIntervalCoverage, normalizeIntervals } from './interval.js';
|
|
21
|
+
// Timezone utilities
|
|
22
|
+
export { getTimezoneOffset, formatInTimeZone, getZonedTime, convertDateToZone, isValidTimeZone, COMMON_TIMEZONES, getLocalOffset, compareZoneOffsets, reinterpretAsZone } from './timezone.js';
|
|
23
|
+
// Working hours utilities
|
|
24
|
+
export { DEFAULT_WORKING_HOURS, isWorkingDay, isWorkingTime, nextWorkingTime, workingTimeBetween, addWorkingHours } from './workingHours.js';
|
|
25
|
+
// Range preset utilities
|
|
26
|
+
export { today, yesterday, tomorrow, lastNDays, nextNDays, thisWeek, lastWeek, nextWeek, thisMonth, lastMonth, nextMonth, thisYear, lastYear, nextYear, rollingWindowDays, quarterRange, lastQuarter, nextQuarter, RANGE_PRESETS } from './rangePresets.js';
|
|
27
|
+
// Constants and types
|
|
28
|
+
export { MILLISECONDS_PER_SECOND, MILLISECONDS_PER_MINUTE, MILLISECONDS_PER_HOUR, MILLISECONDS_PER_DAY, MILLISECONDS_PER_WEEK, MILLISECONDS_PER_MONTH, MILLISECONDS_PER_YEAR, SECONDS_PER_MINUTE, SECONDS_PER_HOUR, SECONDS_PER_DAY, SECONDS_PER_WEEK } from './constants.js';
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interval utilities: operations on time intervals [start, end)
|
|
3
|
+
*/
|
|
4
|
+
export interface Interval {
|
|
5
|
+
start: Date;
|
|
6
|
+
end: Date;
|
|
7
|
+
}
|
|
8
|
+
/** Create an interval ensuring start <= end */
|
|
9
|
+
export declare function createInterval(start: Date | string | number, end: Date | string | number): Interval | null;
|
|
10
|
+
/** Validate an object is a proper interval */
|
|
11
|
+
export declare function isValidInterval(i: any): i is Interval;
|
|
12
|
+
/** Duration of interval in ms */
|
|
13
|
+
export declare function intervalDuration(i: Interval): number;
|
|
14
|
+
/** Whether interval contains date (inclusive start, exclusive end) */
|
|
15
|
+
export declare function intervalContains(i: Interval, date: Date | number): boolean;
|
|
16
|
+
/** Whether two intervals overlap */
|
|
17
|
+
export declare function intervalsOverlap(a: Interval, b: Interval): boolean;
|
|
18
|
+
/** Intersection of two intervals, or null */
|
|
19
|
+
export declare function intervalIntersection(a: Interval, b: Interval): Interval | null;
|
|
20
|
+
/** Merge overlapping or touching intervals into a minimal set */
|
|
21
|
+
export declare function mergeIntervals(intervals: Interval[]): Interval[];
|
|
22
|
+
/** Subtract interval b from a (can split into 0,1,2 intervals) */
|
|
23
|
+
export declare function subtractInterval(a: Interval, b: Interval): Interval[];
|
|
24
|
+
/** Split an interval into day-boundary intervals (UTC based) */
|
|
25
|
+
export declare function splitIntervalByDay(i: Interval): Interval[];
|
|
26
|
+
/** Total covered duration of possibly overlapping intervals */
|
|
27
|
+
export declare function totalIntervalCoverage(intervals: Interval[]): number;
|
|
28
|
+
/** Normalize array: filter invalid and merge */
|
|
29
|
+
export declare function normalizeIntervals(intervals: (Interval | null | undefined)[]): Interval[];
|
|
30
|
+
//# sourceMappingURL=interval.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"interval.d.ts","sourceRoot":"","sources":["../../src/interval.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,QAAQ;IACvB,KAAK,EAAE,IAAI,CAAC;IACZ,GAAG,EAAE,IAAI,CAAC;CACX;AAED,+CAA+C;AAC/C,wBAAgB,cAAc,CAAC,KAAK,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,EAAE,GAAG,EAAE,IAAI,GAAG,MAAM,GAAG,MAAM,GAAG,QAAQ,GAAG,IAAI,CAK1G;AAED,8CAA8C;AAC9C,wBAAgB,eAAe,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,IAAI,QAAQ,CAErD;AAED,iCAAiC;AACjC,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,GAAG,MAAM,CAEpD;AAED,sEAAsE;AACtE,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,GAAG,MAAM,GAAG,OAAO,CAG1E;AAED,oCAAoC;AACpC,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,CAElE;AAED,6CAA6C;AAC7C,wBAAgB,oBAAoB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,CAI9E;AAED,iEAAiE;AACjE,wBAAgB,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,QAAQ,EAAE,CAgBhE;AAED,kEAAkE;AAClE,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAMrE;AAED,gEAAgE;AAChE,wBAAgB,kBAAkB,CAAC,CAAC,EAAE,QAAQ,GAAG,QAAQ,EAAE,CAU1D;AAED,+DAA+D;AAC/D,wBAAgB,qBAAqB,CAAC,SAAS,EAAE,QAAQ,EAAE,GAAG,MAAM,CAEnE;AAED,gDAAgD;AAChD,wBAAgB,kBAAkB,CAAC,SAAS,EAAE,CAAC,QAAQ,GAAG,IAAI,GAAG,SAAS,CAAC,EAAE,GAAG,QAAQ,EAAE,CAEzF"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Interval utilities: operations on time intervals [start, end)
|
|
3
|
+
*/
|
|
4
|
+
/** Create an interval ensuring start <= end */
|
|
5
|
+
export function createInterval(start, end) {
|
|
6
|
+
const s = new Date(start);
|
|
7
|
+
const e = new Date(end);
|
|
8
|
+
if (isNaN(s.getTime()) || isNaN(e.getTime()) || s > e)
|
|
9
|
+
return null;
|
|
10
|
+
return { start: s, end: e };
|
|
11
|
+
}
|
|
12
|
+
/** Validate an object is a proper interval */
|
|
13
|
+
export function isValidInterval(i) {
|
|
14
|
+
return !!i && i.start instanceof Date && i.end instanceof Date && !isNaN(i.start) && !isNaN(i.end) && i.start <= i.end;
|
|
15
|
+
}
|
|
16
|
+
/** Duration of interval in ms */
|
|
17
|
+
export function intervalDuration(i) {
|
|
18
|
+
return i.end.getTime() - i.start.getTime();
|
|
19
|
+
}
|
|
20
|
+
/** Whether interval contains date (inclusive start, exclusive end) */
|
|
21
|
+
export function intervalContains(i, date) {
|
|
22
|
+
const d = date instanceof Date ? date : new Date(date);
|
|
23
|
+
return d >= i.start && d < i.end;
|
|
24
|
+
}
|
|
25
|
+
/** Whether two intervals overlap */
|
|
26
|
+
export function intervalsOverlap(a, b) {
|
|
27
|
+
return a.start < b.end && b.start < a.end;
|
|
28
|
+
}
|
|
29
|
+
/** Intersection of two intervals, or null */
|
|
30
|
+
export function intervalIntersection(a, b) {
|
|
31
|
+
const start = a.start > b.start ? a.start : b.start;
|
|
32
|
+
const end = a.end < b.end ? a.end : b.end;
|
|
33
|
+
return start < end ? { start, end } : null;
|
|
34
|
+
}
|
|
35
|
+
/** Merge overlapping or touching intervals into a minimal set */
|
|
36
|
+
export function mergeIntervals(intervals) {
|
|
37
|
+
if (intervals.length === 0)
|
|
38
|
+
return [];
|
|
39
|
+
const sorted = [...intervals].sort((a, b) => a.start.getTime() - b.start.getTime());
|
|
40
|
+
const result = [];
|
|
41
|
+
let current = { ...sorted[0] };
|
|
42
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
43
|
+
const next = sorted[i];
|
|
44
|
+
if (next.start <= current.end) { // overlap or touching
|
|
45
|
+
if (next.end > current.end)
|
|
46
|
+
current.end = next.end;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
result.push(current);
|
|
50
|
+
current = { ...next };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
result.push(current);
|
|
54
|
+
return result;
|
|
55
|
+
}
|
|
56
|
+
/** Subtract interval b from a (can split into 0,1,2 intervals) */
|
|
57
|
+
export function subtractInterval(a, b) {
|
|
58
|
+
if (!intervalsOverlap(a, b))
|
|
59
|
+
return [a];
|
|
60
|
+
const parts = [];
|
|
61
|
+
if (b.start > a.start)
|
|
62
|
+
parts.push({ start: a.start, end: b.start });
|
|
63
|
+
if (b.end < a.end)
|
|
64
|
+
parts.push({ start: b.end, end: a.end });
|
|
65
|
+
return parts;
|
|
66
|
+
}
|
|
67
|
+
/** Split an interval into day-boundary intervals (UTC based) */
|
|
68
|
+
export function splitIntervalByDay(i) {
|
|
69
|
+
const res = [];
|
|
70
|
+
let cursor = new Date(i.start);
|
|
71
|
+
while (cursor < i.end) {
|
|
72
|
+
const dayEnd = new Date(Date.UTC(cursor.getUTCFullYear(), cursor.getUTCMonth(), cursor.getUTCDate() + 1));
|
|
73
|
+
const end = dayEnd < i.end ? dayEnd : i.end;
|
|
74
|
+
res.push({ start: new Date(cursor), end: new Date(end) });
|
|
75
|
+
cursor = end;
|
|
76
|
+
}
|
|
77
|
+
return res;
|
|
78
|
+
}
|
|
79
|
+
/** Total covered duration of possibly overlapping intervals */
|
|
80
|
+
export function totalIntervalCoverage(intervals) {
|
|
81
|
+
return mergeIntervals(intervals).reduce((sum, i) => sum + intervalDuration(i), 0);
|
|
82
|
+
}
|
|
83
|
+
/** Normalize array: filter invalid and merge */
|
|
84
|
+
export function normalizeIntervals(intervals) {
|
|
85
|
+
return mergeIntervals(intervals.filter(isValidInterval));
|
|
86
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced date parsing utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Parse various date formats intelligently
|
|
6
|
+
* @param input - date string, number, or Date object
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseDate(input: string | number | Date): Date | null;
|
|
9
|
+
/**
|
|
10
|
+
* Parse relative date strings like "tomorrow", "next monday", "2 weeks ago"
|
|
11
|
+
* @param input - relative date string
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseRelativeDate(input: string): Date | null;
|
|
14
|
+
/**
|
|
15
|
+
* Parse "time ago" strings like "5 minutes ago", "2 hours ago"
|
|
16
|
+
* @param input - time ago string
|
|
17
|
+
*/
|
|
18
|
+
export declare function parseTimeAgo(input: string): Date | null;
|
|
19
|
+
/**
|
|
20
|
+
* Parse custom date format
|
|
21
|
+
* @param dateString - date string to parse
|
|
22
|
+
* @param format - format pattern (e.g., "YYYY-MM-DD", "DD/MM/YYYY")
|
|
23
|
+
*/
|
|
24
|
+
export declare function parseCustomFormat(dateString: string, format: string): Date | null;
|
|
25
|
+
/**
|
|
26
|
+
* Try parsing with multiple formats
|
|
27
|
+
* @param dateString - date string to parse
|
|
28
|
+
* @param formats - array of format patterns to try
|
|
29
|
+
*/
|
|
30
|
+
export declare function parseManyFormats(dateString: string, formats: string[]): Date | null;
|
|
31
|
+
//# sourceMappingURL=parse.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.d.ts","sourceRoot":"","sources":["../../src/parse.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAuDpE;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CA6C5D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAiBvD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAmDjF;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI,GAAG,IAAI,CAMnF"}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advanced date parsing utilities
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Parse various date formats intelligently
|
|
6
|
+
* @param input - date string, number, or Date object
|
|
7
|
+
*/
|
|
8
|
+
export function parseDate(input) {
|
|
9
|
+
if (input instanceof Date) {
|
|
10
|
+
return isNaN(input.getTime()) ? null : input;
|
|
11
|
+
}
|
|
12
|
+
if (typeof input === 'number') {
|
|
13
|
+
const date = new Date(input);
|
|
14
|
+
return isNaN(date.getTime()) ? null : date;
|
|
15
|
+
}
|
|
16
|
+
if (typeof input !== 'string') {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
// Try native Date parsing first, but validate the result
|
|
20
|
+
const nativeDate = new Date(input);
|
|
21
|
+
if (!isNaN(nativeDate.getTime())) {
|
|
22
|
+
// Additional validation for edge cases like "2025-02-30"
|
|
23
|
+
if (input.includes('-') && input.match(/^\d{4}-\d{2}-\d{2}/)) {
|
|
24
|
+
const [year, month, day] = input.split('-').map(Number);
|
|
25
|
+
const testDate = new Date(year, month - 1, day);
|
|
26
|
+
if (testDate.getFullYear() !== year || testDate.getMonth() !== month - 1 || testDate.getDate() !== day) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return nativeDate;
|
|
31
|
+
}
|
|
32
|
+
// Try common patterns
|
|
33
|
+
const patterns = [
|
|
34
|
+
/^(\d{4})-(\d{2})-(\d{2})$/, // YYYY-MM-DD
|
|
35
|
+
/^(\d{2})\/(\d{2})\/(\d{4})$/, // MM/DD/YYYY
|
|
36
|
+
/^(\d{2})-(\d{2})-(\d{4})$/, // MM-DD-YYYY
|
|
37
|
+
/^(\d{4})(\d{2})(\d{2})$/, // YYYYMMDD
|
|
38
|
+
];
|
|
39
|
+
for (const pattern of patterns) {
|
|
40
|
+
const match = input.match(pattern);
|
|
41
|
+
if (match) {
|
|
42
|
+
const [, first, second, third] = match;
|
|
43
|
+
// Try different interpretations based on pattern
|
|
44
|
+
if (pattern.source.includes('(\\d{4})')) {
|
|
45
|
+
// Year first format
|
|
46
|
+
const date = new Date(parseInt(first), parseInt(second) - 1, parseInt(third));
|
|
47
|
+
if (!isNaN(date.getTime()))
|
|
48
|
+
return date;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
// Month/day first format (assuming US format)
|
|
52
|
+
const date = new Date(parseInt(third), parseInt(first) - 1, parseInt(second));
|
|
53
|
+
if (!isNaN(date.getTime()))
|
|
54
|
+
return date;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse relative date strings like "tomorrow", "next monday", "2 weeks ago"
|
|
62
|
+
* @param input - relative date string
|
|
63
|
+
*/
|
|
64
|
+
export function parseRelativeDate(input) {
|
|
65
|
+
const now = new Date();
|
|
66
|
+
const lowercaseInput = input.toLowerCase().trim();
|
|
67
|
+
// Handle simple cases
|
|
68
|
+
switch (lowercaseInput) {
|
|
69
|
+
case 'now':
|
|
70
|
+
case 'today':
|
|
71
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
72
|
+
case 'yesterday':
|
|
73
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - 1);
|
|
74
|
+
case 'tomorrow':
|
|
75
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
|
|
76
|
+
}
|
|
77
|
+
// Handle "X time ago" or "in X time"
|
|
78
|
+
const agoMatch = lowercaseInput.match(/^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/);
|
|
79
|
+
if (agoMatch) {
|
|
80
|
+
const [, amount, unit] = agoMatch;
|
|
81
|
+
return subtractTimeUnits(now, parseInt(amount), unit);
|
|
82
|
+
}
|
|
83
|
+
const inMatch = lowercaseInput.match(/^in\s+(\d+)\s+(second|minute|hour|day|week|month|year)s?$/);
|
|
84
|
+
if (inMatch) {
|
|
85
|
+
const [, amount, unit] = inMatch;
|
|
86
|
+
return addTimeUnits(now, parseInt(amount), unit);
|
|
87
|
+
}
|
|
88
|
+
// Handle "next/last weekday"
|
|
89
|
+
const weekdays = ['sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'];
|
|
90
|
+
const nextWeekdayMatch = lowercaseInput.match(/^next\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/);
|
|
91
|
+
if (nextWeekdayMatch) {
|
|
92
|
+
const targetDay = weekdays.indexOf(nextWeekdayMatch[1]);
|
|
93
|
+
const daysUntilTarget = (targetDay - now.getDay() + 7) % 7 || 7;
|
|
94
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() + daysUntilTarget);
|
|
95
|
+
}
|
|
96
|
+
const lastWeekdayMatch = lowercaseInput.match(/^last\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)$/);
|
|
97
|
+
if (lastWeekdayMatch) {
|
|
98
|
+
const targetDay = weekdays.indexOf(lastWeekdayMatch[1]);
|
|
99
|
+
const daysSinceTarget = (now.getDay() - targetDay + 7) % 7 || 7;
|
|
100
|
+
return new Date(now.getFullYear(), now.getMonth(), now.getDate() - daysSinceTarget);
|
|
101
|
+
}
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Parse "time ago" strings like "5 minutes ago", "2 hours ago"
|
|
106
|
+
* @param input - time ago string
|
|
107
|
+
*/
|
|
108
|
+
export function parseTimeAgo(input) {
|
|
109
|
+
const now = new Date();
|
|
110
|
+
const lowercaseInput = input.toLowerCase().trim();
|
|
111
|
+
// Handle simple cases
|
|
112
|
+
if (lowercaseInput === 'just now' || lowercaseInput === 'now') {
|
|
113
|
+
return now;
|
|
114
|
+
}
|
|
115
|
+
// Handle "X time ago"
|
|
116
|
+
const match = lowercaseInput.match(/^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/);
|
|
117
|
+
if (match) {
|
|
118
|
+
const [, amount, unit] = match;
|
|
119
|
+
return subtractTimeUnits(now, parseInt(amount), unit);
|
|
120
|
+
}
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Parse custom date format
|
|
125
|
+
* @param dateString - date string to parse
|
|
126
|
+
* @param format - format pattern (e.g., "YYYY-MM-DD", "DD/MM/YYYY")
|
|
127
|
+
*/
|
|
128
|
+
export function parseCustomFormat(dateString, format) {
|
|
129
|
+
// Simple implementation for common patterns
|
|
130
|
+
const formatMap = {
|
|
131
|
+
'YYYY-MM-DD': /^(\d{4})-(\d{2})-(\d{2})$/,
|
|
132
|
+
'DD/MM/YYYY': /^(\d{2})\/(\d{2})\/(\d{4})$/,
|
|
133
|
+
'MM/DD/YYYY': /^(\d{2})\/(\d{2})\/(\d{4})$/,
|
|
134
|
+
'DD-MM-YYYY': /^(\d{2})-(\d{2})-(\d{4})$/,
|
|
135
|
+
'MM-DD-YYYY': /^(\d{2})-(\d{2})-(\d{4})$/,
|
|
136
|
+
};
|
|
137
|
+
const regex = formatMap[format];
|
|
138
|
+
if (!regex)
|
|
139
|
+
return null;
|
|
140
|
+
const match = dateString.match(regex);
|
|
141
|
+
if (!match)
|
|
142
|
+
return null;
|
|
143
|
+
const [, first, second, third] = match;
|
|
144
|
+
let year, month, day;
|
|
145
|
+
switch (format) {
|
|
146
|
+
case 'YYYY-MM-DD':
|
|
147
|
+
year = parseInt(first);
|
|
148
|
+
month = parseInt(second) - 1;
|
|
149
|
+
day = parseInt(third);
|
|
150
|
+
break;
|
|
151
|
+
case 'DD/MM/YYYY':
|
|
152
|
+
case 'DD-MM-YYYY':
|
|
153
|
+
day = parseInt(first);
|
|
154
|
+
month = parseInt(second) - 1;
|
|
155
|
+
year = parseInt(third);
|
|
156
|
+
break;
|
|
157
|
+
case 'MM/DD/YYYY':
|
|
158
|
+
case 'MM-DD-YYYY':
|
|
159
|
+
month = parseInt(first) - 1;
|
|
160
|
+
day = parseInt(second);
|
|
161
|
+
year = parseInt(third);
|
|
162
|
+
break;
|
|
163
|
+
default:
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
const date = new Date(year, month, day);
|
|
167
|
+
// Validate that the date components match what was parsed
|
|
168
|
+
// This catches cases like February 30th which JS converts to March 2nd
|
|
169
|
+
if (date.getFullYear() !== year || date.getMonth() !== month || date.getDate() !== day) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
return isNaN(date.getTime()) ? null : date;
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Try parsing with multiple formats
|
|
176
|
+
* @param dateString - date string to parse
|
|
177
|
+
* @param formats - array of format patterns to try
|
|
178
|
+
*/
|
|
179
|
+
export function parseManyFormats(dateString, formats) {
|
|
180
|
+
for (const format of formats) {
|
|
181
|
+
const result = parseCustomFormat(dateString, format);
|
|
182
|
+
if (result)
|
|
183
|
+
return result;
|
|
184
|
+
}
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
// Helper functions
|
|
188
|
+
function addTimeUnits(date, amount, unit) {
|
|
189
|
+
const result = new Date(date);
|
|
190
|
+
switch (unit) {
|
|
191
|
+
case 'second':
|
|
192
|
+
result.setSeconds(result.getSeconds() + amount);
|
|
193
|
+
break;
|
|
194
|
+
case 'minute':
|
|
195
|
+
result.setMinutes(result.getMinutes() + amount);
|
|
196
|
+
break;
|
|
197
|
+
case 'hour':
|
|
198
|
+
result.setHours(result.getHours() + amount);
|
|
199
|
+
break;
|
|
200
|
+
case 'day':
|
|
201
|
+
result.setDate(result.getDate() + amount);
|
|
202
|
+
break;
|
|
203
|
+
case 'week':
|
|
204
|
+
result.setDate(result.getDate() + (amount * 7));
|
|
205
|
+
break;
|
|
206
|
+
case 'month':
|
|
207
|
+
result.setMonth(result.getMonth() + amount);
|
|
208
|
+
break;
|
|
209
|
+
case 'year':
|
|
210
|
+
result.setFullYear(result.getFullYear() + amount);
|
|
211
|
+
break;
|
|
212
|
+
}
|
|
213
|
+
return result;
|
|
214
|
+
}
|
|
215
|
+
function subtractTimeUnits(date, amount, unit) {
|
|
216
|
+
return addTimeUnits(date, -amount, unit);
|
|
217
|
+
}
|