@timestamp-js/core 0.1.0-alpha.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 +21 -0
- package/README.md +75 -0
- package/dist/index.d.ts +980 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1410 -0
- package/dist/index.js.map +1 -0
- package/package.json +55 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1410 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Matches supported date and date-time input.
|
|
3
|
+
*
|
|
4
|
+
* Accepts `YYYY-MM`, `YYYY-MM-DD`, space-separated date/time strings,
|
|
5
|
+
* ISO-style `T` separators, optional seconds, optional milliseconds, and
|
|
6
|
+
* optional timezone suffixes such as `Z`, `+06:00`, or `-0700`.
|
|
7
|
+
*/
|
|
8
|
+
export const PARSE_DATETIME = /^(\d{4})-(\d{1,2})(?:-(\d{1,2}))?(?:[Tt\s]+(\d{1,2})(?::(\d{1,2}))?(?::(\d{1,2})(?:\.(\d{1,3}))?)?)?(?:\s*(Z|[+-]\d{2}:?\d{2}))?$/;
|
|
9
|
+
/**
|
|
10
|
+
* Matches the date portion of a timestamp string.
|
|
11
|
+
*/
|
|
12
|
+
export const PARSE_DATE = /^(\d{4})-(\d{1,2})(-(\d{1,2}))?/;
|
|
13
|
+
/**
|
|
14
|
+
* Matches `HH`, `HH:mm`, `HH:mm:ss`, or `HH:mm:ss.SSS` time strings.
|
|
15
|
+
*/
|
|
16
|
+
export const PARSE_TIME = /^(\d\d?)(?::(\d\d?))?(?::(\d\d?))?(?:\.(\d{1,3}))?$/;
|
|
17
|
+
/**
|
|
18
|
+
* Month lengths for a non-leap Gregorian year.
|
|
19
|
+
*
|
|
20
|
+
* Index `0` is intentionally unused so month numbers can be used directly.
|
|
21
|
+
*/
|
|
22
|
+
export const DAYS_IN_MONTH = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
23
|
+
/**
|
|
24
|
+
* Month lengths for a leap Gregorian year.
|
|
25
|
+
*
|
|
26
|
+
* Index `0` is intentionally unused so month numbers can be used directly.
|
|
27
|
+
*/
|
|
28
|
+
export const DAYS_IN_MONTH_LEAP = [0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
29
|
+
/**
|
|
30
|
+
* Shared conversion constants for milliseconds, seconds, minutes, hours, and days.
|
|
31
|
+
*/
|
|
32
|
+
export const TIME_CONSTANTS = {
|
|
33
|
+
MILLISECONDS_IN: {
|
|
34
|
+
SECOND: 1000,
|
|
35
|
+
MINUTE: 60000,
|
|
36
|
+
HOUR: 3600000,
|
|
37
|
+
DAY: 86400000,
|
|
38
|
+
WEEK: 604800000,
|
|
39
|
+
},
|
|
40
|
+
SECONDS_IN: {
|
|
41
|
+
MINUTE: 60,
|
|
42
|
+
HOUR: 3600,
|
|
43
|
+
DAY: 86400,
|
|
44
|
+
WEEK: 604800,
|
|
45
|
+
},
|
|
46
|
+
MINUTES_IN: {
|
|
47
|
+
MINUTE: 1,
|
|
48
|
+
HOUR: 60,
|
|
49
|
+
DAY: 1440,
|
|
50
|
+
WEEK: 10080,
|
|
51
|
+
},
|
|
52
|
+
HOURS_IN: {
|
|
53
|
+
DAY: 24,
|
|
54
|
+
WEEK: 168,
|
|
55
|
+
},
|
|
56
|
+
DAYS_IN: {
|
|
57
|
+
WEEK: 7,
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Minimum number of days found in any Gregorian month.
|
|
62
|
+
*/
|
|
63
|
+
export const DAYS_IN_MONTH_MIN = 28;
|
|
64
|
+
/**
|
|
65
|
+
* Maximum number of days found in any Gregorian month.
|
|
66
|
+
*/
|
|
67
|
+
export const DAYS_IN_MONTH_MAX = 31;
|
|
68
|
+
/**
|
|
69
|
+
* Maximum Gregorian month number.
|
|
70
|
+
*/
|
|
71
|
+
export const MONTH_MAX = 12;
|
|
72
|
+
/**
|
|
73
|
+
* Minimum Gregorian month number.
|
|
74
|
+
*/
|
|
75
|
+
export const MONTH_MIN = 1;
|
|
76
|
+
/**
|
|
77
|
+
* Minimum day-of-month number.
|
|
78
|
+
*/
|
|
79
|
+
export const DAY_MIN = 1;
|
|
80
|
+
/**
|
|
81
|
+
* First hour in a 24-hour day.
|
|
82
|
+
*/
|
|
83
|
+
export const FIRST_HOUR = 0;
|
|
84
|
+
/**
|
|
85
|
+
* Number of days in a week.
|
|
86
|
+
*/
|
|
87
|
+
export const DAYS_IN_WEEK = TIME_CONSTANTS.DAYS_IN.WEEK;
|
|
88
|
+
/**
|
|
89
|
+
* Number of minutes in an hour.
|
|
90
|
+
*/
|
|
91
|
+
export const MINUTES_IN_HOUR = TIME_CONSTANTS.MINUTES_IN.HOUR;
|
|
92
|
+
/**
|
|
93
|
+
* Number of hours in a day.
|
|
94
|
+
*/
|
|
95
|
+
export const HOURS_IN_DAY = TIME_CONSTANTS.HOURS_IN.DAY;
|
|
96
|
+
/**
|
|
97
|
+
* Number of milliseconds in one minute.
|
|
98
|
+
*/
|
|
99
|
+
export const MILLISECONDS_IN_MINUTE = TIME_CONSTANTS.MILLISECONDS_IN.MINUTE;
|
|
100
|
+
/**
|
|
101
|
+
* Number of milliseconds in one hour.
|
|
102
|
+
*/
|
|
103
|
+
export const MILLISECONDS_IN_HOUR = TIME_CONSTANTS.MILLISECONDS_IN.HOUR;
|
|
104
|
+
/**
|
|
105
|
+
* Number of milliseconds in one day.
|
|
106
|
+
*/
|
|
107
|
+
export const MILLISECONDS_IN_DAY = TIME_CONSTANTS.MILLISECONDS_IN.DAY;
|
|
108
|
+
/**
|
|
109
|
+
* Number of milliseconds in one week.
|
|
110
|
+
*/
|
|
111
|
+
export const MILLISECONDS_IN_WEEK = TIME_CONSTANTS.MILLISECONDS_IN.WEEK;
|
|
112
|
+
/**
|
|
113
|
+
* Frozen empty timestamp template.
|
|
114
|
+
*
|
|
115
|
+
* Use {@link copyTimestamp} or parser helpers to create new timestamp objects
|
|
116
|
+
* instead of mutating this shared default.
|
|
117
|
+
*/
|
|
118
|
+
export const Timestamp = freezeTimestamp({
|
|
119
|
+
date: "",
|
|
120
|
+
hasDay: false,
|
|
121
|
+
year: 0,
|
|
122
|
+
month: 0,
|
|
123
|
+
day: 0,
|
|
124
|
+
hasTime: false,
|
|
125
|
+
hour: 0,
|
|
126
|
+
minute: 0,
|
|
127
|
+
weekday: 0,
|
|
128
|
+
doy: 0,
|
|
129
|
+
workweek: 0,
|
|
130
|
+
past: false,
|
|
131
|
+
current: false,
|
|
132
|
+
future: false,
|
|
133
|
+
disabled: false,
|
|
134
|
+
});
|
|
135
|
+
/**
|
|
136
|
+
* Frozen empty time-object template.
|
|
137
|
+
*/
|
|
138
|
+
export const TimeObject = Object.freeze({
|
|
139
|
+
hour: 0,
|
|
140
|
+
minute: 0,
|
|
141
|
+
});
|
|
142
|
+
function freezeTimestamp(timestamp) {
|
|
143
|
+
return Object.freeze({ ...timestamp });
|
|
144
|
+
}
|
|
145
|
+
function cloneTimestamp(timestamp) {
|
|
146
|
+
return { ...timestamp };
|
|
147
|
+
}
|
|
148
|
+
function parseMillisecond(value) {
|
|
149
|
+
return value === undefined ? undefined : parseInt(value.padEnd(3, "0"), 10);
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Validates whether an input string matches the supported timestamp grammar.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} input A string in the form `YYYY-MM-DD`, `YYYY-MM-DD HH:mm`, or a full ISO-like date time.
|
|
155
|
+
* @returns {boolean} True if parseable
|
|
156
|
+
*/
|
|
157
|
+
export function validateTimestamp(input) {
|
|
158
|
+
if (typeof input !== "string")
|
|
159
|
+
return false;
|
|
160
|
+
return PARSE_DATETIME.test(input);
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Fast low-level parser for date and date-time strings.
|
|
164
|
+
*
|
|
165
|
+
* This parser fills numeric fields, but does not update formatted date,
|
|
166
|
+
* weekday, day-of-year, workweek, or relative flags. Use
|
|
167
|
+
* {@link parseTimestamp} when those derived fields are needed.
|
|
168
|
+
*
|
|
169
|
+
* @param {string} input In the form `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or an ISO-like date time with optional milliseconds and timezone suffix.
|
|
170
|
+
* @returns {Timestamp} This {@link Timestamp} is minimally filled in. The {@link Timestamp.date} and {@link Timestamp.time} as well as relative data will not be filled in.
|
|
171
|
+
*/
|
|
172
|
+
export function parsed(input) {
|
|
173
|
+
if (typeof input !== "string")
|
|
174
|
+
return null;
|
|
175
|
+
const parts = PARSE_DATETIME.exec(input);
|
|
176
|
+
if (!parts || !parts[1] || !parts[2])
|
|
177
|
+
return null;
|
|
178
|
+
const year = parseInt(parts[1], 10);
|
|
179
|
+
const month = parseInt(parts[2], 10);
|
|
180
|
+
const day = parseInt(parts[3] || "1", 10);
|
|
181
|
+
const hour = parseInt(parts[4] || "0", 10);
|
|
182
|
+
const minute = parseInt(parts[5] || "0", 10);
|
|
183
|
+
const second = parts[6] === undefined ? undefined : parseInt(parts[6], 10);
|
|
184
|
+
const millisecond = parseMillisecond(parts[7]);
|
|
185
|
+
const timestamp = {
|
|
186
|
+
date: input,
|
|
187
|
+
year,
|
|
188
|
+
month,
|
|
189
|
+
day,
|
|
190
|
+
hour,
|
|
191
|
+
minute,
|
|
192
|
+
hasDay: !!parts[3],
|
|
193
|
+
hasTime: true, // time is always present, even if '00:00'
|
|
194
|
+
past: false,
|
|
195
|
+
current: false,
|
|
196
|
+
future: false,
|
|
197
|
+
disabled: false,
|
|
198
|
+
weekday: 0,
|
|
199
|
+
doy: 0,
|
|
200
|
+
workweek: 0,
|
|
201
|
+
};
|
|
202
|
+
if (second !== undefined) {
|
|
203
|
+
timestamp.second = second;
|
|
204
|
+
}
|
|
205
|
+
if (millisecond !== undefined) {
|
|
206
|
+
timestamp.millisecond = millisecond;
|
|
207
|
+
}
|
|
208
|
+
if (parts[8] !== undefined) {
|
|
209
|
+
timestamp.timezone = parts[8];
|
|
210
|
+
}
|
|
211
|
+
timestamp.time = getTime(timestamp);
|
|
212
|
+
return freezeTimestamp(timestamp);
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Takes a JavaScript Date and returns a {@link Timestamp}. The {@link Timestamp} is not updated with relative information.
|
|
216
|
+
* @param {Date} date JavaScript Date
|
|
217
|
+
* @param {boolean} utc If set the {@link Timestamp} will parse the Date as UTC
|
|
218
|
+
* @returns {Timestamp} A minimal {@link Timestamp} without updated or relative updates.
|
|
219
|
+
*/
|
|
220
|
+
export function parseDate(date, utc = false) {
|
|
221
|
+
if (!(date instanceof Date))
|
|
222
|
+
return null;
|
|
223
|
+
const UTC = utc ? "UTC" : "";
|
|
224
|
+
const second = date[`get${UTC}Seconds`]();
|
|
225
|
+
const millisecond = date[`get${UTC}Milliseconds`]();
|
|
226
|
+
const timestamp = {
|
|
227
|
+
date: padNumber(date[`get${UTC}FullYear`](), 4) +
|
|
228
|
+
"-" +
|
|
229
|
+
padNumber(date[`get${UTC}Month`]() + 1, 2) +
|
|
230
|
+
"-" +
|
|
231
|
+
padNumber(date[`get${UTC}Date`](), 2),
|
|
232
|
+
time: padNumber(date[`get${UTC}Hours`]() || 0, 2) +
|
|
233
|
+
":" +
|
|
234
|
+
padNumber(date[`get${UTC}Minutes`]() || 0, 2),
|
|
235
|
+
year: date[`get${UTC}FullYear`](),
|
|
236
|
+
month: date[`get${UTC}Month`]() + 1,
|
|
237
|
+
day: date[`get${UTC}Date`](),
|
|
238
|
+
hour: date[`get${UTC}Hours`](),
|
|
239
|
+
minute: date[`get${UTC}Minutes`](),
|
|
240
|
+
weekday: 0,
|
|
241
|
+
doy: 0,
|
|
242
|
+
workweek: 0,
|
|
243
|
+
hasDay: true,
|
|
244
|
+
hasTime: true, // Date always has time, even if it is '00:00'
|
|
245
|
+
past: false,
|
|
246
|
+
current: false,
|
|
247
|
+
future: false,
|
|
248
|
+
disabled: false,
|
|
249
|
+
};
|
|
250
|
+
if (second !== 0) {
|
|
251
|
+
timestamp.second = second;
|
|
252
|
+
}
|
|
253
|
+
if (millisecond !== 0) {
|
|
254
|
+
timestamp.millisecond = millisecond;
|
|
255
|
+
}
|
|
256
|
+
return updateFormatted(timestamp);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Pads a number to a requested string length.
|
|
260
|
+
*
|
|
261
|
+
* Useful for formatting values such as `5` as `05`.
|
|
262
|
+
* @param {number} x The number to pad
|
|
263
|
+
* @param {number} length The length of the required number as a string
|
|
264
|
+
* @returns {string} The padded number (as a string). (ie: 5 = '05')
|
|
265
|
+
*/
|
|
266
|
+
export function padNumber(x, length) {
|
|
267
|
+
let padded = String(x);
|
|
268
|
+
while (padded.length < length) {
|
|
269
|
+
padded = "0" + padded;
|
|
270
|
+
}
|
|
271
|
+
return padded;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Returns if the passed year is a leap year
|
|
275
|
+
* @param {number} year The year to check (ie: 1999, 2020)
|
|
276
|
+
* @returns {boolean} True if the year is a leap year
|
|
277
|
+
*/
|
|
278
|
+
export function isLeapYear(year) {
|
|
279
|
+
// A year is a Gregorian leap year if it is divisible by 4,
|
|
280
|
+
// but not by 100, unless it is also divisible by 400.
|
|
281
|
+
return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Returns the days of the specified month in a year
|
|
285
|
+
* @param {number} year The year (ie: 1999, 2020)
|
|
286
|
+
* @param {number} month The Gregorian month number, where January is `1`
|
|
287
|
+
* @returns {number} The number of days in the month (corrected for leap years)
|
|
288
|
+
*/
|
|
289
|
+
export function daysInMonth(year, month) {
|
|
290
|
+
return (isLeapYear(year) ? DAYS_IN_MONTH_LEAP[month] : DAYS_IN_MONTH[month]);
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Returns a {@link Timestamp} of next day from passed in {@link Timestamp}
|
|
294
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
295
|
+
* @returns {Timestamp} A new {@link Timestamp} representing the next day
|
|
296
|
+
*/
|
|
297
|
+
export function nextDay(timestamp) {
|
|
298
|
+
const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day + 1);
|
|
299
|
+
return updateFormatted(normalizeTimestamp({
|
|
300
|
+
...timestamp,
|
|
301
|
+
year: date.getFullYear(),
|
|
302
|
+
month: date.getMonth() + 1,
|
|
303
|
+
day: date.getDate(),
|
|
304
|
+
}));
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Returns a {@link Timestamp} of previous day from passed in {@link Timestamp}
|
|
308
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
309
|
+
* @returns {Timestamp} A new {@link Timestamp} representing the previous day
|
|
310
|
+
*/
|
|
311
|
+
export function prevDay(timestamp) {
|
|
312
|
+
const date = new Date(timestamp.year, timestamp.month - 1, timestamp.day - 1);
|
|
313
|
+
return updateFormatted(normalizeTimestamp({
|
|
314
|
+
...timestamp,
|
|
315
|
+
year: date.getFullYear(),
|
|
316
|
+
month: date.getMonth() + 1,
|
|
317
|
+
day: date.getDate(),
|
|
318
|
+
}));
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Returns today's date
|
|
322
|
+
* @returns {string} Date string in the form `YYYY-MM-DD`
|
|
323
|
+
*/
|
|
324
|
+
export function today() {
|
|
325
|
+
const d = new Date(), month = d.getMonth() + 1, day = d.getDate(), year = d.getFullYear();
|
|
326
|
+
return [year, padNumber(month, 2), padNumber(day, 2)].join("-");
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Takes a date string ('YYYY-MM-DD') and validates if it is today's date
|
|
330
|
+
* @param {string} date Date string in the form 'YYYY-MM-DD'
|
|
331
|
+
* @returns {boolean} True if the date is today's date
|
|
332
|
+
*/
|
|
333
|
+
export function isToday(date) {
|
|
334
|
+
return date === today();
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Returns the start of the week give a {@link Timestamp} and weekdays (in which it finds the day representing the start of the week).
|
|
338
|
+
* If today {@link Timestamp} is passed in then this is used to update relative information in the returned {@link Timestamp}.
|
|
339
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the start of the week
|
|
340
|
+
* @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
|
|
341
|
+
* @param {Timestamp=} today If passed in then the {@link Timestamp} is updated with relative information
|
|
342
|
+
* @returns {Timestamp} A new {@link Timestamp} representing the start of the week
|
|
343
|
+
*/
|
|
344
|
+
export function getStartOfWeek(timestamp, weekdays, today) {
|
|
345
|
+
let start = cloneTimestamp(timestamp);
|
|
346
|
+
if (!weekdays) {
|
|
347
|
+
return freezeTimestamp(start);
|
|
348
|
+
}
|
|
349
|
+
if (start.day === 1 || start.weekday === 0) {
|
|
350
|
+
while (!weekdays.includes(Number(start.weekday))) {
|
|
351
|
+
start = nextDay(start);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
start = findWeekday(start, weekdays[0], prevDay);
|
|
355
|
+
start = updateFormatted(start);
|
|
356
|
+
if (today) {
|
|
357
|
+
start = updateRelative(start, today, start.hasTime);
|
|
358
|
+
}
|
|
359
|
+
return start;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Returns the end of the week give a {@link Timestamp} and weekdays (in which it finds the day representing the last of the week).
|
|
363
|
+
* If today {@link Timestamp} is passed in then this is used to update relative information in the returned {@link Timestamp}.
|
|
364
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the end of the week
|
|
365
|
+
* @param {number[]} weekdays The array is [0,1,2,3,4,5,6] where 0=Sunday and 6=Saturday
|
|
366
|
+
* @param {Timestamp=} today If passed in then the {@link Timestamp} is updated with relative information
|
|
367
|
+
* @returns {Timestamp} A new {@link Timestamp} representing the end of the week
|
|
368
|
+
*/
|
|
369
|
+
export function getEndOfWeek(timestamp, weekdays, today) {
|
|
370
|
+
let end = cloneTimestamp(timestamp);
|
|
371
|
+
if (!weekdays || !Array.isArray(weekdays)) {
|
|
372
|
+
return freezeTimestamp(end);
|
|
373
|
+
}
|
|
374
|
+
// is last day of month?
|
|
375
|
+
const lastDay = daysInMonth(end.year, end.month);
|
|
376
|
+
if (lastDay === end.day || end.weekday === weekdays[weekdays.length - 1]) {
|
|
377
|
+
while (!weekdays.includes(Number(end.weekday))) {
|
|
378
|
+
end = prevDay(end);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
end = findWeekday(end, weekdays[weekdays.length - 1], nextDay);
|
|
382
|
+
end = updateFormatted(end);
|
|
383
|
+
if (today) {
|
|
384
|
+
end = updateRelative(end, today, end.hasTime);
|
|
385
|
+
}
|
|
386
|
+
return end;
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Finds the start of the month based on the passed in {@link Timestamp}
|
|
390
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the start of the month
|
|
391
|
+
* @returns {Timestamp} A {@link Timestamp} of the start of the month
|
|
392
|
+
*/
|
|
393
|
+
export function getStartOfMonth(timestamp) {
|
|
394
|
+
let start = cloneTimestamp(timestamp);
|
|
395
|
+
start.day = DAY_MIN;
|
|
396
|
+
start = updateFormatted(start);
|
|
397
|
+
return start;
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Finds the end of the month based on the passed in {@link Timestamp}
|
|
401
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use to find the end of the month
|
|
402
|
+
* @returns {Timestamp} A {@link Timestamp} of the end of the month
|
|
403
|
+
*/
|
|
404
|
+
export function getEndOfMonth(timestamp) {
|
|
405
|
+
let end = cloneTimestamp(timestamp);
|
|
406
|
+
end.day = daysInMonth(end.year, end.month);
|
|
407
|
+
end = updateFormatted(end);
|
|
408
|
+
return end;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Converts time input into minutes since midnight.
|
|
412
|
+
*
|
|
413
|
+
* Strings may include seconds or milliseconds, but sub-minute precision is
|
|
414
|
+
* ignored for this minute-oriented helper.
|
|
415
|
+
*
|
|
416
|
+
* @param input - Minutes since midnight, a time string, or an object with hour and minute fields.
|
|
417
|
+
* @returns Minutes since midnight, or `false` when the input cannot be parsed.
|
|
418
|
+
*/
|
|
419
|
+
export function parseTime(input) {
|
|
420
|
+
const type = Object.prototype.toString.call(input);
|
|
421
|
+
switch (type) {
|
|
422
|
+
case "[object Number]":
|
|
423
|
+
// when a number is given, it's minutes since 12:00am
|
|
424
|
+
return input;
|
|
425
|
+
case "[object String]": {
|
|
426
|
+
// when a string is given, it's a hh:mm:ss format where seconds are optional, but not used for minute math
|
|
427
|
+
const parts = PARSE_TIME.exec(input);
|
|
428
|
+
if (!parts) {
|
|
429
|
+
return false;
|
|
430
|
+
}
|
|
431
|
+
return parseInt(parts[1], 10) * 60 + parseInt(parts[2] || "0", 10);
|
|
432
|
+
}
|
|
433
|
+
case "[object Object]":
|
|
434
|
+
// when an object is given, it must have hour and minute
|
|
435
|
+
if (typeof input !== "object" ||
|
|
436
|
+
typeof input.hour !== "number" ||
|
|
437
|
+
typeof input.minute !== "number") {
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
if (typeof input === "object" && "hour" in input && "minute" in input) {
|
|
441
|
+
return input.hour * 60 + input.minute;
|
|
442
|
+
}
|
|
443
|
+
return false;
|
|
444
|
+
}
|
|
445
|
+
return false;
|
|
446
|
+
}
|
|
447
|
+
/**
|
|
448
|
+
* Compares two {@link Timestamp}s for exactness
|
|
449
|
+
* @param {Timestamp} ts1 The first {@link Timestamp}
|
|
450
|
+
* @param {Timestamp} ts2 The second {@link Timestamp}
|
|
451
|
+
* @returns {boolean} True if the two {@link Timestamp}s are an exact match
|
|
452
|
+
*/
|
|
453
|
+
export function compareTimestamps(ts1, ts2) {
|
|
454
|
+
if (!ts1 || !ts2)
|
|
455
|
+
return false;
|
|
456
|
+
return (ts1.year === ts2.year &&
|
|
457
|
+
ts1.month === ts2.month &&
|
|
458
|
+
ts1.day === ts2.day &&
|
|
459
|
+
ts1.hour === ts2.hour &&
|
|
460
|
+
ts1.minute === ts2.minute &&
|
|
461
|
+
ts1.second === ts2.second &&
|
|
462
|
+
ts1.millisecond === ts2.millisecond &&
|
|
463
|
+
ts1.timezone === ts2.timezone);
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Compares the date of two {@link Timestamp}s that have been updated with relative data
|
|
467
|
+
* @param {Timestamp} ts1 The first {@link Timestamp}
|
|
468
|
+
* @param {Timestamp} ts2 The second {@link Timestamp}
|
|
469
|
+
* @returns {boolean} True if the two dates are the same
|
|
470
|
+
*/
|
|
471
|
+
export function compareDate(ts1, ts2) {
|
|
472
|
+
return getDate(ts1) === getDate(ts2);
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Compares the time of two {@link Timestamp}s that have been updated with relative data
|
|
476
|
+
* @param {Timestamp} ts1 The first {@link Timestamp}
|
|
477
|
+
* @param {Timestamp} ts2 The second {@link Timestamp}
|
|
478
|
+
* @returns {boolean} True if the two times are an exact match
|
|
479
|
+
*/
|
|
480
|
+
export function compareTime(ts1, ts2) {
|
|
481
|
+
return getTime(ts1) === getTime(ts2);
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Compares the date and time of two {@link Timestamp}s that have been updated with relative data
|
|
485
|
+
* @param {Timestamp} ts1 The first {@link Timestamp}
|
|
486
|
+
* @param {Timestamp} ts2 The second {@link Timestamp}
|
|
487
|
+
* @returns {boolean} True if the date and time are an exact match
|
|
488
|
+
*/
|
|
489
|
+
export function compareDateTime(ts1, ts2) {
|
|
490
|
+
return getDateTime(ts1) === getDateTime(ts2);
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* High-level parser that converts a string to a fully formatted {@link Timestamp}.
|
|
494
|
+
*
|
|
495
|
+
* If `now` is supplied, the returned timestamp also includes relative flags
|
|
496
|
+
* such as `past`, `current`, `future`, and `currentWeekday`.
|
|
497
|
+
*
|
|
498
|
+
* @param {string} input In the form `YYYY-MM-DD`, `YYYY-MM-DD HH:mm:ss`, or an ISO-like date time with optional milliseconds and timezone suffix.
|
|
499
|
+
* @param {Timestamp} now A {@link Timestamp} to use for relative data updates
|
|
500
|
+
* @returns {Timestamp} The {@link Timestamp.date} will be filled in as well as the {@link Timestamp.time} if a time is supplied and formatted fields (doy, weekday, workweek, etc). If 'now' is supplied, then relative data will also be updated.
|
|
501
|
+
*/
|
|
502
|
+
export function parseTimestamp(input, now = null) {
|
|
503
|
+
let timestamp = parsed(input);
|
|
504
|
+
if (!timestamp)
|
|
505
|
+
return null;
|
|
506
|
+
timestamp = updateFormatted(timestamp);
|
|
507
|
+
if (now) {
|
|
508
|
+
timestamp = updateRelative(timestamp, now, timestamp.hasTime);
|
|
509
|
+
}
|
|
510
|
+
return timestamp;
|
|
511
|
+
}
|
|
512
|
+
/**
|
|
513
|
+
* Converts a {@link Timestamp} into a numeric date identifier based on the passed {@link Timestamp}'s date
|
|
514
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
515
|
+
* @returns {number} The numeric date identifier
|
|
516
|
+
*/
|
|
517
|
+
export function getDayIdentifier(timestamp) {
|
|
518
|
+
return ((timestamp.year ?? 0) * 100000000 +
|
|
519
|
+
(timestamp.month ?? 0) * 1000000 +
|
|
520
|
+
(timestamp.day ?? 0) * 10000);
|
|
521
|
+
}
|
|
522
|
+
/**
|
|
523
|
+
* Converts a {@link Timestamp} into a numeric time identifier based on the passed {@link Timestamp}'s time
|
|
524
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
525
|
+
* @returns {number} The numeric time identifier
|
|
526
|
+
*/
|
|
527
|
+
export function getTimeIdentifier(timestamp) {
|
|
528
|
+
return (timestamp.hour ?? 0) * 100 + (timestamp.minute ?? 0);
|
|
529
|
+
}
|
|
530
|
+
function getTimeComparisonValue(timestamp) {
|
|
531
|
+
return ((((timestamp.hour ?? 0) * TIME_CONSTANTS.MINUTES_IN.HOUR + (timestamp.minute ?? 0)) *
|
|
532
|
+
TIME_CONSTANTS.SECONDS_IN.MINUTE +
|
|
533
|
+
(timestamp.second ?? 0)) *
|
|
534
|
+
TIME_CONSTANTS.MILLISECONDS_IN.SECOND +
|
|
535
|
+
(timestamp.millisecond ?? 0));
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Converts a {@link Timestamp} into a numeric date and time identifier based on the passed {@link Timestamp}'s date and time
|
|
539
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
540
|
+
* @returns {number} The numeric date+time identifier
|
|
541
|
+
*/
|
|
542
|
+
export function getDayTimeIdentifier(timestamp) {
|
|
543
|
+
return getDayIdentifier(timestamp) + getTimeIdentifier(timestamp);
|
|
544
|
+
}
|
|
545
|
+
/**
|
|
546
|
+
* Returns the difference between two {@link Timestamp}s
|
|
547
|
+
* @param {Timestamp} ts1 The first {@link Timestamp}
|
|
548
|
+
* @param {Timestamp} ts2 The second {@link Timestamp}
|
|
549
|
+
* @param {boolean=} strict Optional flag to not to return negative numbers
|
|
550
|
+
* @returns {number} The difference
|
|
551
|
+
*/
|
|
552
|
+
export function diffTimestamp(ts1, ts2, strict = false) {
|
|
553
|
+
const utc1 = Date.UTC(ts1.year ?? 0, (ts1.month ?? 1) - 1, ts1.day ?? 1, ts1.hour ?? 0, ts1.minute ?? 0, ts1.second ?? 0, ts1.millisecond ?? 0);
|
|
554
|
+
const utc2 = Date.UTC(ts2.year ?? 0, (ts2.month ?? 1) - 1, ts2.day ?? 1, ts2.hour ?? 0, ts2.minute ?? 0, ts2.second ?? 0, ts2.millisecond ?? 0);
|
|
555
|
+
if (strict === true && utc2 < utc1) {
|
|
556
|
+
// Not negative number
|
|
557
|
+
// utc2 - utc1 < 0 -> utc2 < utc1 -> NO: utc1 >= utc2
|
|
558
|
+
return 0;
|
|
559
|
+
}
|
|
560
|
+
return utc2 - utc1;
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Updates a {@link Timestamp} with relative data (past, current and future)
|
|
564
|
+
* @param {Timestamp} timestamp The {@link Timestamp} that needs relative data updated
|
|
565
|
+
* @param {Timestamp} now {@link Timestamp} that represents the current date (optional time)
|
|
566
|
+
* @param {boolean=} time Optional flag to include time ('timestamp' and 'now' params should have time values)
|
|
567
|
+
* @returns {Timestamp} A new {@link Timestamp}
|
|
568
|
+
*/
|
|
569
|
+
export function updateRelative(timestamp, now, time = false) {
|
|
570
|
+
const ts = cloneTimestamp(timestamp);
|
|
571
|
+
let a = getDayIdentifier(now);
|
|
572
|
+
let b = getDayIdentifier(ts);
|
|
573
|
+
let current = a === b;
|
|
574
|
+
if (ts.hasTime && time && current) {
|
|
575
|
+
a = getTimeComparisonValue(now);
|
|
576
|
+
b = getTimeComparisonValue(ts);
|
|
577
|
+
current = a === b;
|
|
578
|
+
}
|
|
579
|
+
ts.past = b < a;
|
|
580
|
+
ts.current = current;
|
|
581
|
+
ts.future = b > a;
|
|
582
|
+
ts.currentWeekday = ts.weekday === now.weekday;
|
|
583
|
+
return freezeTimestamp(ts);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Returns a timestamp set to a number of minutes past midnight.
|
|
587
|
+
*
|
|
588
|
+
* The returned timestamp has updated hour/minute fields and clears second and
|
|
589
|
+
* millisecond precision because this helper is minute-oriented.
|
|
590
|
+
*
|
|
591
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
592
|
+
* @param {number} minutes The number of minutes to set from midnight
|
|
593
|
+
* @param {Timestamp=} now Optional {@link Timestamp} representing current date and time
|
|
594
|
+
* @returns {Timestamp} A new {@link Timestamp}
|
|
595
|
+
*/
|
|
596
|
+
export function updateMinutes(timestamp, minutes, now = null) {
|
|
597
|
+
let ts = cloneTimestamp(timestamp);
|
|
598
|
+
ts.hasTime = true;
|
|
599
|
+
ts.hour = Math.floor(minutes / TIME_CONSTANTS.MINUTES_IN.HOUR);
|
|
600
|
+
ts.minute = minutes % TIME_CONSTANTS.MINUTES_IN.HOUR;
|
|
601
|
+
delete ts.second;
|
|
602
|
+
delete ts.millisecond;
|
|
603
|
+
ts.time = getTime(ts);
|
|
604
|
+
if (now) {
|
|
605
|
+
return updateRelative(ts, now, true);
|
|
606
|
+
}
|
|
607
|
+
return freezeTimestamp(ts);
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Updates the {@link Timestamp} with the weekday
|
|
611
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
612
|
+
* @returns A new Timestamp
|
|
613
|
+
*/
|
|
614
|
+
export function updateWeekday(timestamp) {
|
|
615
|
+
const ts = cloneTimestamp(timestamp);
|
|
616
|
+
ts.weekday = getWeekday(ts);
|
|
617
|
+
return freezeTimestamp(ts);
|
|
618
|
+
}
|
|
619
|
+
/**
|
|
620
|
+
* Updates the {@link Timestamp} with the day of the year (doy)
|
|
621
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
622
|
+
* @returns A new Timestamp
|
|
623
|
+
*/
|
|
624
|
+
export function updateDayOfYear(timestamp) {
|
|
625
|
+
const ts = cloneTimestamp(timestamp);
|
|
626
|
+
ts.doy = getDayOfYear(ts) || 0;
|
|
627
|
+
return freezeTimestamp(ts);
|
|
628
|
+
}
|
|
629
|
+
/**
|
|
630
|
+
* Updates the {@link Timestamp} with the workweek
|
|
631
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
632
|
+
* @returns A new {@link Timestamp}
|
|
633
|
+
*/
|
|
634
|
+
export function updateWorkWeek(timestamp) {
|
|
635
|
+
const ts = cloneTimestamp(timestamp);
|
|
636
|
+
ts.workweek = getWorkWeek(ts);
|
|
637
|
+
return freezeTimestamp(ts);
|
|
638
|
+
}
|
|
639
|
+
function isDisabledDayConfig(value) {
|
|
640
|
+
return typeof value === "object" && value !== null && Array.isArray(value) === false;
|
|
641
|
+
}
|
|
642
|
+
function applyDisabledDayConfig(timestamp, config) {
|
|
643
|
+
timestamp.disabled = true;
|
|
644
|
+
if (config !== undefined) {
|
|
645
|
+
timestamp.disabledColor = config.color;
|
|
646
|
+
timestamp.disabledTextColor = config.textColor;
|
|
647
|
+
timestamp.disabledClass = config.class;
|
|
648
|
+
timestamp.disabledStyle = config.style;
|
|
649
|
+
timestamp.disabledLabel = config.label;
|
|
650
|
+
}
|
|
651
|
+
return timestamp;
|
|
652
|
+
}
|
|
653
|
+
function isTimestampInDisabledDay(timestamp, day) {
|
|
654
|
+
const target = getDayIdentifier(timestamp);
|
|
655
|
+
if (Array.isArray(day) === true) {
|
|
656
|
+
if (day.length === 2 && day[0] && day[1]) {
|
|
657
|
+
const start = parsed(day[0]);
|
|
658
|
+
const end = parsed(day[1]);
|
|
659
|
+
return start !== null && end !== null && isBetweenDates(timestamp, start, end);
|
|
660
|
+
}
|
|
661
|
+
return day.some((date) => {
|
|
662
|
+
const disabledDay = parseTimestamp(date);
|
|
663
|
+
return disabledDay !== null && getDayIdentifier(disabledDay) === target;
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
if (isDisabledDayConfig(day) === true) {
|
|
667
|
+
const date = day.date;
|
|
668
|
+
const startDate = day.from ?? day.start;
|
|
669
|
+
const endDate = day.to ?? day.end;
|
|
670
|
+
if (date !== undefined) {
|
|
671
|
+
const disabledDay = parseTimestamp(date);
|
|
672
|
+
return disabledDay !== null && getDayIdentifier(disabledDay) === target;
|
|
673
|
+
}
|
|
674
|
+
if (startDate !== undefined && endDate !== undefined) {
|
|
675
|
+
const start = parsed(startDate);
|
|
676
|
+
const end = parsed(endDate);
|
|
677
|
+
return start !== null && end !== null && isBetweenDates(timestamp, start, end);
|
|
678
|
+
}
|
|
679
|
+
return false;
|
|
680
|
+
}
|
|
681
|
+
const disabledDay = parseTimestamp(day);
|
|
682
|
+
return disabledDay !== null && getDayIdentifier(disabledDay) === target;
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Updates the passed {@link Timestamp} with disabled, if needed
|
|
686
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
687
|
+
* @param {string} [disabledBefore] In `YYYY-MM-DD` format
|
|
688
|
+
* @param {string} [disabledAfter] In `YYYY-MM-DD` format
|
|
689
|
+
* @param {number[]} [disabledWeekdays] An array of numbers representing weekdays [0 = Sun, ..., 6 = Sat]
|
|
690
|
+
* @param {DisabledDays} [disabledDays] An array of days in 'YYYY-MM-DD' format. If an array with a pair of dates is in first array, then this is treated as a range. Object entries can include date/from/to plus color metadata.
|
|
691
|
+
* @returns A new {@link Timestamp}
|
|
692
|
+
*/
|
|
693
|
+
export function updateDisabled(timestamp, disabledBefore, disabledAfter, disabledWeekdays, disabledDays) {
|
|
694
|
+
let ts = cloneTimestamp(timestamp);
|
|
695
|
+
const t = getDayIdentifier(ts);
|
|
696
|
+
if (disabledBefore !== undefined) {
|
|
697
|
+
const disabledDay = parsed(disabledBefore);
|
|
698
|
+
if (disabledDay) {
|
|
699
|
+
const before = getDayIdentifier(disabledDay);
|
|
700
|
+
if (t <= before) {
|
|
701
|
+
ts.disabled = true;
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (ts.disabled !== true && disabledAfter !== undefined) {
|
|
706
|
+
const disabledDay = parsed(disabledAfter);
|
|
707
|
+
if (disabledDay) {
|
|
708
|
+
const after = getDayIdentifier(disabledDay);
|
|
709
|
+
if (t >= after) {
|
|
710
|
+
ts.disabled = true;
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
if (ts.disabled !== true && Array.isArray(disabledWeekdays) && disabledWeekdays.length > 0) {
|
|
715
|
+
for (const weekday in disabledWeekdays) {
|
|
716
|
+
if (disabledWeekdays[weekday] === ts.weekday) {
|
|
717
|
+
ts.disabled = true;
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
if (ts.disabled !== true && Array.isArray(disabledDays) && disabledDays.length > 0) {
|
|
723
|
+
for (const day of disabledDays) {
|
|
724
|
+
if (isTimestampInDisabledDay(ts, day) === true) {
|
|
725
|
+
ts = applyDisabledDayConfig(ts, isDisabledDayConfig(day) === true ? day : undefined);
|
|
726
|
+
break;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
return freezeTimestamp(ts);
|
|
731
|
+
}
|
|
732
|
+
/**
|
|
733
|
+
* Updates the passed {@link Timestamp} with formatted data (time string, date string, weekday, day of year and workweek)
|
|
734
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
735
|
+
* @returns A new {@link Timestamp}
|
|
736
|
+
*/
|
|
737
|
+
export function updateFormatted(timestamp) {
|
|
738
|
+
const ts = cloneTimestamp(timestamp);
|
|
739
|
+
ts.hasTime = true;
|
|
740
|
+
ts.time = getTime(ts);
|
|
741
|
+
ts.date = getDate(ts);
|
|
742
|
+
ts.weekday = getWeekday(ts);
|
|
743
|
+
ts.doy = getDayOfYear(ts) || 0;
|
|
744
|
+
ts.workweek = getWorkWeek(ts);
|
|
745
|
+
return freezeTimestamp(ts);
|
|
746
|
+
}
|
|
747
|
+
/**
|
|
748
|
+
* Returns day of the year (doy) for the passed in {@link Timestamp}
|
|
749
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
750
|
+
* @returns {number} The day of the year
|
|
751
|
+
*/
|
|
752
|
+
export function getDayOfYear(timestamp) {
|
|
753
|
+
if (timestamp.year === 0)
|
|
754
|
+
return;
|
|
755
|
+
return ((Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day) -
|
|
756
|
+
Date.UTC(timestamp.year, 0, 0)) /
|
|
757
|
+
24 /
|
|
758
|
+
60 /
|
|
759
|
+
60 /
|
|
760
|
+
1000);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Returns workweek for the passed in {@link Timestamp}
|
|
764
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
765
|
+
* @returns {number} The work week
|
|
766
|
+
*/
|
|
767
|
+
export function getWorkWeek(timestamp) {
|
|
768
|
+
let ts = timestamp;
|
|
769
|
+
if (ts.year === 0) {
|
|
770
|
+
const parsedToday = parseTimestamp(today());
|
|
771
|
+
if (parsedToday) {
|
|
772
|
+
ts = parsedToday;
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
// Remove time components of date
|
|
776
|
+
const weekday = new Date(Date.UTC(ts.year, ts.month - 1, ts.day));
|
|
777
|
+
// Adjust the date to the correct day of the week
|
|
778
|
+
const dayAdjustment = 4; // thursday is 4
|
|
779
|
+
weekday.setUTCDate(weekday.getUTCDate() - ((weekday.getUTCDay() + 6) % 7) + dayAdjustment);
|
|
780
|
+
// Set to nearest Thursday: current date + 4 - current day number
|
|
781
|
+
// Make Sunday's day number 7
|
|
782
|
+
weekday.setUTCDate(weekday.getUTCDate() + dayAdjustment - (weekday.getUTCDay() || 7));
|
|
783
|
+
// Get first day of year
|
|
784
|
+
var yearStart = new Date(Date.UTC(weekday.getUTCFullYear(), 0, 1));
|
|
785
|
+
// Calculate full weeks to nearest Thursday
|
|
786
|
+
var weekNumber = Math.ceil(((weekday.valueOf() - yearStart.valueOf()) / 86400000 + 1) / 7);
|
|
787
|
+
return weekNumber;
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Returns weekday for the passed in {@link Timestamp}
|
|
791
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
792
|
+
* @returns {number} The weekday
|
|
793
|
+
*/
|
|
794
|
+
export function getWeekday(timestamp) {
|
|
795
|
+
let weekday = timestamp.weekday;
|
|
796
|
+
if (timestamp.hasDay) {
|
|
797
|
+
const floor = Math.floor;
|
|
798
|
+
const day = timestamp.day;
|
|
799
|
+
const month = ((timestamp.month + 9) % MONTH_MAX) + 1;
|
|
800
|
+
const century = floor(timestamp.year / 100);
|
|
801
|
+
const year = (timestamp.year % 100) - (timestamp.month <= 2 ? 1 : 0);
|
|
802
|
+
weekday =
|
|
803
|
+
(((day +
|
|
804
|
+
floor(2.6 * month - 0.2) -
|
|
805
|
+
2 * century +
|
|
806
|
+
year +
|
|
807
|
+
floor(year / 4) +
|
|
808
|
+
floor(century / 4)) %
|
|
809
|
+
7) +
|
|
810
|
+
7) %
|
|
811
|
+
7;
|
|
812
|
+
}
|
|
813
|
+
return weekday ?? 0;
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Makes a copy of the passed in {@link Timestamp}
|
|
817
|
+
* @param {Timestamp} timestamp The original {@link Timestamp}
|
|
818
|
+
* @returns {Timestamp} A copy of the original {@link Timestamp}
|
|
819
|
+
*/
|
|
820
|
+
export function copyTimestamp(timestamp) {
|
|
821
|
+
return freezeTimestamp(timestamp);
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Used internally to convert {@link Timestamp} used with 'parsed' or 'parseDate' so the 'date' portion of the {@link Timestamp} is correct.
|
|
825
|
+
* @param {Timestamp} timestamp The (raw) {@link Timestamp}
|
|
826
|
+
* @returns {string} A formatted date ('YYYY-MM-DD')
|
|
827
|
+
*/
|
|
828
|
+
export function getDate(timestamp) {
|
|
829
|
+
let str = `${padNumber(timestamp.year, 4)}-${padNumber(timestamp.month, 2)}`;
|
|
830
|
+
if (timestamp.hasDay)
|
|
831
|
+
str += `-${padNumber(timestamp.day, 2)}`;
|
|
832
|
+
return str;
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Used internally to convert {@link Timestamp} with 'parsed' or 'parseDate' so the 'time' portion of the {@link Timestamp} is correct.
|
|
836
|
+
* @param {Timestamp} timestamp The (raw) {@link Timestamp}
|
|
837
|
+
* @returns {string} A formatted time ('hh:mm')
|
|
838
|
+
*/
|
|
839
|
+
export function getTime(timestamp) {
|
|
840
|
+
if (!timestamp.hasTime) {
|
|
841
|
+
return "";
|
|
842
|
+
}
|
|
843
|
+
let time = `${padNumber(timestamp.hour, 2)}:${padNumber(timestamp.minute, 2)}`;
|
|
844
|
+
if (timestamp.second !== undefined || timestamp.millisecond !== undefined) {
|
|
845
|
+
time += `:${padNumber(timestamp.second ?? 0, 2)}`;
|
|
846
|
+
}
|
|
847
|
+
if (timestamp.millisecond !== undefined) {
|
|
848
|
+
time += `.${padNumber(timestamp.millisecond, 3)}`;
|
|
849
|
+
}
|
|
850
|
+
return time;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Returns a formatted string date and time ('YYYY-YY-MM hh:mm')
|
|
854
|
+
* @param {Timestamp} timestamp The {@link Timestamp}
|
|
855
|
+
* @returns {string} A formatted date time ('YYYY-MM-DD HH:mm')
|
|
856
|
+
*/
|
|
857
|
+
export function getDateTime(timestamp) {
|
|
858
|
+
return getDate(timestamp) + " " + (timestamp.hasTime ? getTime(timestamp) : "00:00");
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* An alias for {relativeDays}
|
|
862
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
863
|
+
* @param {function} [mover=nextDay] The mover function to use (ie: {nextDay} or {prevDay}).
|
|
864
|
+
* @param {number} [days=1] The number of days to move.
|
|
865
|
+
* @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
|
|
866
|
+
* @returns A new {@link Timestamp}
|
|
867
|
+
*/
|
|
868
|
+
export function moveRelativeDays(timestamp, mover = nextDay, days = 1, allowedWeekdays = [0, 1, 2, 3, 4, 5, 6]) {
|
|
869
|
+
return relativeDays(timestamp, mover, days, allowedWeekdays);
|
|
870
|
+
}
|
|
871
|
+
/**
|
|
872
|
+
* Moves the {@link Timestamp} the number of relative days
|
|
873
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
874
|
+
* @param {function} [mover=nextDay] The mover function to use (ie: {nextDay} or {prevDay}).
|
|
875
|
+
* @param {number} [days=1] The number of days to move.
|
|
876
|
+
* @param {number[]} [allowedWeekdays=[ 0, 1, 2, 3, 4, 5, 6 ]] An array of numbers representing the weekdays. ie: [0 = Sun, ..., 6 = Sat].
|
|
877
|
+
* @returns A new {@link Timestamp}
|
|
878
|
+
*/
|
|
879
|
+
export function relativeDays(timestamp, mover = nextDay, days = 1, allowedWeekdays = [0, 1, 2, 3, 4, 5, 6]) {
|
|
880
|
+
let ts = copyTimestamp(timestamp);
|
|
881
|
+
if (!allowedWeekdays.includes(Number(ts.weekday)) && ts.weekday === 0 && mover === nextDay) {
|
|
882
|
+
++days;
|
|
883
|
+
}
|
|
884
|
+
while (--days >= 0) {
|
|
885
|
+
ts = mover(ts);
|
|
886
|
+
if (allowedWeekdays.length < 7 && !allowedWeekdays.includes(Number(ts.weekday))) {
|
|
887
|
+
++days;
|
|
888
|
+
}
|
|
889
|
+
}
|
|
890
|
+
return ts;
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Finds the specified weekday (forward or back) based on the {@link Timestamp}
|
|
894
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to transform
|
|
895
|
+
* @param {number} weekday The weekday number (Sun = 0, ..., Sat = 6)
|
|
896
|
+
* @param {function} [mover=nextDay] The function to use ({prevDay} or {nextDay}).
|
|
897
|
+
* @param {number} [maxDays=6] The number of days to look forward or back.
|
|
898
|
+
* @returns A new {@link Timestamp}
|
|
899
|
+
*/
|
|
900
|
+
export function findWeekday(timestamp, weekday, mover = nextDay, maxDays = 6) {
|
|
901
|
+
let ts = copyTimestamp(timestamp);
|
|
902
|
+
while (ts.weekday !== weekday && --maxDays >= 0)
|
|
903
|
+
ts = mover(ts);
|
|
904
|
+
return ts;
|
|
905
|
+
}
|
|
906
|
+
/**
|
|
907
|
+
* Creates an array of {@link Timestamp}s based on start and end params
|
|
908
|
+
* @param {Timestamp} start The starting {@link Timestamp}
|
|
909
|
+
* @param {Timestamp} end The ending {@link Timestamp}
|
|
910
|
+
* @param {Timestamp} now The relative day
|
|
911
|
+
* @param {number[]} weekdays An array of numbers (representing days of the week) that are 0 (=Sunday) to 6 (=Saturday)
|
|
912
|
+
* @param {string} [disabledBefore] Days before this date are disabled (YYYY-MM-DD)
|
|
913
|
+
* @param {string} [disabledAfter] Days after this date are disabled (YYYY-MM-DD)
|
|
914
|
+
* @param {number[]} [disabledWeekdays] An array representing weekdays that are disabled [0 = Sun, ..., 6 = Sat]
|
|
915
|
+
* @param {DisabledDays} [disabledDays] An array of days in 'YYYY-MM-DD' format. If an array with a pair of dates is in first array, then this is treated as a range.
|
|
916
|
+
* @param {number} [max=42] Max days to do
|
|
917
|
+
* @param {number} [min=0] Min days to do
|
|
918
|
+
* @returns {Timestamp[]} The requested array of {@link Timestamp}s
|
|
919
|
+
*/
|
|
920
|
+
export function createDayList(start, end, now, weekdays = [0, 1, 2, 3, 4, 5, 6], disabledBefore = undefined, disabledAfter = undefined, disabledWeekdays = [], disabledDays = [], max = 42, min = 0) {
|
|
921
|
+
const begin = getDayIdentifier(start);
|
|
922
|
+
const stop = getDayIdentifier(end);
|
|
923
|
+
const days = [];
|
|
924
|
+
let current = copyTimestamp(start);
|
|
925
|
+
let currentIdentifier = 0;
|
|
926
|
+
let stopped = currentIdentifier === stop;
|
|
927
|
+
if (stop < begin) {
|
|
928
|
+
return days;
|
|
929
|
+
}
|
|
930
|
+
while ((!stopped || days.length < min) && days.length < max) {
|
|
931
|
+
currentIdentifier = getDayIdentifier(current);
|
|
932
|
+
stopped = stopped || (currentIdentifier > stop && days.length >= min);
|
|
933
|
+
if (stopped) {
|
|
934
|
+
break;
|
|
935
|
+
}
|
|
936
|
+
if (!weekdays.includes(Number(current.weekday))) {
|
|
937
|
+
current = relativeDays(current, nextDay);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
let day = copyTimestamp(current);
|
|
941
|
+
day = updateFormatted(day);
|
|
942
|
+
day = updateRelative(day, now);
|
|
943
|
+
day = updateDisabled(day, disabledBefore, disabledAfter, disabledWeekdays, disabledDays);
|
|
944
|
+
days.push(day);
|
|
945
|
+
current = relativeDays(current, nextDay);
|
|
946
|
+
}
|
|
947
|
+
return days;
|
|
948
|
+
}
|
|
949
|
+
/**
|
|
950
|
+
* Creates an array of interval {@link Timestamp}s based on params
|
|
951
|
+
* @param {Timestamp} timestamp The starting {@link Timestamp}
|
|
952
|
+
* @param {number} first The starting interval time
|
|
953
|
+
* @param {number} minutes How many minutes between intervals (ie: 60, 30, 15 would be common ones)
|
|
954
|
+
* @param {number} count The number of intervals needed
|
|
955
|
+
* @param {Timestamp} now A relative {@link Timestamp} with time
|
|
956
|
+
* @returns {Timestamp[]} The requested array of interval {@link Timestamp}s
|
|
957
|
+
*/
|
|
958
|
+
export function createIntervalList(timestamp, first, minutes, count, now) {
|
|
959
|
+
const intervals = [];
|
|
960
|
+
for (let i = 0; i < count; ++i) {
|
|
961
|
+
const mins = (first + i) * minutes;
|
|
962
|
+
intervals.push(updateMinutes(timestamp, mins, now));
|
|
963
|
+
}
|
|
964
|
+
return intervals;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* @callback getOptions
|
|
968
|
+
* @param {Timestamp} timestamp A {@link Timestamp} object
|
|
969
|
+
* @param {boolean} short True if using short options
|
|
970
|
+
* @returns {Object} An Intl object representing options to be used
|
|
971
|
+
*/
|
|
972
|
+
/**
|
|
973
|
+
* @callback formatter
|
|
974
|
+
* @param {Timestamp} timestamp The {@link Timestamp} being used
|
|
975
|
+
* @param {boolean} short If short format is being requested
|
|
976
|
+
* @returns {string} The localized string of the formatted {@link Timestamp}
|
|
977
|
+
*/
|
|
978
|
+
/**
|
|
979
|
+
* Returns a locale formatter backed by `Intl.DateTimeFormat`.
|
|
980
|
+
*
|
|
981
|
+
* The helper is SSR-safe: if `Intl.DateTimeFormat` is unavailable in a target
|
|
982
|
+
* runtime, it returns a formatter that produces an empty string instead of
|
|
983
|
+
* throwing during module load.
|
|
984
|
+
*
|
|
985
|
+
* @param {string} locale The locale to use (ie: en-US)
|
|
986
|
+
* @param {getOptions} cb The function to call for options. This function should return an Intl formatted object. The function is passed (timestamp, short).
|
|
987
|
+
* @returns {formatter} The function has params (timestamp, short). The short is to use the short options.
|
|
988
|
+
*/
|
|
989
|
+
export function createNativeLocaleFormatter(locale, cb) {
|
|
990
|
+
const emptyFormatter = () => "";
|
|
991
|
+
/* istanbul ignore next */
|
|
992
|
+
if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
|
|
993
|
+
return emptyFormatter;
|
|
994
|
+
}
|
|
995
|
+
return (timestamp, short) => {
|
|
996
|
+
try {
|
|
997
|
+
const intlFormatter = new Intl.DateTimeFormat(locale || undefined, cb(timestamp, short));
|
|
998
|
+
return intlFormatter.format(makeDateTime(timestamp));
|
|
999
|
+
}
|
|
1000
|
+
catch (e) /* istanbul ignore next */ {
|
|
1001
|
+
console.error(`Intl.DateTimeFormat: ${e.message} -> ${getDateTime(timestamp)}`);
|
|
1002
|
+
return "";
|
|
1003
|
+
}
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
/**
|
|
1007
|
+
* Makes a JavaScript Date from the passed {@link Timestamp}
|
|
1008
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
1009
|
+
* @param {boolean} utc True to get Date object using UTC
|
|
1010
|
+
* @returns {Date} A JavaScript Date
|
|
1011
|
+
*/
|
|
1012
|
+
export function makeDate(timestamp, utc = true) {
|
|
1013
|
+
if (utc)
|
|
1014
|
+
return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0));
|
|
1015
|
+
return new Date(timestamp.year, timestamp.month - 1, timestamp.day, 0, 0);
|
|
1016
|
+
}
|
|
1017
|
+
/**
|
|
1018
|
+
* Makes a JavaScript Date from the passed {@link Timestamp} (with time)
|
|
1019
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to use
|
|
1020
|
+
* @param {boolean} utc True to get Date object using UTC
|
|
1021
|
+
* @returns {Date} A JavaScript Date
|
|
1022
|
+
*/
|
|
1023
|
+
export function makeDateTime(timestamp, utc = true) {
|
|
1024
|
+
if (utc)
|
|
1025
|
+
return new Date(Date.UTC(timestamp.year, timestamp.month - 1, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0));
|
|
1026
|
+
return new Date(timestamp.year, timestamp.month - 1, timestamp.day, timestamp.hour, timestamp.minute, timestamp.second ?? 0, timestamp.millisecond ?? 0);
|
|
1027
|
+
}
|
|
1028
|
+
/**
|
|
1029
|
+
* Converts a {@link Timestamp} to a local JavaScript `Date`.
|
|
1030
|
+
*
|
|
1031
|
+
* This is equivalent to `makeDateTime(timestamp, false)`.
|
|
1032
|
+
*
|
|
1033
|
+
* @param {Timestamp} timestamp The {@link Timestamp} to convert
|
|
1034
|
+
* @returns {Date} A local JavaScript Date
|
|
1035
|
+
*/
|
|
1036
|
+
export function getDateObject(timestamp) {
|
|
1037
|
+
return makeDateTime(timestamp, false);
|
|
1038
|
+
}
|
|
1039
|
+
/**
|
|
1040
|
+
* Validates if the input is a finite number.
|
|
1041
|
+
*
|
|
1042
|
+
* @param input - The value to be validated. Can be a string or a number.
|
|
1043
|
+
* @returns A boolean indicating whether the input is a finite number.
|
|
1044
|
+
* Returns true if the input is a finite number, false otherwise.
|
|
1045
|
+
*/
|
|
1046
|
+
export function validateNumber(input) {
|
|
1047
|
+
return isFinite(Number(input));
|
|
1048
|
+
}
|
|
1049
|
+
/**
|
|
1050
|
+
* Given an array of {@link Timestamp}s, finds the max date (and possible time)
|
|
1051
|
+
* @param {Timestamp[]} timestamps This is an array of {@link Timestamp}s
|
|
1052
|
+
* @param {boolean=} useTime Default false; if true, uses time in the comparison as well
|
|
1053
|
+
* @returns The {@link Timestamp} with the highest date (and possibly time) value
|
|
1054
|
+
*/
|
|
1055
|
+
export function maxTimestamp(timestamps, useTime = false) {
|
|
1056
|
+
const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier;
|
|
1057
|
+
return timestamps.reduce((prev, cur) => {
|
|
1058
|
+
return Math.max(func(prev), func(cur)) === func(prev) ? prev : cur;
|
|
1059
|
+
});
|
|
1060
|
+
}
|
|
1061
|
+
/**
|
|
1062
|
+
* Given an array of {@link Timestamp}s, finds the min date (and possible time)
|
|
1063
|
+
* @param {Timestamp[]} timestamps This is an array of {@link Timestamp}s
|
|
1064
|
+
* @param {boolean=} useTime Default false; if true, uses time in the comparison as well
|
|
1065
|
+
* @returns The {@link Timestamp} with the lowest date (and possibly time) value
|
|
1066
|
+
*/
|
|
1067
|
+
export function minTimestamp(timestamps, useTime = false) {
|
|
1068
|
+
const func = useTime === true ? getDayTimeIdentifier : getDayIdentifier;
|
|
1069
|
+
return timestamps.reduce((prev, cur) => {
|
|
1070
|
+
return Math.min(func(prev), func(cur)) === func(prev) ? prev : cur;
|
|
1071
|
+
});
|
|
1072
|
+
}
|
|
1073
|
+
/**
|
|
1074
|
+
* Determines if the passed {@link Timestamp} is between (or equal) to two {@link Timestamp}s (range)
|
|
1075
|
+
* @param {Timestamp} timestamp The {@link Timestamp} for testing
|
|
1076
|
+
* @param {Timestamp} startTimestamp The starting {@link Timestamp}
|
|
1077
|
+
* @param {Timestamp} endTimestamp The ending {@link Timestamp}
|
|
1078
|
+
* @param {boolean=} useTime If true, use time from the {@link Timestamp}s
|
|
1079
|
+
* @returns {boolean} True if {@link Timestamp} is between (or equal) to two {@link Timestamp}s (range)
|
|
1080
|
+
*/
|
|
1081
|
+
export function isBetweenDates(timestamp, startTimestamp, endTimestamp, useTime = false) {
|
|
1082
|
+
const cd = getDayIdentifier(timestamp) + (useTime === true ? getTimeIdentifier(timestamp) : 0);
|
|
1083
|
+
const sd = getDayIdentifier(startTimestamp) + (useTime === true ? getTimeIdentifier(startTimestamp) : 0);
|
|
1084
|
+
const ed = getDayIdentifier(endTimestamp) + (useTime === true ? getTimeIdentifier(endTimestamp) : 0);
|
|
1085
|
+
return cd >= sd && cd <= ed;
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Determine if two ranges of {@link Timestamp}s overlap each other
|
|
1089
|
+
* @param {Timestamp} startTimestamp The starting {@link Timestamp} of first range
|
|
1090
|
+
* @param {Timestamp} endTimestamp The endinging {@link Timestamp} of first range
|
|
1091
|
+
* @param {Timestamp} firstTimestamp The starting {@link Timestamp} of second range
|
|
1092
|
+
* @param {Timestamp} lastTimestamp The ending {@link Timestamp} of second range
|
|
1093
|
+
* @returns {boolean} True if the two ranges overlap each other
|
|
1094
|
+
*/
|
|
1095
|
+
export function isOverlappingDates(startTimestamp, endTimestamp, firstTimestamp, lastTimestamp) {
|
|
1096
|
+
const start = getDayIdentifier(startTimestamp);
|
|
1097
|
+
const end = getDayIdentifier(endTimestamp);
|
|
1098
|
+
const first = getDayIdentifier(firstTimestamp);
|
|
1099
|
+
const last = getDayIdentifier(lastTimestamp);
|
|
1100
|
+
return ((start >= first && start <= last) || // overlap left
|
|
1101
|
+
(end >= first && end <= last) || // overlap right
|
|
1102
|
+
(first >= start && end >= last) // surrounding
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
/**
|
|
1106
|
+
* Adds or subtracts date/time units from a timestamp.
|
|
1107
|
+
*
|
|
1108
|
+
* This function returns a new frozen {@link Timestamp}; it does not mutate the
|
|
1109
|
+
* timestamp passed in.
|
|
1110
|
+
*
|
|
1111
|
+
* @param {Timestamp} timestamp The {@link Timestamp} object
|
|
1112
|
+
* @param {Object} options configuration data
|
|
1113
|
+
* @param {number=} options.year If positive, adds years. If negative, removes years.
|
|
1114
|
+
* @param {number=} options.month If positive, adds months. If negative, removes month.
|
|
1115
|
+
* @param {number=} options.day If positive, adds days. If negative, removes days.
|
|
1116
|
+
* @param {number=} options.hour If positive, adds hours. If negative, removes hours.
|
|
1117
|
+
* @param {number=} options.minute If positive, adds minutes. If negative, removes minutes.
|
|
1118
|
+
* @param {number=} options.second If positive, adds seconds. If negative, removes seconds.
|
|
1119
|
+
* @param {number=} options.millisecond If positive, adds milliseconds. If negative, removes milliseconds.
|
|
1120
|
+
* @returns {Timestamp} A new normalized {@link Timestamp}
|
|
1121
|
+
*/
|
|
1122
|
+
export function addToDate(timestamp, options) {
|
|
1123
|
+
const ts = cloneTimestamp(timestamp);
|
|
1124
|
+
if (options.year)
|
|
1125
|
+
ts.year += options.year;
|
|
1126
|
+
if (options.month)
|
|
1127
|
+
ts.month += options.month;
|
|
1128
|
+
if (options.day)
|
|
1129
|
+
ts.day += options.day;
|
|
1130
|
+
if (options.hour)
|
|
1131
|
+
ts.hour += options.hour;
|
|
1132
|
+
if (options.minute)
|
|
1133
|
+
ts.minute += options.minute;
|
|
1134
|
+
if (options.second)
|
|
1135
|
+
ts.second = (ts.second ?? 0) + options.second;
|
|
1136
|
+
if (options.millisecond)
|
|
1137
|
+
ts.millisecond = (ts.millisecond ?? 0) + options.millisecond;
|
|
1138
|
+
return updateFormatted(normalizeTimestamp(ts));
|
|
1139
|
+
}
|
|
1140
|
+
/**
|
|
1141
|
+
* Normalizes a timestamp object by creating a JavaScript Date object and extracting standardized values.
|
|
1142
|
+
* This function ensures that the timestamp values are consistent and correctly represent a valid date and time.
|
|
1143
|
+
*
|
|
1144
|
+
* @param {Object} ts - The timestamp object to normalize.
|
|
1145
|
+
* @param {number} ts.year - The year of the timestamp.
|
|
1146
|
+
* @param {number} ts.month - The month of the timestamp (1-12).
|
|
1147
|
+
* @param {number} ts.day - The day of the month.
|
|
1148
|
+
* @param {number} ts.hour - The hour of the day (0-23).
|
|
1149
|
+
* @param {number} ts.minute - The minute of the hour (0-59).
|
|
1150
|
+
* @returns {Object} A new object with normalized timestamp values.
|
|
1151
|
+
* The returned object includes all properties from the input object,
|
|
1152
|
+
* with year, month, day, hour, and minute properties updated to normalized values.
|
|
1153
|
+
*/
|
|
1154
|
+
function normalizeTimestamp(ts) {
|
|
1155
|
+
const date = new Date(ts.year, ts.month - 1, ts.day, ts.hour, ts.minute, ts.second ?? 0, ts.millisecond ?? 0);
|
|
1156
|
+
const timestamp = {
|
|
1157
|
+
...ts,
|
|
1158
|
+
year: date.getFullYear(),
|
|
1159
|
+
month: date.getMonth() + 1,
|
|
1160
|
+
day: date.getDate(),
|
|
1161
|
+
hour: date.getHours(),
|
|
1162
|
+
minute: date.getMinutes(),
|
|
1163
|
+
};
|
|
1164
|
+
if (ts.second !== undefined || date.getSeconds() !== 0) {
|
|
1165
|
+
timestamp.second = date.getSeconds();
|
|
1166
|
+
}
|
|
1167
|
+
if (ts.millisecond !== undefined || date.getMilliseconds() !== 0) {
|
|
1168
|
+
timestamp.millisecond = date.getMilliseconds();
|
|
1169
|
+
}
|
|
1170
|
+
return freezeTimestamp(timestamp);
|
|
1171
|
+
}
|
|
1172
|
+
/**
|
|
1173
|
+
* Returns number of days between two {@link Timestamp}s
|
|
1174
|
+
* @param {Timestamp} ts1 The first {@link Timestamp}
|
|
1175
|
+
* @param {Timestamp} ts2 The second {@link Timestamp}
|
|
1176
|
+
* @returns Number of days
|
|
1177
|
+
*/
|
|
1178
|
+
export function daysBetween(ts1, ts2) {
|
|
1179
|
+
const diff = diffTimestamp(ts1, ts2, true);
|
|
1180
|
+
return Math.floor(diff / TIME_CONSTANTS.MILLISECONDS_IN.DAY);
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Returns number of weeks between two {@link Timestamp}s
|
|
1184
|
+
* @param {Timestamp} ts1 The first {@link Timestamp}
|
|
1185
|
+
* @param {Timestamp} ts2 The second {@link Timestamp}
|
|
1186
|
+
*/
|
|
1187
|
+
export function weeksBetween(ts1, ts2) {
|
|
1188
|
+
let t1 = copyTimestamp(ts1);
|
|
1189
|
+
let t2 = copyTimestamp(ts2);
|
|
1190
|
+
t1 = findWeekday(t1, 0);
|
|
1191
|
+
t2 = findWeekday(t2, 6);
|
|
1192
|
+
return Math.ceil(daysBetween(t1, t2) / TIME_CONSTANTS.DAYS_IN.WEEK);
|
|
1193
|
+
}
|
|
1194
|
+
// Known dates
|
|
1195
|
+
const weekdayDateMap = {
|
|
1196
|
+
Sun: new Date("2020-01-05T00:00:00.000Z"),
|
|
1197
|
+
Mon: new Date("2020-01-06T00:00:00.000Z"),
|
|
1198
|
+
Tue: new Date("2020-01-07T00:00:00.000Z"),
|
|
1199
|
+
Wed: new Date("2020-01-08T00:00:00.000Z"),
|
|
1200
|
+
Thu: new Date("2020-01-09T00:00:00.000Z"),
|
|
1201
|
+
Fri: new Date("2020-01-10T00:00:00.000Z"),
|
|
1202
|
+
Sat: new Date("2020-01-11T00:00:00.000Z"),
|
|
1203
|
+
};
|
|
1204
|
+
function resolveIntlNameFormat(options, type) {
|
|
1205
|
+
if (type === "long" || type === "short" || type === "narrow") {
|
|
1206
|
+
return options[type];
|
|
1207
|
+
}
|
|
1208
|
+
return options.long;
|
|
1209
|
+
}
|
|
1210
|
+
/**
|
|
1211
|
+
* Returns a function that uses Intl.DateTimeFormat to format weekdays.
|
|
1212
|
+
*
|
|
1213
|
+
* @function getWeekdayFormatter
|
|
1214
|
+
* @returns {function} A function that formats weekdays.
|
|
1215
|
+
*
|
|
1216
|
+
* @example
|
|
1217
|
+
* const formatWeekday = getWeekdayFormatter();
|
|
1218
|
+
* console.log(formatWeekday('Mon', 'long', 'en-US')); // "Monday"
|
|
1219
|
+
* console.log(formatWeekday('Mon', 'short', 'fr-FR')); // "lun."
|
|
1220
|
+
*
|
|
1221
|
+
* @param {string} weekday - The abbreviation of the weekday (e.g., 'Mon', 'Tue', 'Wed', etc.).
|
|
1222
|
+
* @param {string} [type='long'] - The type of formatting to use ('narrow', 'short', or 'long').
|
|
1223
|
+
* @param {string} [locale=''] - The locale to use for formatting.
|
|
1224
|
+
*
|
|
1225
|
+
* @returns {string} The formatted weekday.
|
|
1226
|
+
*/
|
|
1227
|
+
export function getWeekdayFormatter() {
|
|
1228
|
+
const emptyFormatter = () => "";
|
|
1229
|
+
const options = {
|
|
1230
|
+
long: { timeZone: "UTC", weekday: "long" },
|
|
1231
|
+
short: { timeZone: "UTC", weekday: "short" },
|
|
1232
|
+
narrow: { timeZone: "UTC", weekday: "narrow" },
|
|
1233
|
+
};
|
|
1234
|
+
/* istanbul ignore next */
|
|
1235
|
+
if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
|
|
1236
|
+
return emptyFormatter;
|
|
1237
|
+
}
|
|
1238
|
+
/**
|
|
1239
|
+
* Formats a given weekday into a localized string based on the specified type and locale.
|
|
1240
|
+
*
|
|
1241
|
+
* @param {number} weekday - The day of the week (0 for Sunday, 1 for Monday, etc.).
|
|
1242
|
+
* @param {string} type - The format type (e.g., 'narrow', 'short', 'long') to use for formatting.
|
|
1243
|
+
* @param {string} [locale] - The locale string (e.g., 'en-US') to use for formatting. Defaults to the user's locale if not provided.
|
|
1244
|
+
* @returns {string} The formatted weekday string.
|
|
1245
|
+
*/
|
|
1246
|
+
function weekdayFormatter(weekday, type, locale) {
|
|
1247
|
+
try {
|
|
1248
|
+
const intlFormatter = new Intl.DateTimeFormat(locale || undefined, resolveIntlNameFormat(options, type));
|
|
1249
|
+
return intlFormatter.format(weekdayDateMap[weekday]);
|
|
1250
|
+
}
|
|
1251
|
+
catch (e) /* istanbul ignore next */ {
|
|
1252
|
+
if (e instanceof Error) {
|
|
1253
|
+
console.error(`Intl.DateTimeFormat: ${e.message} -> day of week: ${weekday}`);
|
|
1254
|
+
}
|
|
1255
|
+
return "";
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
1258
|
+
return weekdayFormatter;
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Retrieves an array of localized weekday names.
|
|
1262
|
+
*
|
|
1263
|
+
* @param {string} type - The format type for the weekday names. Can be 'narrow', 'short', or 'long'.
|
|
1264
|
+
* @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
|
|
1265
|
+
* @returns {string[]} An array of localized weekday names in the specified format.
|
|
1266
|
+
*/
|
|
1267
|
+
export function getWeekdayNames(type, locale) {
|
|
1268
|
+
const shortWeekdays = Object.keys(weekdayDateMap);
|
|
1269
|
+
const weekdayFormatter = getWeekdayFormatter();
|
|
1270
|
+
return shortWeekdays.map((weekday) => String(weekdayFormatter(weekday, type, locale)));
|
|
1271
|
+
}
|
|
1272
|
+
/**
|
|
1273
|
+
* Creates and returns a function for formatting month names based on locale and format type.
|
|
1274
|
+
*
|
|
1275
|
+
* @returns {Function} A function that formats month names.
|
|
1276
|
+
* The returned function accepts the following parameters:
|
|
1277
|
+
* @param {number} month - The month to format (0-11, where 0 is January).
|
|
1278
|
+
* @param {string} [type='long'] - The format type: 'narrow', 'short', or 'long'.
|
|
1279
|
+
* @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
|
|
1280
|
+
* @returns {string} The formatted month name.
|
|
1281
|
+
*
|
|
1282
|
+
* @throws {Error} If Intl or Intl.DateTimeFormat is not supported in the environment.
|
|
1283
|
+
*/
|
|
1284
|
+
export function getMonthFormatter() {
|
|
1285
|
+
const emptyFormatter = () => "";
|
|
1286
|
+
const options = {
|
|
1287
|
+
long: { timeZone: "UTC", month: "long" },
|
|
1288
|
+
short: { timeZone: "UTC", month: "short" },
|
|
1289
|
+
narrow: { timeZone: "UTC", month: "narrow" },
|
|
1290
|
+
};
|
|
1291
|
+
/* istanbul ignore next */
|
|
1292
|
+
if (typeof Intl === "undefined" || typeof Intl.DateTimeFormat === "undefined") {
|
|
1293
|
+
return emptyFormatter;
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Formats a given month into a string based on the specified type and locale.
|
|
1297
|
+
*
|
|
1298
|
+
* @param {number} month - The month to format (0 for January, 11 for December).
|
|
1299
|
+
* @param {string} type - The format type (e.g., 'narrow', 'long', 'short', etc.).
|
|
1300
|
+
* @param {string} [locale] - The locale to use for formatting (defaults to the system locale if not provided).
|
|
1301
|
+
* @returns {string} The formatted month string.
|
|
1302
|
+
*/
|
|
1303
|
+
function monthFormatter(month, type = "long", locale) {
|
|
1304
|
+
try {
|
|
1305
|
+
const intlFormatter = new Intl.DateTimeFormat(locale || undefined, resolveIntlNameFormat(options, type));
|
|
1306
|
+
const date = new Date();
|
|
1307
|
+
date.setDate(1);
|
|
1308
|
+
date.setMonth(month);
|
|
1309
|
+
return intlFormatter.format(date);
|
|
1310
|
+
}
|
|
1311
|
+
catch (e) /* istanbul ignore next */ {
|
|
1312
|
+
if (e instanceof Error) {
|
|
1313
|
+
console.error(`Intl.DateTimeFormat: ${e.message} -> month: ${month}`);
|
|
1314
|
+
}
|
|
1315
|
+
return "";
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
return monthFormatter;
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Retrieves an array of localized month names.
|
|
1322
|
+
*
|
|
1323
|
+
* @param {string} type - The format type for the month names. Can be 'narrow', 'short', or 'long'.
|
|
1324
|
+
* @param {string} [locale] - The locale to use for formatting. If not provided, the default locale is used.
|
|
1325
|
+
* @returns {string[]} An array of localized month names in the specified format.
|
|
1326
|
+
*/
|
|
1327
|
+
export function getMonthNames(type, locale) {
|
|
1328
|
+
const monthFormatter = getMonthFormatter();
|
|
1329
|
+
return [...Array(12).keys()].map((month) => monthFormatter(month, type, locale));
|
|
1330
|
+
}
|
|
1331
|
+
// the exports...
|
|
1332
|
+
export default {
|
|
1333
|
+
PARSE_DATETIME,
|
|
1334
|
+
PARSE_DATE,
|
|
1335
|
+
PARSE_TIME,
|
|
1336
|
+
DAYS_IN_MONTH,
|
|
1337
|
+
DAYS_IN_MONTH_LEAP,
|
|
1338
|
+
DAYS_IN_MONTH_MIN,
|
|
1339
|
+
DAYS_IN_MONTH_MAX,
|
|
1340
|
+
MONTH_MAX,
|
|
1341
|
+
MONTH_MIN,
|
|
1342
|
+
DAY_MIN,
|
|
1343
|
+
TIME_CONSTANTS,
|
|
1344
|
+
DAYS_IN_WEEK,
|
|
1345
|
+
MINUTES_IN_HOUR,
|
|
1346
|
+
HOURS_IN_DAY,
|
|
1347
|
+
FIRST_HOUR,
|
|
1348
|
+
MILLISECONDS_IN_MINUTE,
|
|
1349
|
+
MILLISECONDS_IN_HOUR,
|
|
1350
|
+
MILLISECONDS_IN_DAY,
|
|
1351
|
+
MILLISECONDS_IN_WEEK,
|
|
1352
|
+
Timestamp,
|
|
1353
|
+
TimeObject,
|
|
1354
|
+
today,
|
|
1355
|
+
getStartOfWeek,
|
|
1356
|
+
getEndOfWeek,
|
|
1357
|
+
getStartOfMonth,
|
|
1358
|
+
getEndOfMonth,
|
|
1359
|
+
parseTime,
|
|
1360
|
+
validateTimestamp,
|
|
1361
|
+
parsed,
|
|
1362
|
+
parseTimestamp,
|
|
1363
|
+
parseDate,
|
|
1364
|
+
getDayIdentifier,
|
|
1365
|
+
getTimeIdentifier,
|
|
1366
|
+
getDayTimeIdentifier,
|
|
1367
|
+
diffTimestamp,
|
|
1368
|
+
updateRelative,
|
|
1369
|
+
updateMinutes,
|
|
1370
|
+
updateWeekday,
|
|
1371
|
+
updateDayOfYear,
|
|
1372
|
+
updateWorkWeek,
|
|
1373
|
+
updateDisabled,
|
|
1374
|
+
updateFormatted,
|
|
1375
|
+
getDayOfYear,
|
|
1376
|
+
getWorkWeek,
|
|
1377
|
+
getWeekday,
|
|
1378
|
+
isLeapYear,
|
|
1379
|
+
daysInMonth,
|
|
1380
|
+
copyTimestamp,
|
|
1381
|
+
padNumber,
|
|
1382
|
+
getDate,
|
|
1383
|
+
getTime,
|
|
1384
|
+
getDateTime,
|
|
1385
|
+
nextDay,
|
|
1386
|
+
prevDay,
|
|
1387
|
+
relativeDays,
|
|
1388
|
+
findWeekday,
|
|
1389
|
+
createDayList,
|
|
1390
|
+
createIntervalList,
|
|
1391
|
+
createNativeLocaleFormatter,
|
|
1392
|
+
makeDate,
|
|
1393
|
+
makeDateTime,
|
|
1394
|
+
getDateObject,
|
|
1395
|
+
validateNumber,
|
|
1396
|
+
isBetweenDates,
|
|
1397
|
+
isOverlappingDates,
|
|
1398
|
+
daysBetween,
|
|
1399
|
+
weeksBetween,
|
|
1400
|
+
addToDate,
|
|
1401
|
+
compareTimestamps,
|
|
1402
|
+
compareDate,
|
|
1403
|
+
compareTime,
|
|
1404
|
+
compareDateTime,
|
|
1405
|
+
getWeekdayFormatter,
|
|
1406
|
+
getWeekdayNames,
|
|
1407
|
+
getMonthFormatter,
|
|
1408
|
+
getMonthNames,
|
|
1409
|
+
};
|
|
1410
|
+
//# sourceMappingURL=index.js.map
|