jupyter-ijavascript-utils 1.50.0 → 1.51.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/Dockerfile +1 -1
- package/package.json +1 -1
- package/src/array.js +123 -4
- package/src/color.js +99 -0
- package/src/date.js +503 -0
- package/src/format.js +16 -0
- package/src/hashMap.js +24 -0
- package/src/index.js +4 -0
- package/src/object.js +2 -0
package/Dockerfile
CHANGED
package/package.json
CHANGED
package/src/array.js
CHANGED
|
@@ -13,6 +13,7 @@ require('./_types/global');
|
|
|
13
13
|
* * {@link module:array.arrange|array.arrange(size, start, step)} - generate array of a size, and INCREASING default value
|
|
14
14
|
* * {@link module:array.arrangeMulti|array.arrangeMulti(n, m, ...)} - generate a multi-dimensional array
|
|
15
15
|
* * {@link module:array.clone|array.clone(array)} - deep clones arrays
|
|
16
|
+
* * {@link module:array.zip|array.zip(arrayleft, arrayRight)} - zips two arrays to join values at the same index together.
|
|
16
17
|
* * Sorting
|
|
17
18
|
* * {@link module:array.createSort|array.createSort(sortIndex, sortIndex, ...)} - generates a sorting function
|
|
18
19
|
* * {@link module:array.SORT_ASCENDING|array.SORT_ASCENDING} - common ascending sorting function for array.sort()
|
|
@@ -21,6 +22,7 @@ require('./_types/global');
|
|
|
21
22
|
* * Rearrange Array
|
|
22
23
|
* * {@link module:array.reshape|array.reshape} - reshapes an array to a size of rows and columns
|
|
23
24
|
* * {@link module:array.transpose|array.transpose} - transposes (flips - the array along the diagonal)
|
|
25
|
+
* * {@link module:array.resize|array.resize} - repeats or truncates to change the size of an array.
|
|
24
26
|
* * Picking Values
|
|
25
27
|
* * {@link module:array.peekFirst|array.peekFirst} - peeks at the first value in the list
|
|
26
28
|
* * {@link module:array.peekLast|array.peekLast} - peeks at the last value in the list
|
|
@@ -36,6 +38,8 @@ require('./_types/global');
|
|
|
36
38
|
* {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/substring|Substring}
|
|
37
39
|
* from a multi-line string or array of strings
|
|
38
40
|
* * {@link module:array.multiStepReduce|array.multiStepReduce} - Performs reduce, and returns the value of reduce at each step
|
|
41
|
+
* * {@link module:array.extractFromHardSpacedTable|array.extractFromHardSpacedTable} - Extract values where each line has no delimiter,
|
|
42
|
+
* but instead a column index (ex: column 13)
|
|
39
43
|
* * Applying a value
|
|
40
44
|
* * {@link module:array.applyArrayValue|array.applyArrayValue} - applies a value deeply into an array safely
|
|
41
45
|
* * {@link module:array.applyArrayValues|array.applyArrayValues} - applies a value / multiple values deeply into an array safely
|
|
@@ -607,8 +611,8 @@ module.exports.transpose = function transpose(matrix) {
|
|
|
607
611
|
};
|
|
608
612
|
|
|
609
613
|
/**
|
|
610
|
-
*
|
|
611
|
-
* @param {any[]} sourceArray - an array to
|
|
614
|
+
* Re-shapes an NxM dimensional array by number of columns
|
|
615
|
+
* @param {any[]} sourceArray - an array to reshape
|
|
612
616
|
* @param {Number} numColumns - number of columns
|
|
613
617
|
* @returns {any[][]} - 2 dimensinal array
|
|
614
618
|
* @example
|
|
@@ -618,14 +622,14 @@ module.exports.transpose = function transpose(matrix) {
|
|
|
618
622
|
* 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
|
|
619
623
|
* ]
|
|
620
624
|
*
|
|
621
|
-
* //--
|
|
625
|
+
* //-- reshape the 1d array based on 3 columns
|
|
622
626
|
* newArray = utils.array.reshape(baseArray, 3)
|
|
623
627
|
* [ [ 0, 1, 2 ],
|
|
624
628
|
* [ 3, 4, 5 ],
|
|
625
629
|
* [ 6, 7, 8 ],
|
|
626
630
|
* [ 9, 10, 11 ] ];
|
|
627
631
|
*
|
|
628
|
-
* //-- now
|
|
632
|
+
* //-- now reshape the 4x3 array to 3x4
|
|
629
633
|
* utils.array.reshape(newArray, 4);
|
|
630
634
|
* [ [ 0, 1, 2, 3 ],
|
|
631
635
|
* [ 4, 5, 6, 7 ],
|
|
@@ -1447,3 +1451,118 @@ module.exports.asyncWaitAndChain = (seconds, fn, rows) => {
|
|
|
1447
1451
|
return callNext();
|
|
1448
1452
|
});
|
|
1449
1453
|
};
|
|
1454
|
+
|
|
1455
|
+
/**
|
|
1456
|
+
* Resizes an array - if shorter (truncates), if longer cycles values.
|
|
1457
|
+
*
|
|
1458
|
+
* ```
|
|
1459
|
+
* categoryValues = ['rock', 'paper', 'scissors'];
|
|
1460
|
+
*
|
|
1461
|
+
* utils.array.resize(categoryValues, 2); // ['rock', 'paper']
|
|
1462
|
+
* utils.array.resize(categoryValues, 7); // ['rock', 'paper', 'scissors',
|
|
1463
|
+
* 'rock', 'paper', 'scissors', 'rock];
|
|
1464
|
+
* ```
|
|
1465
|
+
*
|
|
1466
|
+
* @param {Array} sourceList - array of values
|
|
1467
|
+
* @param {Number} length - new number of items in the list
|
|
1468
|
+
*/
|
|
1469
|
+
module.exports.resize = function resize(sourceList, length) {
|
|
1470
|
+
if (!sourceList || !Array.isArray(sourceList)) return [];
|
|
1471
|
+
if (length < 1 || sourceList.length < 1) return [];
|
|
1472
|
+
return new Array(length)
|
|
1473
|
+
.fill(0)
|
|
1474
|
+
.map((_, index) => sourceList[index % sourceList.length]);
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
/**
|
|
1478
|
+
* Combines arrays together by joining the values at the same index.
|
|
1479
|
+
*
|
|
1480
|
+
* Similar to Panda's zip
|
|
1481
|
+
*
|
|
1482
|
+
* This can be very helpful for joining multiple value lists.
|
|
1483
|
+
*
|
|
1484
|
+
* ```
|
|
1485
|
+
* first = ['john', 'paul', 'george', 'ringo'];
|
|
1486
|
+
* last = ['lennon', 'mccartney', 'harrison', 'starr'];
|
|
1487
|
+
* phrase = ['imagine', 'yesterday', 'taxman', 'walrus'];
|
|
1488
|
+
*
|
|
1489
|
+
* names = utils.array.zip(first, last);
|
|
1490
|
+
* // [['john', 'lennon'], ['paul', 'mccartney'],
|
|
1491
|
+
* // ['george', 'harrison'], ['ringo', 'starr']];
|
|
1492
|
+
* ```
|
|
1493
|
+
*
|
|
1494
|
+
* You can also zip together existing arrays
|
|
1495
|
+
*
|
|
1496
|
+
* ```
|
|
1497
|
+
* utils.array.zip(names, phrase);
|
|
1498
|
+
* // [['john', 'lennon', 'imagine'],
|
|
1499
|
+
* // ['paul', 'mccartney', 'yesterday'],
|
|
1500
|
+
* // ['george', 'harrison', 'taxman'],
|
|
1501
|
+
* // ['ringo', 'starr', 'walrus']]
|
|
1502
|
+
* ```
|
|
1503
|
+
*
|
|
1504
|
+
* or you can zip them together all at once
|
|
1505
|
+
*
|
|
1506
|
+
* ```
|
|
1507
|
+
* utils.array.zip(first, last, phrase);
|
|
1508
|
+
* // [['john', 'lennon', 'imagine'],
|
|
1509
|
+
* // ['paul', 'mccartney', 'yesterday'],
|
|
1510
|
+
* // ['george', 'harrison', 'taxman'],
|
|
1511
|
+
* // ['ringo', 'starr', 'walrus']]
|
|
1512
|
+
* ```
|
|
1513
|
+
*
|
|
1514
|
+
* @param {Array} arrayLeft - one array to combine with the array on the right
|
|
1515
|
+
* @param {Array} arrayRight - another array to combine at the same indices on the left
|
|
1516
|
+
* @param {...any} rest - additional arrays to combine
|
|
1517
|
+
* @returns {Array<Array>}
|
|
1518
|
+
*/
|
|
1519
|
+
module.exports.zip = function zip(arrayLeft, arrayRight, ...rest) {
|
|
1520
|
+
if (!arrayLeft || !arrayLeft[Symbol.iterator]) {
|
|
1521
|
+
throw new Error('zip: left must be iterable');
|
|
1522
|
+
}
|
|
1523
|
+
if (!arrayRight || !arrayRight[Symbol.iterator]) {
|
|
1524
|
+
throw new Error('zip: right must be iterable');
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
const cleanLeft = Array.isArray(arrayLeft) ? arrayLeft : [...arrayLeft];
|
|
1528
|
+
const cleanRight = Array.isArray(arrayRight) ? arrayRight : [...arrayRight];
|
|
1529
|
+
|
|
1530
|
+
let result;
|
|
1531
|
+
|
|
1532
|
+
if (cleanLeft.length === 0 && cleanRight.length === 0) {
|
|
1533
|
+
result = [[]];
|
|
1534
|
+
} else if (cleanLeft.length === 0) {
|
|
1535
|
+
result = cleanRight.map((val) => Array.isArray(val) ? val : [val]);
|
|
1536
|
+
} else if (cleanRight.length === 0) {
|
|
1537
|
+
result = cleanLeft.map((val) => Array.isArray(val) ? val : [val]);
|
|
1538
|
+
} else {
|
|
1539
|
+
const cleanLeftLen = cleanLeft.length;
|
|
1540
|
+
const cleanRightLen = cleanRight.length;
|
|
1541
|
+
const zipLen = Math.min(cleanLeftLen, cleanRightLen);
|
|
1542
|
+
|
|
1543
|
+
result = new Array(zipLen).fill(0);
|
|
1544
|
+
|
|
1545
|
+
for (let i = 0; i < zipLen; i += 1) {
|
|
1546
|
+
const leftVal = cleanLeft[i];
|
|
1547
|
+
const rightVal = cleanRight[i];
|
|
1548
|
+
const leftValArray = Array.isArray(leftVal);
|
|
1549
|
+
const rightValArray = Array.isArray(rightVal);
|
|
1550
|
+
if (leftValArray && rightValArray) {
|
|
1551
|
+
result[i] = [...leftVal, ...rightVal];
|
|
1552
|
+
} else if (leftValArray) {
|
|
1553
|
+
result[i] = [...leftVal, rightVal];
|
|
1554
|
+
} else if (rightValArray) {
|
|
1555
|
+
result[i] = [leftVal, ...rightVal];
|
|
1556
|
+
} else {
|
|
1557
|
+
result[i] = [leftVal, rightVal];
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
if (rest && rest.length > 0) {
|
|
1563
|
+
const [newRight, ...newRest] = rest;
|
|
1564
|
+
result = ArrayUtils.zip(result, newRight, ...newRest);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
return result;
|
|
1568
|
+
};
|
package/src/color.js
CHANGED
|
@@ -21,6 +21,8 @@
|
|
|
21
21
|
*
|
|
22
22
|
* See other common libraries for working with color on NPM:
|
|
23
23
|
* like [d3/color](https://d3js.org/d3-color)
|
|
24
|
+
* or [d3-scale-chromatic scales](https://d3js.org/d3-scale-chromatic)
|
|
25
|
+
* or [d3-color-interpolation](https://d3js.org/d3-interpolate/color)
|
|
24
26
|
*
|
|
25
27
|
* * Parsing color formats
|
|
26
28
|
* * {@link module:color.parse|color.parse(string|array|object, optionalAlpha 0-1)} - intelligently parse any of the types to an array format
|
|
@@ -41,6 +43,8 @@
|
|
|
41
43
|
* with properties: {r:Number[0-255], g: Number[0-255], b: Number[0-255], a: Number[0-1]}
|
|
42
44
|
* * interpolate
|
|
43
45
|
* * {@link module:color.interpolate|color.interpolate(fromColor, toColor, percent, formatType)} - gradually converts one color to another
|
|
46
|
+
* * {@link module:color.interpolator|color.interpolator} - create a function you can then call with a percentage over and over again.
|
|
47
|
+
* * {@link module:color.generateSequence|color.generateSequence} - generate a sequence of colors from one to another, in X number of steps
|
|
44
48
|
* * {@link module:color.interpolationStrategy|color.interpolationStrategy} - the function to use for interpolation,
|
|
45
49
|
* a function of signature (fromColor:Number[0-255], toColor:Number[0-255], percentage:Number[0-1]):Number[0-255]
|
|
46
50
|
* * {@link module:color.INTERPOLATION_STRATEGIES|color.INTERPOLATION_STRATEGIES} - a list of strategies for interpolation you can choose from
|
|
@@ -568,6 +572,7 @@ module.exports.convert = function convert(target, formatType = ColorUtils.defaul
|
|
|
568
572
|
* @see {@link module:color.interpolationStrategy|color.interpolationStrategy} - the default interpolation
|
|
569
573
|
* used to calculate how the percentages come up with the color
|
|
570
574
|
* @see {@link module:color.defaultFormat|color.defaultFormat} - the default format to use if not specified
|
|
575
|
+
* @see {@link module:format.mapArrayDomain|format.mapArrayDomain}
|
|
571
576
|
*/
|
|
572
577
|
module.exports.interpolate = function interpolate(
|
|
573
578
|
fromColor,
|
|
@@ -588,3 +593,97 @@ module.exports.interpolate = function interpolate(
|
|
|
588
593
|
newColor[2] = Math.round(newColor[2]);
|
|
589
594
|
return ColorUtils.convert(newColor, formatType);
|
|
590
595
|
};
|
|
596
|
+
|
|
597
|
+
/**
|
|
598
|
+
* Curried function for color interpolation, so only the percent between [0-1] inclusive is needed.
|
|
599
|
+
*
|
|
600
|
+
* Meaning that you can do something like this:
|
|
601
|
+
*
|
|
602
|
+
* ```
|
|
603
|
+
* black = `#000000`;
|
|
604
|
+
* white = `#FFFFFF`;
|
|
605
|
+
*
|
|
606
|
+
* colorFn = utils.color.interpolator(black, white);
|
|
607
|
+
*
|
|
608
|
+
* colorFn(0); // '#000000';
|
|
609
|
+
* colorFn(0.5); // '#808080';
|
|
610
|
+
* colorFn(1); // '#FFFFFF;
|
|
611
|
+
*
|
|
612
|
+
* ```
|
|
613
|
+
*
|
|
614
|
+
* Instead of something like this with the interpolate function
|
|
615
|
+
*
|
|
616
|
+
* ```
|
|
617
|
+
* utils.color.interpolate(black, white, 0); // `#000000`
|
|
618
|
+
* utils.color.interpolate(black, white, 0.5); // `#808080`
|
|
619
|
+
* utils.color.interpolate(black, white, 1); // `#FFFFFF`
|
|
620
|
+
* ```
|
|
621
|
+
*
|
|
622
|
+
* @param {string|array|object} fromColor -the color to interpolate from
|
|
623
|
+
* @param {string|array|object} toColor - the color to interpolate to
|
|
624
|
+
* @param {Number} percent - value from 0-1 on where we should be on the sliding scale
|
|
625
|
+
* @param {Function} [interpolationFn = ColorUtils.interpolationStrategy] - function of
|
|
626
|
+
* signature (fromVal:Number[0-255], toVal:Number[0-255], pct:Number[0-1]):Number[0-255]
|
|
627
|
+
* @param {String} [formatType = ColorUtils.defaultFormat] - the format to convert the result to
|
|
628
|
+
* @returns {Function} - of signature: (Number) => {string|array|object}
|
|
629
|
+
* @see {@link module:color.interpolate|color.interpolate} - as this is a curried version of that function.
|
|
630
|
+
* @see {@link module:format.mapArrayDomain|format.mapArrayDomain}
|
|
631
|
+
*/
|
|
632
|
+
module.exports.interpolator = function interpolator(
|
|
633
|
+
fromColor,
|
|
634
|
+
toColor,
|
|
635
|
+
interpolationFn = ColorUtils.interpolationStrategy,
|
|
636
|
+
formatType = ColorUtils.defaultFormat
|
|
637
|
+
) {
|
|
638
|
+
return function interpolatorImpl(pct) {
|
|
639
|
+
return ColorUtils.interpolate(fromColor, toColor, pct, interpolationFn, formatType);
|
|
640
|
+
};
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
/**
|
|
644
|
+
* Generates a sequential array of colors interpolating fromColor to toColor,
|
|
645
|
+
*
|
|
646
|
+
* ```
|
|
647
|
+
* black = `#000000`;
|
|
648
|
+
* white = `#FFFFFF`;
|
|
649
|
+
*
|
|
650
|
+
* categoricalColors = utils.color.generateSequence(black, white, 5);
|
|
651
|
+
* // [' #000000' ,' #404040' ,' #808080' ,' #bfbfbf' ,' #ffffff' ]
|
|
652
|
+
* ```
|
|
653
|
+
*
|
|
654
|
+
* @param {string|array|object} fromColor -the color to interpolate from
|
|
655
|
+
* @param {string|array|object} toColor - the color to interpolate to
|
|
656
|
+
* @param {Number} lengthOfSequence - how many steps in the sequence to generate
|
|
657
|
+
* @param {Function} [interpolationFn = ColorUtils.interpolationStrategy] - function of
|
|
658
|
+
* signature (fromVal:Number[0-255], toVal:Number[0-255], pct:Number[0-1]):Number[0-255]
|
|
659
|
+
* @param {String} [formatType = ColorUtils.defaultFormat] - the format to convert the result to
|
|
660
|
+
* @returns {Function} - of signature: (Number) => {string|array|object}
|
|
661
|
+
* @see {@link module:color.interpolate|color.interpolate} - as this is a curried version of that function.
|
|
662
|
+
*/
|
|
663
|
+
module.exports.generateSequence = function generateSequence(
|
|
664
|
+
fromColor,
|
|
665
|
+
toColor,
|
|
666
|
+
lengthOfSequence,
|
|
667
|
+
interpolationFn = ColorUtils.interpolationStrategy,
|
|
668
|
+
formatType = ColorUtils.defaultFormat
|
|
669
|
+
) {
|
|
670
|
+
if (lengthOfSequence <= 0) return [];
|
|
671
|
+
const cleanLengthOSequence = Math.floor(lengthOfSequence);
|
|
672
|
+
const maxIndex = cleanLengthOSequence - 1;
|
|
673
|
+
const result = new Array(lengthOfSequence).fill(0)
|
|
674
|
+
.map((_, index) => index / maxIndex)
|
|
675
|
+
.map((pct) => ColorUtils.interpolate(fromColor, toColor, pct, interpolationFn, formatType));
|
|
676
|
+
return result;
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Simple sequence of colors to use when plotting categorical values.
|
|
681
|
+
*
|
|
682
|
+
* Used based on the Tableau color scheme.
|
|
683
|
+
*
|
|
684
|
+
* For example:
|
|
685
|
+
*
|
|
686
|
+
* ```
|
|
687
|
+
* utils.color.SEQUENCE[0]
|
|
688
|
+
*/
|
|
689
|
+
ColorUtils.SEQUENCE = ['#4e79a7', '#f28e2c', '#e15759', '#76b7b2', '#59a14f', '#edc949', '#af7aa1', '#ff9da7', '#9c755f', '#bab0ab'];
|
package/src/date.js
ADDED
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility methods for working with dates and date ranges
|
|
3
|
+
*
|
|
4
|
+
|
|
5
|
+
* * Valiate
|
|
6
|
+
* * {@link module:date.isValid|date.isValid(date)} - whether the date provided is an invalid date
|
|
7
|
+
* * Parse
|
|
8
|
+
* * {@link module:date.parse|date.parse(String)} - parse a date and throw an exception if it is not a valid date
|
|
9
|
+
* * TimeZones
|
|
10
|
+
* * {@link module:date.getTimezoneOffset|date.getTimezoneOffset(String)} - gets the number of milliseconds offset for a given timezone
|
|
11
|
+
* * {@link module:date.correctForTimezone|date.correctForTimezone(Date, String)} - meant to correct a date already off from UTC to the correct time
|
|
12
|
+
* * {@link module:date.epochShift|date.epochShift(Date, String)} - offsets a date from UTC to a given time amount
|
|
13
|
+
* - knowing some methods might behave incorrectly
|
|
14
|
+
* * Add
|
|
15
|
+
* * {@link module:date.add|date.add(Date, {days, hours, minutes, seconds)} - shift a date by a given amount
|
|
16
|
+
* * {@link module:date.endOfDay|date.endOfDay(Date)} - finds the end of day UTC for a given date
|
|
17
|
+
* * {@link module:date.startOfDay|date.startOfDay(Date)} - finds the end of day UTC for a given date
|
|
18
|
+
*
|
|
19
|
+
* --------
|
|
20
|
+
*
|
|
21
|
+
* See other libraries for more complete functionality:
|
|
22
|
+
*
|
|
23
|
+
* * [Luxon](https://moment.github.io/luxon/index.html) - successor to [Moment.js](https://momentjs.com/)
|
|
24
|
+
* * [date-fns-tz](https://github.com/marnusw/date-fns-tz) extension for [date-fns](https://date-fns.org/)
|
|
25
|
+
*
|
|
26
|
+
* also watch the [TC39 Temporal Proposal](https://github.com/tc39/proposal-temporal)
|
|
27
|
+
* - also found under caniuse: https://caniuse.com/temporal
|
|
28
|
+
*
|
|
29
|
+
* @module date
|
|
30
|
+
* @exports date
|
|
31
|
+
* @see {@link https://stackoverflow.com/questions/15141762/how-to-initialize-a-javascript-date-to-a-particular-time-zone}
|
|
32
|
+
* @see {@link https://www.youtube.com/watch?v=2rnIHsqABfM&t=750s|epochShifting}
|
|
33
|
+
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/getTimeZones|MDN TimeZone Names}
|
|
34
|
+
*/
|
|
35
|
+
module.exports = {};
|
|
36
|
+
const DateUtils = module.exports;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Collection of time durations in milliseconds
|
|
40
|
+
*/
|
|
41
|
+
module.exports.TIME = { MILLI: 1 };
|
|
42
|
+
module.exports.TIME.SECOND = DateUtils.TIME.MILLI * 1000;
|
|
43
|
+
module.exports.TIME.MINUTE = DateUtils.TIME.SECOND * 60;
|
|
44
|
+
module.exports.TIME.HOUR = DateUtils.TIME.MINUTE * 60;
|
|
45
|
+
module.exports.TIME.DAY = DateUtils.TIME.HOUR * 24;
|
|
46
|
+
|
|
47
|
+
module.exports.padTime = function padTime(num, size = 2) {
|
|
48
|
+
return String(num).padStart(size, '0');
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Simple check on whether the a JavaScript Date object is - or is not - an 'Invalid Date' instance.
|
|
53
|
+
*
|
|
54
|
+
* ```
|
|
55
|
+
* d = new Date('2024-12-1');
|
|
56
|
+
* utils.date.isValid(d); // true
|
|
57
|
+
*
|
|
58
|
+
* d = new Date('2024-12-1T');
|
|
59
|
+
* utils.date.isValid(d); // false
|
|
60
|
+
*
|
|
61
|
+
* d = new Date('some string');
|
|
62
|
+
* utils.date.isValid(d); // false
|
|
63
|
+
* ```
|
|
64
|
+
*
|
|
65
|
+
* @param {Date} testDate - JavaScript date to validate
|
|
66
|
+
* @returns {boolean} - whether the Date object is an 'invalid date' instance
|
|
67
|
+
*/
|
|
68
|
+
module.exports.isValid = (testDate) => {
|
|
69
|
+
if (!testDate) return false;
|
|
70
|
+
if (!(testDate instanceof Date)) return false;
|
|
71
|
+
return !Number.isNaN(testDate.getTime());
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Harshly parses a JavaScript Date.
|
|
76
|
+
*
|
|
77
|
+
* If the testValue is null, undefined then the same value is returned.
|
|
78
|
+
*
|
|
79
|
+
* if the testValue is a valid Date - then the parsed Date object is returned.
|
|
80
|
+
*
|
|
81
|
+
* If the testValue is not a valid Date, then throws an Error.
|
|
82
|
+
*
|
|
83
|
+
* ```
|
|
84
|
+
* d = utils.date.parse('2024-12-01'); // returns Date object
|
|
85
|
+
* d = utils.date.parse(0); // returns Date object
|
|
86
|
+
*
|
|
87
|
+
* d = utils.date.parse(null); // returns null
|
|
88
|
+
*
|
|
89
|
+
* @param {String} dateStr - value passed to Date.parse
|
|
90
|
+
* @returns {Date}
|
|
91
|
+
* @see {@link module:date.isValid|date.isValid} - in checking for invalid dates
|
|
92
|
+
*/
|
|
93
|
+
module.exports.parse = (dateStr) => {
|
|
94
|
+
if (dateStr === undefined || dateStr === null) return dateStr;
|
|
95
|
+
const result = new Date(Date.parse(dateStr));
|
|
96
|
+
if (!DateUtils.isValid(result)) {
|
|
97
|
+
throw new Error(`Could not parse date: ${dateStr}`);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
module.exports.timeZoneOffsets = new Map();
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Determines the number of milliseconds difference between
|
|
106
|
+
* a given timezone and UTC.
|
|
107
|
+
*
|
|
108
|
+
* (Note: these values are cached, and optimized for repeated use on the same value)
|
|
109
|
+
*
|
|
110
|
+
* See {@link https://en.wikipedia.org/wiki/List_of_tz_database_time_zones|the list of TZ database time zones}
|
|
111
|
+
* for the full list of options.
|
|
112
|
+
*
|
|
113
|
+
* @param {String} timeZoneStr - a timezone string like "America/Toronto"
|
|
114
|
+
* @returns {Number} - the number of milliseconds between UTC and that timezone
|
|
115
|
+
*/
|
|
116
|
+
module.exports.getTimezoneOffset = function getTimezoneOffset(timeZoneStr) {
|
|
117
|
+
if (DateUtils.timeZoneOffsets.has(timeZoneStr)) {
|
|
118
|
+
return DateUtils.timeZoneOffsets.get(timeZoneStr);
|
|
119
|
+
}
|
|
120
|
+
const d = new Date();
|
|
121
|
+
|
|
122
|
+
const format = new Intl.DateTimeFormat('en-us', {
|
|
123
|
+
year: 'numeric',
|
|
124
|
+
month: 'numeric',
|
|
125
|
+
day: 'numeric',
|
|
126
|
+
hour: 'numeric',
|
|
127
|
+
minute: 'numeric',
|
|
128
|
+
second: 'numeric',
|
|
129
|
+
hour12: false,
|
|
130
|
+
fractionalSecondDigits: 3,
|
|
131
|
+
timeZone: timeZoneStr
|
|
132
|
+
});
|
|
133
|
+
const dm = format.formatToParts(d)
|
|
134
|
+
.filter(({ type }) => type !== 'literal')
|
|
135
|
+
.reduce((result, { type, value }) => {
|
|
136
|
+
// eslint-disable-next-line no-param-reassign
|
|
137
|
+
result[type] = value;
|
|
138
|
+
return result;
|
|
139
|
+
}, {});
|
|
140
|
+
// const impactedDate = new Date(d.toLocaleString('en-US', { timeZone: timeZoneStr }));
|
|
141
|
+
const dateStr = `${dm.year}-${DateUtils.padTime(dm.month)}-${DateUtils.padTime(dm.day)}T${
|
|
142
|
+
DateUtils.padTime(dm.hour)}:${DateUtils.padTime(dm.minute)}:${DateUtils.padTime(dm.second)}.${
|
|
143
|
+
DateUtils.padTime(dm.fractionalSecond, 3)}`;
|
|
144
|
+
const impactedDate = new Date(dateStr);
|
|
145
|
+
|
|
146
|
+
const diff = d.getTime() - impactedDate.getTime();
|
|
147
|
+
|
|
148
|
+
DateUtils.timeZoneOffsets.set(timeZoneStr, diff);
|
|
149
|
+
|
|
150
|
+
return diff;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* JavaScript always stores dates in UTC, but the data you imported may have lost the timezone information.
|
|
155
|
+
*
|
|
156
|
+
* Use this to correct the timezone to the correct time UTC.
|
|
157
|
+
*
|
|
158
|
+
* For Example:
|
|
159
|
+
*
|
|
160
|
+
* ```
|
|
161
|
+
* // the date we originally pulled from the database
|
|
162
|
+
* // but thetimezone of the database was america/Toronto but not in UTC
|
|
163
|
+
* dateStr = '2024-12-06 18:00';
|
|
164
|
+
*
|
|
165
|
+
* // we may have done this:
|
|
166
|
+
* myDate = new Date(dateStr);
|
|
167
|
+
*
|
|
168
|
+
* // but now calling toISOString() is incorrect
|
|
169
|
+
* myDate.toISOString(); // '2024-12-06T18:00:00.0000'
|
|
170
|
+
*
|
|
171
|
+
* // it should be:
|
|
172
|
+
* correctedDate = utils.date.correctForTimezone('america/Toronto');
|
|
173
|
+
* correctedDate.toISOString(); // '2024-12-06T13:00:00.00.000' -- the correct time UTC
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* See {@link https://en.wikipedia.org/wiki/List_of_tz_database_time_zones|the list of TZ database time zones}
|
|
177
|
+
* for the full list of timezone options.
|
|
178
|
+
*
|
|
179
|
+
* @param {Date} date - the date to be corrected in a new instance
|
|
180
|
+
* @param {String} timeZoneStr - tz database name for the timezone
|
|
181
|
+
* @returns {Date} - copy of the date corrected
|
|
182
|
+
*/
|
|
183
|
+
module.exports.correctForTimezone = function correctForTimezone(date, timeZoneStr) {
|
|
184
|
+
const offsetMilli = DateUtils.getTimezoneOffset(timeZoneStr);
|
|
185
|
+
return new Date(date.getTime() + offsetMilli);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Epoch shift a date, so the utcDate is no longer correct,
|
|
190
|
+
* but many other functions behave closer to expected.
|
|
191
|
+
*
|
|
192
|
+
* See {@link https://stackoverflow.com/a/15171030|here why this might not be what you want}
|
|
193
|
+
*
|
|
194
|
+
* @param {Date} date - date to shift
|
|
195
|
+
* @param {String} timeZoneStr - the tz database name of the timezone
|
|
196
|
+
* @returns {Date}
|
|
197
|
+
*/
|
|
198
|
+
module.exports.epochShift = function epochShift(date, timeZoneStr) {
|
|
199
|
+
const offsetMilli = DateUtils.getTimezoneOffset(timeZoneStr);
|
|
200
|
+
return new Date(date.getTime() - offsetMilli);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Clones a date.
|
|
205
|
+
*
|
|
206
|
+
* (Doesn't seem needed currently)
|
|
207
|
+
*
|
|
208
|
+
* (NOTE: the timezone information is lost)
|
|
209
|
+
*
|
|
210
|
+
* @param {Date} targetDate - the date to be cloned
|
|
211
|
+
* @returns {Date}
|
|
212
|
+
*/
|
|
213
|
+
/*
|
|
214
|
+
module.exports.clone = function clone(targetDate) {
|
|
215
|
+
return new Date(targetDate.getTime());
|
|
216
|
+
};
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Adds an amount to a date: days, hours, minutes, seconds
|
|
221
|
+
*
|
|
222
|
+
* ```
|
|
223
|
+
* d = new Date('2024-12-26 6:00:00');
|
|
224
|
+
* d30 = utils.date.add(d, { minutes: 30 }); // Date('2024-12-26 6:00:00')
|
|
225
|
+
* ```
|
|
226
|
+
*
|
|
227
|
+
* @param {Date} dateValue - date to add to
|
|
228
|
+
* @param {Object} options - options of what to add
|
|
229
|
+
* @param {Number} [options.days=0] - number of days to add
|
|
230
|
+
* @param {Number} [options.minutes=0] - number of minutes to add
|
|
231
|
+
* @param {Number} [options.hours=0] - number of minutes to add
|
|
232
|
+
* @param {Number} [options.seconds=0] - number of seconds to add
|
|
233
|
+
* @returns {Date} -
|
|
234
|
+
*/
|
|
235
|
+
module.exports.add = function add(dateValue, options = null) {
|
|
236
|
+
if (!options) return dateValue;
|
|
237
|
+
|
|
238
|
+
const { days = 0, minutes = 0, hours = 0, seconds = 0 } = options;
|
|
239
|
+
return new Date(dateValue.getTime()
|
|
240
|
+
+ DateUtils.TIME.DAY * days
|
|
241
|
+
+ DateUtils.TIME.HOUR * hours
|
|
242
|
+
+ DateUtils.TIME.MINUTE * minutes
|
|
243
|
+
+ DateUtils.TIME.SECOND * seconds);
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Creates a new date that is at the end of the day (in UTC)
|
|
248
|
+
*
|
|
249
|
+
* ```
|
|
250
|
+
* d = new Date('2024-12-26 6:00:00');
|
|
251
|
+
* dEnd = utils.date.endOfDay(d); // Date('2024-12-26 23:59:59.9999')
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* @param {Date} dateValue - Date where only the year,month,day is used
|
|
255
|
+
* @returns {Date} - new date set to the end of the day for dateValue's date
|
|
256
|
+
*/
|
|
257
|
+
module.exports.endOfDay = function endOfDay(dateValue) {
|
|
258
|
+
const startDate = Math.floor(dateValue.getTime() / DateUtils.TIME.DAY) * DateUtils.TIME.DAY;
|
|
259
|
+
return new Date(startDate + DateUtils.TIME.DAY - 1);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Creates a new date that is at the start of the day (in UTC)
|
|
264
|
+
*
|
|
265
|
+
* ```
|
|
266
|
+
* d = new Date('2024-12-26 6:00:00');
|
|
267
|
+
* dEnd = utils.date.startOfDay(d); // Date('2024-12-26 0:00:00.0000')
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
270
|
+
* @param {Date} dateValue - Date where only the year,month,day is used
|
|
271
|
+
* @returns {Date} - new date set to the end of the day for dateValue's date
|
|
272
|
+
*/
|
|
273
|
+
module.exports.startOfDay = function endOfDay(dateValue) {
|
|
274
|
+
const startDate = Math.floor(dateValue.getTime() / DateUtils.TIME.DAY) * DateUtils.TIME.DAY;
|
|
275
|
+
return new Date(startDate);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Represents a Range between two times
|
|
280
|
+
*/
|
|
281
|
+
class DateRange {
|
|
282
|
+
/**
|
|
283
|
+
* The starting date
|
|
284
|
+
* @type {Date}
|
|
285
|
+
*/
|
|
286
|
+
startDate;
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* The ending date
|
|
290
|
+
* @type {Date}
|
|
291
|
+
*/
|
|
292
|
+
endDate;
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* @param {Date} startDate - the starting datetime of the range
|
|
296
|
+
* @param {Date} endDate - the ending datetime of the range
|
|
297
|
+
*/
|
|
298
|
+
constructor(startDate, endDate) {
|
|
299
|
+
this.reinitialize(startDate, endDate);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Reinitializes the object
|
|
304
|
+
*
|
|
305
|
+
* (Sometimes useful for shifting times after the fact)
|
|
306
|
+
*
|
|
307
|
+
* @param {Date} startDate - the starting date
|
|
308
|
+
* @param {Date} endDate - the ending date
|
|
309
|
+
*/
|
|
310
|
+
reinitialize(startDate, endDate) {
|
|
311
|
+
if (startDate > endDate) {
|
|
312
|
+
this.startDate = endDate;
|
|
313
|
+
this.endDate = startDate;
|
|
314
|
+
} else {
|
|
315
|
+
this.startDate = startDate;
|
|
316
|
+
this.endDate = endDate;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Creates a DateRange based on the start and end of the day UTC.
|
|
322
|
+
*
|
|
323
|
+
* This is very useful for determining overlapping dates.
|
|
324
|
+
*
|
|
325
|
+
* @param {Date} targetDate - date to use to find the start and end UTC for
|
|
326
|
+
* @returns {DateRange}
|
|
327
|
+
*/
|
|
328
|
+
static startAndEndOfDay(targetDate) {
|
|
329
|
+
const startDate = DateUtils.startOfDay(targetDate);
|
|
330
|
+
const endDate = DateUtils.endOfDay(targetDate);
|
|
331
|
+
return new DateRange(startDate, endDate);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Whether this dateRange overlaps with a target dateRange.
|
|
336
|
+
* @param {DateRange} targetDateRange - dateRange to compare
|
|
337
|
+
* @returns {Boolean}
|
|
338
|
+
* @example
|
|
339
|
+
* overlapA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
|
|
340
|
+
* overlapB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
|
|
341
|
+
* overlapC = new Date(Date.UTC(2024, 12, 26, 14, 0, 0));
|
|
342
|
+
* overlapD = new Date(Date.UTC(2024, 12, 26, 15, 0, 0));
|
|
343
|
+
*
|
|
344
|
+
* rangeBefore = new utils.DateRange(overlapA, overlapB);
|
|
345
|
+
* rangeAfter = new utils.DateRange(overlapC, overlapD);
|
|
346
|
+
*
|
|
347
|
+
* rangeBefore.overlaps(rangeAfter); // false
|
|
348
|
+
* rangeAfter.overlaps(rangeBefore); // false
|
|
349
|
+
*
|
|
350
|
+
* rangeBefore = new utils.DateRange(overlapA, overlapC);
|
|
351
|
+
* rangeAfter = new utils.DateRange(overlapB, overlapD);
|
|
352
|
+
*
|
|
353
|
+
* rangeBefore.overlaps(rangeAfter); // true
|
|
354
|
+
* rangeAfter.overlaps(rangeBefore); // true
|
|
355
|
+
*/
|
|
356
|
+
overlaps(targetDateRange) {
|
|
357
|
+
return (this.endDate > targetDateRange.startDate
|
|
358
|
+
&& this.startDate < targetDateRange.endDate);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Determines if a datetime is within the range
|
|
363
|
+
*
|
|
364
|
+
* @param {Date} dateToCheck - the value to test if it is within the date range
|
|
365
|
+
* @returns {Boolean} - if the value is within the range (true) or not (false)
|
|
366
|
+
*
|
|
367
|
+
* @example
|
|
368
|
+
* withinA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
|
|
369
|
+
* withinB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
|
|
370
|
+
* withinC = new Date(Date.UTC(2024, 12, 26, 14, 0, 0));
|
|
371
|
+
* withinD = new Date(Date.UTC(2024, 12, 26, 15, 0, 0));
|
|
372
|
+
*
|
|
373
|
+
* range = new utils.DateRange(withinB, withinD);
|
|
374
|
+
* range.contains(withinA); // false - it was before the range
|
|
375
|
+
*
|
|
376
|
+
* range.contains(withinB); // true
|
|
377
|
+
* range.contains(withinC); // true
|
|
378
|
+
* range.contains(withinD); // true
|
|
379
|
+
*
|
|
380
|
+
*/
|
|
381
|
+
contains(dateToCheck) {
|
|
382
|
+
const testTime = dateToCheck.getTime();
|
|
383
|
+
return testTime >= this.startDate.getTime() && testTime <= this.endDate.getTime();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Determines the millisecond duration between the end and start time.
|
|
388
|
+
*
|
|
389
|
+
* ```
|
|
390
|
+
* durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
|
|
391
|
+
* durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
|
|
392
|
+
* range = new utils.DateRange(durationA, durationB);
|
|
393
|
+
*
|
|
394
|
+
* range.durationString(); // 1 hour in milliseconds; 1000 * 60 * 60;
|
|
395
|
+
* ```
|
|
396
|
+
*
|
|
397
|
+
* @returns {Number}
|
|
398
|
+
*/
|
|
399
|
+
duration() {
|
|
400
|
+
return this.endDate.getTime() - this.startDate.getTime();
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Determines the duration in a clear and understandable string;
|
|
405
|
+
*
|
|
406
|
+
* ```
|
|
407
|
+
* durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
|
|
408
|
+
* durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
|
|
409
|
+
* range = new utils.DateRange(durationA, durationB);
|
|
410
|
+
*
|
|
411
|
+
* range.durationString(); // '0 days, 1 hours, 0 minutes, 0.0 seconds';
|
|
412
|
+
* ```
|
|
413
|
+
*
|
|
414
|
+
* @returns {String}
|
|
415
|
+
*/
|
|
416
|
+
durationString() {
|
|
417
|
+
const dur = this.duration();
|
|
418
|
+
const divideRemainder = (val, denominator) => ({ value: Math.floor(val / denominator), remainder: val % denominator });
|
|
419
|
+
let result = divideRemainder(dur, DateUtils.TIME.DAY);
|
|
420
|
+
const days = result.value;
|
|
421
|
+
result = divideRemainder(result.remainder, DateUtils.TIME.HOUR);
|
|
422
|
+
const hours = result.value;
|
|
423
|
+
result = divideRemainder(result.remainder, DateUtils.TIME.MINUTE);
|
|
424
|
+
const minutes = result.value;
|
|
425
|
+
result = divideRemainder(result.remainder, DateUtils.TIME.SECOND);
|
|
426
|
+
const seconds = result.value;
|
|
427
|
+
const milli = result.remainder;
|
|
428
|
+
return `${days} days, ${hours} hours, ${minutes} minutes, ${seconds}.${milli} seconds`;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Determines the duration in days:hours:minutes:seconds.milliseconds
|
|
433
|
+
*
|
|
434
|
+
* ```
|
|
435
|
+
* durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
|
|
436
|
+
* durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
|
|
437
|
+
* range = new utils.DateRange(durationA, durationB);
|
|
438
|
+
*
|
|
439
|
+
* range.durationString(); // '0:01:00:00.0000';
|
|
440
|
+
* ```
|
|
441
|
+
*
|
|
442
|
+
* @returns {String}
|
|
443
|
+
*/
|
|
444
|
+
durationISO() {
|
|
445
|
+
const dur = this.duration();
|
|
446
|
+
const divideRemainder = (val, denominator) => ({ value: Math.floor(val / denominator), remainder: val % denominator });
|
|
447
|
+
let result = divideRemainder(dur, DateUtils.TIME.DAY);
|
|
448
|
+
const days = String(result.value);
|
|
449
|
+
result = divideRemainder(result.remainder, DateUtils.TIME.HOUR);
|
|
450
|
+
const hours = String(result.value).padStart(2, '0');
|
|
451
|
+
result = divideRemainder(result.remainder, DateUtils.TIME.MINUTE);
|
|
452
|
+
const minutes = String(result.value).padStart(2, '0');
|
|
453
|
+
result = divideRemainder(result.remainder, DateUtils.TIME.SECOND);
|
|
454
|
+
const seconds = String(result.value).padStart(2, '0');
|
|
455
|
+
const milli = String(result.remainder).padStart(4, '0');
|
|
456
|
+
return `${days}:${hours}:${minutes}:${seconds}.${milli}`;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Determines if both the startDate and endDate are valid dates.
|
|
461
|
+
*
|
|
462
|
+
* @returns {Boolean}
|
|
463
|
+
*/
|
|
464
|
+
isValid() {
|
|
465
|
+
return DateUtils.isValid(this.startDate) && DateUtils.isValid(this.endDate);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/**
|
|
469
|
+
* Converts the daterange to a string value
|
|
470
|
+
*
|
|
471
|
+
* ```
|
|
472
|
+
* durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
|
|
473
|
+
* durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
|
|
474
|
+
* range = new utils.DateRange(durationA, durationB);
|
|
475
|
+
*
|
|
476
|
+
* range.toString(); // '2025-01-26T12:00:00.000Z to 2025-01-26T13:00:00.000Z';
|
|
477
|
+
* ```
|
|
478
|
+
*
|
|
479
|
+
* @returns {String}
|
|
480
|
+
*/
|
|
481
|
+
toString() {
|
|
482
|
+
return `${this.startDate.toISOString()} to ${this.endDate.toISOString()}`;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Converts the daterange to a local string value
|
|
487
|
+
*
|
|
488
|
+
* ```
|
|
489
|
+
* durationA = new Date(Date.UTC(2024, 12, 26, 12, 0, 0));
|
|
490
|
+
* durationB = new Date(Date.UTC(2024, 12, 26, 13, 0, 0));
|
|
491
|
+
* range = new utils.DateRange(durationA, durationB);
|
|
492
|
+
*
|
|
493
|
+
* range.toLocaleString(); // '1/26/2025, 12:00:00 PM to 1/26/2025, 1:00:00 PM'
|
|
494
|
+
* ```
|
|
495
|
+
*
|
|
496
|
+
* @returns {String}
|
|
497
|
+
*/
|
|
498
|
+
toLocaleString() {
|
|
499
|
+
return `${this.startDate.toLocaleString()} to ${this.endDate.toLocaleString()}`;
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
module.exports.DateRange = DateRange;
|
package/src/format.js
CHANGED
|
@@ -1347,3 +1347,19 @@ module.exports.extractWords = function extractWords(strToExtractFrom, additional
|
|
|
1347
1347
|
|
|
1348
1348
|
return cleanStrings.reduce((result, str) => [...result, ...((str || '').match(regex) || [])], []);
|
|
1349
1349
|
};
|
|
1350
|
+
|
|
1351
|
+
/**
|
|
1352
|
+
* A function that returns the value provided
|
|
1353
|
+
*
|
|
1354
|
+
* ```
|
|
1355
|
+
* alwaysBlack = utils.format.constantFn('#000000');
|
|
1356
|
+
* alwaysBlack(); // '#000000'
|
|
1357
|
+
* alwaysBlack(); // '#000000'
|
|
1358
|
+
* ```
|
|
1359
|
+
*
|
|
1360
|
+
* @param {any} val - Any value
|
|
1361
|
+
* @returns {Function} - a function that accepts no parameters, and always returns value provided
|
|
1362
|
+
*/
|
|
1363
|
+
module.exports.constantFn = function constantFn(val) {
|
|
1364
|
+
return () => val;
|
|
1365
|
+
};
|
package/src/hashMap.js
CHANGED
|
@@ -216,3 +216,27 @@ module.exports.fromObject = function fromObject(target) {
|
|
|
216
216
|
return [...Object.keys(target)]
|
|
217
217
|
.reduce((result, key) => HashMapUtil.add(result, key, target[key]), new Map());
|
|
218
218
|
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Simple and safe Map accessing function.
|
|
222
|
+
*
|
|
223
|
+
* ```
|
|
224
|
+
* styleMap = new Map(['1', 'background-color: #FF0000'], ['2', 'background-color: #00FF00']]);
|
|
225
|
+
* styleFn = utils.map.mappingFn(styleMap, 'background-color: #aaaaaa');
|
|
226
|
+
*
|
|
227
|
+
* styleFn('1'); // 'background-color: #FF0000';
|
|
228
|
+
* styleFn('2'); // 'background-color: #00FF00';
|
|
229
|
+
* styleFn('somethingElse'); // 'background-color: #aaaaaa' - because it was not found
|
|
230
|
+
*
|
|
231
|
+
* @param {Map} map - map to use when checking the subsequent function
|
|
232
|
+
* @param {any} [defaultValue=''] - default value to return if key is not found
|
|
233
|
+
* @returns {Function} - (key) => map.get(key) || defaultValue
|
|
234
|
+
*/
|
|
235
|
+
module.exports.mappingFn = function mappingFn(map, defaultValue = '') {
|
|
236
|
+
return function mappingFnImpl(key) {
|
|
237
|
+
if (map.has(key)) {
|
|
238
|
+
return map.get(key);
|
|
239
|
+
}
|
|
240
|
+
return defaultValue;
|
|
241
|
+
};
|
|
242
|
+
};
|
package/src/index.js
CHANGED
|
@@ -4,6 +4,7 @@ const base64 = require('./base64');
|
|
|
4
4
|
const chain = require('./chain');
|
|
5
5
|
const color = require('./color');
|
|
6
6
|
const datasets = require('./datasets');
|
|
7
|
+
const date = require('./date');
|
|
7
8
|
const describe = require('./describe');
|
|
8
9
|
const group = require('./group');
|
|
9
10
|
const hashMap = require('./hashMap');
|
|
@@ -50,6 +51,9 @@ module.exports = {
|
|
|
50
51
|
/** @see {@link module:datasets} */
|
|
51
52
|
datasets,
|
|
52
53
|
dataset: datasets,
|
|
54
|
+
/** @see {@link module:date} */
|
|
55
|
+
date,
|
|
56
|
+
DateRange: date.DateRange,
|
|
53
57
|
/** @see {@link module:describe} */
|
|
54
58
|
describe,
|
|
55
59
|
/** @see {@link module:file} */
|
package/src/object.js
CHANGED
|
@@ -217,6 +217,7 @@ module.exports.augment = function augment(objCollection, mappingFn, inPlace = fa
|
|
|
217
217
|
* @param {Function | String} propertyOrFn - Name of the property or Function to return a value
|
|
218
218
|
* @returns {Map<String, Object>} - map using the propertyName as the key
|
|
219
219
|
* @see {@link module:group.by|group(collection, propertyOrFn)} - if there is a possibility the records are not unique
|
|
220
|
+
* @see {@link module:object.join|object.join()} - join two objects by a shared index
|
|
220
221
|
* @example
|
|
221
222
|
* const data = [{ id: '123', name: 'jim' },
|
|
222
223
|
* { id: '456', name: 'mary' },
|
|
@@ -1248,6 +1249,7 @@ module.exports.generateSchema = function generateSchema(targetObj) {
|
|
|
1248
1249
|
* @param {Function} joinFn - function to call each time an objectArray object, has an indexField found in targetMap <br />
|
|
1249
1250
|
* Signature: `(sourceObj:Object, mappedObject:Object) => {Object}`
|
|
1250
1251
|
* @returns {Array<Object>} - Array of results returned from `joinFn`
|
|
1252
|
+
* @see {@link module:object.mapByProperty|object.mapByProperty}
|
|
1251
1253
|
*/
|
|
1252
1254
|
module.exports.join = function join(objectArray, indexField, targetMap, joinFn) {
|
|
1253
1255
|
const cleanArray = !objectArray
|