@valkyriestudios/utils 12.19.0 → 12.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # @valkyriestudios/utils
2
2
 
3
- [![CodeCov](https://codecov.io/gh/ValkyrieStudios/utils/branch/master/graph/badge.svg)](https://codecov.io/gh/ValkyrieStudios/utils)
4
- [![Test](https://github.com/ValkyrieStudios/utils/actions/workflows/test.yml/badge.svg?branch=master)](https://github.com/ValkyrieStudios/utils/actions/workflows/test.yml)
5
- [![Lint](https://github.com/ValkyrieStudios/utils/actions/workflows/lint.yml/badge.svg?branch=master)](https://github.com/ValkyrieStudios/utils/actions/workflows/lint.yml)
6
- [![CodeQL](https://github.com/ValkyrieStudios/utils/actions/workflows/github-code-scanning/codeql/badge.svg?branch=master)](https://github.com/ValkyrieStudios/utils/actions/workflows/github-code-scanning/codeql)
3
+ [![CodeCov](https://codecov.io/gh/ValkyrieStudios/utils/branch/main/graph/badge.svg)](https://codecov.io/gh/ValkyrieStudios/utils)
4
+ [![Test](https://github.com/ValkyrieStudios/utils/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/ValkyrieStudios/utils/actions/workflows/test.yml)
5
+ [![Lint](https://github.com/ValkyrieStudios/utils/actions/workflows/lint.yml/badge.svg?branch=main)](https://github.com/ValkyrieStudios/utils/actions/workflows/lint.yml)
6
+ [![CodeQL](https://github.com/ValkyrieStudios/utils/actions/workflows/github-code-scanning/codeql/badge.svg?branch=main)](https://github.com/ValkyrieStudios/utils/actions/workflows/github-code-scanning/codeql)
7
7
  [![npm](https://img.shields.io/npm/v/@valkyriestudios/utils.svg)](https://www.npmjs.com/package/@valkyriestudios/utils)
8
8
  [![npm](https://img.shields.io/npm/dm/@valkyriestudios/utils.svg)](https://www.npmjs.com/package/@valkyriestudios/utils)
9
9
 
@@ -15,14 +15,14 @@ Zero-dependency collection of single-function utilities for common tasks
15
15
  ## Available Functions
16
16
 
17
17
  ### array
18
- - **isArray(val:any)**
18
+ - **isArray(val:unknown)**
19
19
  Check if a variable is of type Array
20
20
  ```typescript
21
21
  isArray({a:1}); // FALSE
22
22
  isArray([]); // TRUE
23
23
  ```
24
24
 
25
- - **isNotEmptyArray(val:any)**
25
+ - **isNotEmptyArray(val:unknown)**
26
26
  Check if a variable a non-empty array
27
27
  ```typescript
28
28
  isNotEmptyArray({a:1}); // FALSE
@@ -365,13 +365,44 @@ await memoized('123456'); /* Original function will be called and re-cached */
365
365
  ```
366
366
 
367
367
  ### date
368
- - **isDate(val:any)**
368
+ - **isDate(val:unknown)**
369
369
  Check if a variable is of type Date
370
370
  ```typescript
371
371
  isDate(new Date('December 17, 1995 03:24:00')); // TRUE
372
372
  isDate('December 17, 1995 03:24:00'); // FALSE
373
373
  ```
374
374
 
375
+ - **isFormat(val:unknown, spec:string)**
376
+ Check if a variable is a string in a particular date format
377
+ ```typescript
378
+ isFormat('2024-02-07', 'YYYY-MM-DD'); // TRUE
379
+ isFormat('2024-2-07', 'YYYY-MM-DD'); // FALSE
380
+ isFormat('12:30 AM', 'HH:mm A'); // TRUE
381
+ isFormat('2024-Q4', 'YYYY-[Q]Q'); // TRUE
382
+ isFormat('2024-Q5', 'YYYY-[Q]Q'); // FALSE (there is no such thing as a fifth quarter)
383
+ isFormat('2024-02-29T12:30:00.000Z', 'ISO'); // TRUE
384
+ isFormat('2023-02-29T12:30:00.000Z', 'ISO'); // FALSE (leap year)
385
+ ```
386
+
387
+ Available tokens for usage in spec:
388
+ | Token | Description | Example |
389
+ |:---------|:--------------------------|:---------------|
390
+ | `YYYY` | Full Year | 2021 |
391
+ | `Q` | Quarters of the year | 1 2 3 4 |
392
+ | `MM` | Month as 2 char | 01 02 .. 11 12 |
393
+ | `DD` | Day of month as 2 char | 01 02 .. 30 31 |
394
+ | `HH` | Hours as 2-char | 00 01 .. 22 23 |
395
+ | `mm` | Minutes as 2-char | 00 01 .. 58 59 |
396
+ | `ss` | Seconds as 2-char | 00 01 .. 58 59 |
397
+ | `SSS` | Milliseconds as 3-digit | 000 001 ... 998 999 |
398
+ | `A` | Uppercase AM/PM | AM ... PM |
399
+ | `a` | Lowercase AM/PM | am ... pm |
400
+ | `Z` | Zone, does not allow full zone names, only Z or offsets | `Z` `+02:00` |
401
+ | `ISO` | Check for full iso date format, take note this enforces milliseconds as a requirement | 2024-02-03T10:28:30.000Z |
402
+
403
+ Note: The `ISO` token is a shorthand for `YYYY-MM-DDTHH:mm:ss.SSSZ`
404
+ Note: You can escape characters by surrounding them with `[...]` in your spec, eg: `YYYY-[Q]Q` would check for example `2024-Q1`
405
+
375
406
  - **diff(val_a:Date, val_b:Date, key:string)**
376
407
  Take two incoming dates and return the difference between them in a certain unit. Possible key options(week,day,hour,minute,second,millisecond).
377
408
 
@@ -385,6 +416,7 @@ diff(new Date("2022-10-05T13:12:11+02:00"), new Date("2022-10-05T17:43:09.344+06
385
416
  diff(new Date("2022-10-05T13:12:11+02:00"), new Date("2022-10-05T17:43:09.344+06:00"), 'millisecond'); // -1858344
386
417
  diff(new Date("2022-11-05T13:12:11+06:00"), new Date("2022-10-05T13:25:43.898+02:00")); // 2663187102
387
418
  ```
419
+
388
420
  - **format(val:Date, spec:string, locale?:string, zone?:string):string**
389
421
  Format a date according to a spec/locale and zone
390
422
 
@@ -392,6 +424,8 @@ Note: The locale is by default set to 'en-US'
392
424
 
393
425
  Note: The zone is by default detected as the zone of the client
394
426
 
427
+ Note: You can escape characters by surrounding them with `[...]` in your spec, eg: `YYYY-[Q]Q` would for example become `2024-Q1`
428
+
395
429
  Available tokens for usage in spec:
396
430
  | Token | Description | Example |
397
431
  |:---------|:--------------------------|:---------------|
@@ -670,7 +704,7 @@ Generate a unique identifier (guid) according to RFC4122
670
704
  guid(); // 245caf1a-86af-11e7-bb31-be2e44b06b34
671
705
  ```
672
706
 
673
- - **fnv1A(val:any)**
707
+ - **fnv1A(val:unknown)**
674
708
  Generate a fnv1A hash from an object, using a 32-bit prime/offset
675
709
  ```typescript
676
710
  fnv1A('hello world'); // -2023343616
@@ -681,7 +715,7 @@ fnv1A(new Date('2012-02-02')); // 1655579136
681
715
  ```
682
716
 
683
717
  ### number
684
- - **isNumber(val:any)**
718
+ - **isNumber(val:unknown)**
685
719
  Check if a variable is a number
686
720
  ```typescript
687
721
  isNumber('foo'); // FALSE
@@ -735,7 +769,7 @@ isNumberBetween(0, 0, 1); // TRUE
735
769
  isNumberBetween(-1, 0, 1); // FALSE
736
770
  ```
737
771
 
738
- - **isInteger(val:any)**
772
+ - **isInteger(val:unknown)**
739
773
  Check if a variable is an integer
740
774
  ```typescript
741
775
  isInteger('foo'); // FALSE
@@ -791,7 +825,7 @@ isIntegerBetween(0, 0, 1); // TRUE
791
825
  isIntegerBetween(-1, 0, 1); // FALSE
792
826
  ```
793
827
 
794
- - **isNumericalNaN(val:any)**
828
+ - **isNumericalNaN(val:unknown)**
795
829
  Check if a variable is a numerical nan ( a number that is a NaN, this distinguishment is made since both a string or a number can be NaN)
796
830
  ```typescript
797
831
  isNumericalNaN('foo'); // FALSE
@@ -829,14 +863,14 @@ randomIntBetween(25, 100); // Will generate a random between 25 and 100 (100 not
829
863
  ```
830
864
 
831
865
  ### object
832
- - **isObject(val:any)**
866
+ - **isObject(val:unknown)**
833
867
  Check if a variable is of type Object
834
868
  ```typescript
835
869
  isObject({a: 1}); // TRUE
836
870
  isObject(1); // FALSE
837
871
  ```
838
872
 
839
- - **isNotEmptyObject(val:any)**
873
+ - **isNotEmptyObject(val:unknown)**
840
874
  Check if a variable a non-empty object
841
875
  ```typescript
842
876
  isNotEmptyObject({a:1}); // TRUE
@@ -887,7 +921,7 @@ define({
887
921
  ```
888
922
 
889
923
  ### regexp
890
- - **isRegExp(val:any)**
924
+ - **isRegExp(val:unknown)**
891
925
  Check if a variable is an instance of RegExp
892
926
  ```typescript
893
927
  isRegExp('foo'); // FALSE
@@ -903,7 +937,7 @@ sanitizeRegExp('contact@valkyriestudios.be'); // contact@valkyriestudios\\.be
903
937
  ```
904
938
 
905
939
  ### string
906
- - **isString(val:any)**
940
+ - **isString(val:unknown)**
907
941
  Check if a variable is a string
908
942
  ```typescript
909
943
  isString('foo'); // TRUE
@@ -921,7 +955,7 @@ isStringBetween(' Joe', 1, 3); // TRUE
921
955
  isStringBetween(' Joe', 1, 3, false); // FALSE
922
956
  ```
923
957
 
924
- - **isNotEmptyString(val:any, trimmed:boolean=true)**
958
+ - **isNotEmptyString(val:unknown, trimmed:boolean=true)**
925
959
  Check if a variable a non-empty string
926
960
  ```typescript
927
961
  isNotEmptyString({a:1}); // FALSE
@@ -931,7 +965,7 @@ isNotEmptyString(' ', false); // TRUE
931
965
  isNotEmptyString('Hi'); // TRUE
932
966
  ```
933
967
 
934
- - **shorten(val:any, length:integer, postfix:string=..., truncate_words=true)**
968
+ - **shorten(val:string, length:integer, postfix:string=..., truncate_words=true)**
935
969
  Shorten a string and add a postfix if string went over length
936
970
  ```typescript
937
971
  shorten('To the moon and beyond', 11, '..'); // 'To the moon..'
package/date/format.js CHANGED
@@ -15,36 +15,35 @@ finally {
15
15
  DEFAULT_TZ = 'UTC';
16
16
  }
17
17
  const escape_rgx = /\[[\s\S]+?]/g;
18
- const intl_formatters = new Map();
19
- const spec_cache = new Map();
20
- const zone_offset_cache = new Map();
18
+ const intl_formatters = Object.create(null);
19
+ const spec_cache = Object.create(null);
20
+ const zone_offset_cache = Object.create(null);
21
21
  function DOY(d) {
22
22
  return ((d - new Date(d.getFullYear(), 0, 0)) / 86400000) | 0;
23
23
  }
24
24
  function toZone(date, zone) {
25
25
  const ckey = `${zone}:${date.getUTCFullYear()}${DOY(date)}`;
26
- if (zone_offset_cache.has(ckey))
27
- return new Date(date.getTime() + zone_offset_cache.get(ckey));
26
+ if (zone_offset_cache[ckey] !== undefined)
27
+ return new Date(date.getTime() + zone_offset_cache[ckey]);
28
28
  const client_time = date.getTime();
29
- let zone_time = false;
29
+ let zone_time = null;
30
30
  try {
31
31
  zone_time = new Date(date.toLocaleString(DEFAULT_LOCALE, { timeZone: zone })).getTime();
32
32
  }
33
33
  catch (err) {
34
- }
35
- if (!Number.isInteger(zone_time))
36
34
  throw new Error(`format: Invalid zone passed - ${zone}`);
35
+ }
37
36
  const offset = zone_time - client_time;
38
- zone_offset_cache.set(ckey, offset);
37
+ zone_offset_cache[ckey] = offset;
39
38
  return new Date(client_time + offset);
40
39
  }
41
40
  function runIntl(loc, token, props, val) {
42
- const hash = `${loc}:${token}`;
43
- let formatter = intl_formatters.get(hash);
41
+ const hash = loc + ':' + token;
42
+ let formatter = intl_formatters[hash];
44
43
  if (!formatter) {
45
44
  try {
46
45
  formatter = new Intl.DateTimeFormat(loc, props);
47
- intl_formatters.set(hash, formatter);
46
+ intl_formatters[hash] = formatter;
48
47
  }
49
48
  catch (err) {
50
49
  throw new Error(`format: Failed to run conversion for ${token} with locale ${loc}`);
@@ -57,21 +56,46 @@ const Tokens = [
57
56
  ['Q', d => ((d.getMonth() + 3) / 3) | 0],
58
57
  ['MMMM', (d, loc) => runIntl(loc, 'MMMM', { month: 'long' }, d)],
59
58
  ['MMM', (d, loc) => runIntl(loc, 'MMM', { month: 'short' }, d)],
60
- ['MM', d => `${d.getMonth() + 1}`.padStart(2, '0')],
59
+ ['MM', d => {
60
+ const val = d.getMonth() + 1;
61
+ return (val < 10 ? '0' : '') + val;
62
+ }],
61
63
  ['M', d => d.getMonth() + 1],
62
- ['DD', d => `${d.getDate()}`.padStart(2, '0')],
64
+ ['DD', d => {
65
+ const val = d.getDate();
66
+ return (val < 10 ? '0' : '') + val;
67
+ }],
63
68
  ['D', d => d.getDate()],
64
69
  ['dddd', (d, loc) => runIntl(loc, 'dddd', { weekday: 'long' }, d)],
65
70
  ['ddd', (d, loc) => runIntl(loc, 'ddd', { weekday: 'short' }, d)],
66
- ['HH', d => `${d.getHours()}`.padStart(2, '0')],
71
+ ['HH', d => {
72
+ const val = d.getHours();
73
+ return (val < 10 ? '0' : '') + val;
74
+ }],
67
75
  ['H', d => d.getHours()],
68
- ['hh', d => `${((d.getHours() + 11) % 12) + 1}`.padStart(2, '0')],
76
+ ['hh', d => {
77
+ const val = ((d.getHours() + 11) % 12) + 1;
78
+ return (val < 10 ? '0' : '') + val;
79
+ }],
69
80
  ['h', d => ((d.getHours() + 11) % 12) + 1],
70
- ['mm', d => `${d.getMinutes()}`.padStart(2, '0')],
81
+ ['mm', d => {
82
+ const val = d.getMinutes();
83
+ return (val < 10 ? '0' : '') + val;
84
+ }],
71
85
  ['m', d => d.getMinutes()],
72
- ['ss', d => `${d.getSeconds()}`.padStart(2, '0')],
86
+ ['ss', d => {
87
+ const val = d.getSeconds();
88
+ return (val < 10 ? '0' : '') + val;
89
+ }],
73
90
  ['s', d => d.getSeconds()],
74
- ['SSS', d => `${d.getMilliseconds()}`.padStart(3, '0')],
91
+ ['SSS', d => {
92
+ const val = d.getMilliseconds();
93
+ return val < 10
94
+ ? '00' + val
95
+ : val < 100
96
+ ? '0' + val
97
+ : val;
98
+ }],
75
99
  ['A', d => d.getHours() < 12 ? 'AM' : 'PM'],
76
100
  ['a', d => d.getHours() < 12 ? 'am' : 'pm'],
77
101
  ['l', (d, loc) => runIntl(loc, 'l', { dateStyle: 'short' }, d)],
@@ -79,42 +103,46 @@ const Tokens = [
79
103
  ['t', (d, loc) => runIntl(loc, 't', { timeStyle: 'short' }, d)],
80
104
  ['T', (d, loc) => runIntl(loc, 'T', { timeStyle: 'medium' }, d)],
81
105
  ]
82
- .sort((a, b) => a[0].length > b[0].length ? -1 : 1)
83
- .map((el) => [el[0], new RegExp(el[0], 'g'), el[1]]);
106
+ .sort((a, b) => a[0].length > b[0].length ? -1 : 1);
84
107
  function getSpecChain(spec) {
85
- let spec_chain = spec_cache.get(spec);
86
- if (spec_chain)
87
- return spec_chain;
88
- spec_chain = [];
89
- let cursor;
90
- let spec_cursor = spec;
108
+ if (spec_cache[spec] !== undefined)
109
+ return spec_cache[spec];
110
+ const spec_chain = [];
111
+ const matched_positions = new Set();
91
112
  for (let i = 0; i < Tokens.length; i++) {
92
- cursor = Tokens[i];
93
- if (spec_cursor.indexOf(cursor[0]) < 0)
94
- continue;
95
- spec_chain.push(cursor);
96
- spec_cursor = spec_cursor.replace(cursor[1], '');
113
+ const [token] = Tokens[i];
114
+ let pos = spec.indexOf(token);
115
+ const token_len = token.length;
116
+ while (pos !== -1) {
117
+ if (!matched_positions.has(pos)) {
118
+ spec_chain.push(Tokens[i]);
119
+ for (let j = 0; j < token_len; j++) {
120
+ matched_positions.add(pos + j);
121
+ }
122
+ }
123
+ pos = spec.indexOf(token, pos + 1);
124
+ }
97
125
  }
98
- if (spec_chain.length === 0)
99
- return false;
100
- spec_cache.set(spec, spec_chain);
101
- return spec_chain;
126
+ const result = spec_chain.length ? spec_chain : null;
127
+ spec_cache[spec] = result;
128
+ return result;
102
129
  }
103
130
  function format(val, spec, locale = DEFAULT_LOCALE, zone = DEFAULT_TZ) {
104
131
  if (!(0, is_1.isDate)(val))
105
132
  throw new TypeError('format: val must be a Date');
106
- if (typeof spec !== 'string' || !spec.trim().length)
107
- throw new TypeError('format: spec must be a non-empty string');
108
- if (typeof locale !== 'string' || !locale.trim().length)
109
- throw new TypeError('format: locale must be a non-empty string');
110
- if (typeof zone !== 'string' || !zone.trim().length)
111
- throw new TypeError('format: zone must be a non-empty string');
133
+ if (typeof spec !== 'string')
134
+ throw new TypeError('format: spec must be a string');
135
+ if (typeof locale !== 'string')
136
+ throw new TypeError('format: locale must be a string');
137
+ if (typeof zone !== 'string')
138
+ throw new TypeError('format: zone must be a string');
112
139
  let formatted_string = spec;
113
140
  const escaped_acc = [];
141
+ let escaped_count = 0;
114
142
  if (formatted_string.indexOf('[') >= 0) {
115
143
  formatted_string = formatted_string.replace(escape_rgx, match => {
116
- const escape_token = `$R${escaped_acc.length}$`;
117
- escaped_acc.push([escape_token, match.replace('[', '').replace(']', '')]);
144
+ const escape_token = '$R' + escaped_count++ + '$';
145
+ escaped_acc.push([escape_token, match.slice(1, -1)]);
118
146
  return escape_token;
119
147
  });
120
148
  }
@@ -123,11 +151,17 @@ function format(val, spec, locale = DEFAULT_LOCALE, zone = DEFAULT_TZ) {
123
151
  return val.toISOString();
124
152
  const d = toZone(val, zone);
125
153
  for (let i = 0; i < spec_chain.length; i++) {
126
- const el = spec_chain[i];
127
- formatted_string = formatted_string.replace(el[1], el[2](d, locale));
154
+ const [token, formatter] = spec_chain[i];
155
+ let pos = formatted_string.indexOf(token);
156
+ while (pos !== -1) {
157
+ formatted_string = formatted_string.slice(0, pos) +
158
+ formatter(d, locale) +
159
+ formatted_string.slice(pos + token.length);
160
+ pos = formatted_string.indexOf(token, pos + token.length);
161
+ }
128
162
  }
129
- if (escaped_acc.length) {
130
- for (let i = 0; i < escaped_acc.length; i++) {
163
+ if (escaped_count) {
164
+ for (let i = 0; i < escaped_count; i++) {
131
165
  const escape_token = escaped_acc[i];
132
166
  formatted_string = formatted_string.replace(escape_token[0], escape_token[1]);
133
167
  }
package/date/index.d.ts CHANGED
@@ -2,6 +2,7 @@ import { addUTC } from './addUTC';
2
2
  import { diff } from './diff';
3
3
  import { endOfUTC } from './endOfUTC';
4
4
  import { format } from './format';
5
+ import { isDateFormat } from './isFormat';
5
6
  import { isDate } from './is';
6
7
  import { nowUnix } from './nowUnix';
7
8
  import { nowUnixMs } from './nowUnixMs';
@@ -9,4 +10,4 @@ import { setTimeUTC } from './setTimeUTC';
9
10
  import { startOfUTC } from './startOfUTC';
10
11
  import { toUnix } from './toUnix';
11
12
  import { toUTC } from './toUTC';
12
- export { addUTC, diff, endOfUTC, format, isDate, isDate as is, nowUnix, nowUnixMs, setTimeUTC, startOfUTC, toUnix, toUTC };
13
+ export { addUTC, diff, endOfUTC, format, isDateFormat as isFormat, isDateFormat, isDate, isDate as is, nowUnix, nowUnixMs, setTimeUTC, startOfUTC, toUnix, toUTC };
package/date/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toUTC = exports.toUnix = exports.startOfUTC = exports.setTimeUTC = exports.nowUnixMs = exports.nowUnix = exports.is = exports.isDate = exports.format = exports.endOfUTC = exports.diff = exports.addUTC = void 0;
3
+ exports.toUTC = exports.toUnix = exports.startOfUTC = exports.setTimeUTC = exports.nowUnixMs = exports.nowUnix = exports.is = exports.isDate = exports.isDateFormat = exports.isFormat = exports.format = exports.endOfUTC = exports.diff = exports.addUTC = void 0;
4
4
  const addUTC_1 = require("./addUTC");
5
5
  Object.defineProperty(exports, "addUTC", { enumerable: true, get: function () { return addUTC_1.addUTC; } });
6
6
  const diff_1 = require("./diff");
@@ -9,6 +9,9 @@ const endOfUTC_1 = require("./endOfUTC");
9
9
  Object.defineProperty(exports, "endOfUTC", { enumerable: true, get: function () { return endOfUTC_1.endOfUTC; } });
10
10
  const format_1 = require("./format");
11
11
  Object.defineProperty(exports, "format", { enumerable: true, get: function () { return format_1.format; } });
12
+ const isFormat_1 = require("./isFormat");
13
+ Object.defineProperty(exports, "isFormat", { enumerable: true, get: function () { return isFormat_1.isDateFormat; } });
14
+ Object.defineProperty(exports, "isDateFormat", { enumerable: true, get: function () { return isFormat_1.isDateFormat; } });
12
15
  const is_1 = require("./is");
13
16
  Object.defineProperty(exports, "isDate", { enumerable: true, get: function () { return is_1.isDate; } });
14
17
  Object.defineProperty(exports, "is", { enumerable: true, get: function () { return is_1.isDate; } });
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Checks if a given string is in a particular format
3
+ * Eg:
4
+ * isDateFormat('2024-02-09', 'YYYY-MM-DD'); // true
5
+ * isDateFormat('2024-02-09T14:30', 'YYYY-MM-DD'); // false
6
+ *
7
+ * @param {unknown} input - String to format (eg: '2024-08-01')
8
+ * @param {string} spec - Spec to validate (Eg: 'YYYY-MM-DD')
9
+ * @returns {boolean} Whether or not the input is valid according to the spec
10
+ */
11
+ declare function isDateFormat(input: unknown, spec: string): input is string;
12
+ export { isDateFormat, isDateFormat as default };
@@ -0,0 +1,110 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.isDateFormat = isDateFormat;
4
+ exports.default = isDateFormat;
5
+ const SPECIAL_CHARS = /[.*+?^${}()|[\]\\]/g;
6
+ const TOKENS = [
7
+ ['YYYY', /\d{4}/.source, (raw, context) => {
8
+ context.year = parseInt(raw, 10);
9
+ return context.year > 0;
10
+ }],
11
+ ['MM', /(?:0[1-9]|1[0-2])/.source, (raw, context) => {
12
+ context.month = parseInt(raw, 10);
13
+ return context.month >= 1 && context.month <= 12;
14
+ }],
15
+ ['DD', /(?:0[1-9]|[12][0-9]|3[01])/.source, (raw, context) => {
16
+ context.day = parseInt(raw, 10);
17
+ return context.day >= 1 && context.day <= 31;
18
+ }],
19
+ ['HH', /(?:[01][0-9]|2[0-3])/.source, (raw, context) => {
20
+ context.hour = parseInt(raw, 10);
21
+ return context.hour >= 0 && context.hour <= 23;
22
+ }],
23
+ ['mm', /[0-5][0-9]/.source, () => true],
24
+ ['ss', /[0-5][0-9]/.source, () => true],
25
+ ['SSS', /\d{3}/.source, () => true],
26
+ ['Q', /[1-4]/.source, () => true],
27
+ ['A', /(?:AM|PM)/.source, (raw, context) => {
28
+ context.is12 = 1;
29
+ return raw === 'AM' || raw === 'PM';
30
+ }],
31
+ ['a', /(?:am|pm)/.source, (raw, context) => {
32
+ context.is12 = 1;
33
+ return raw === 'am' || raw === 'pm';
34
+ }],
35
+ ['Z', /Z|[+-](?:0[0-9]|1[0-4]):[0-5][0-9]/.source, raw => {
36
+ if (raw === 'Z')
37
+ return true;
38
+ let hour = parseInt(raw[1] + raw[2], 10);
39
+ if (raw[0] === '-')
40
+ hour = -hour;
41
+ const minutes = parseInt(raw[4] + raw[5], 10);
42
+ if (hour === 14 || hour === -12)
43
+ return minutes === 0;
44
+ return hour >= -11 && hour < 14 && [0, 15, 30, 45].indexOf(minutes) >= 0;
45
+ }],
46
+ ];
47
+ const SPEC_ALIASES = {
48
+ ISO: 'YYYY-MM-DDTHH:mm:ss.SSSZ',
49
+ };
50
+ const spec_pat_cache = {};
51
+ function compileSpec(spec) {
52
+ if (spec in spec_pat_cache)
53
+ return spec_pat_cache[spec];
54
+ const tokens = [];
55
+ let pat = '';
56
+ let cursor = 0;
57
+ while (cursor < spec.length) {
58
+ if (spec[cursor] === '[') {
59
+ const end_idx = spec.indexOf(']', cursor);
60
+ if (end_idx === -1)
61
+ throw new Error('isDateFormat: Unmatched [ in format string');
62
+ pat += spec.slice(cursor + 1, end_idx).replace(SPECIAL_CHARS, '\\$&');
63
+ cursor = end_idx + 1;
64
+ }
65
+ else {
66
+ const token_idx = TOKENS.findIndex(([token_key]) => spec.startsWith(token_key, cursor));
67
+ if (token_idx >= 0) {
68
+ const [token_key, token_rgx] = TOKENS[token_idx];
69
+ pat += '(' + token_rgx + ')';
70
+ tokens.push(token_idx);
71
+ cursor += token_key.length;
72
+ }
73
+ else {
74
+ pat += spec[cursor].replace(SPECIAL_CHARS, '\\$&');
75
+ cursor++;
76
+ }
77
+ }
78
+ }
79
+ spec_pat_cache[spec] = { rgx: RegExp('^' + pat + '$'), tokens };
80
+ return spec_pat_cache[spec];
81
+ }
82
+ function isDateFormat(input, spec) {
83
+ if (typeof input !== 'string' || input.trim().length === 0) {
84
+ throw new TypeError('isDateFormat: input must be a non-empty string');
85
+ }
86
+ if (typeof spec !== 'string') {
87
+ throw new TypeError('isDateFormat: spec must be a string');
88
+ }
89
+ const { tokens, rgx } = compileSpec(SPEC_ALIASES[spec] || spec);
90
+ if (!tokens.length)
91
+ return false;
92
+ const patMatch = rgx.exec(input);
93
+ if (!patMatch)
94
+ return false;
95
+ const matches = patMatch.slice(1);
96
+ const context = {};
97
+ for (let i = 0; i < matches.length; i++) {
98
+ if (!TOKENS[tokens[i]][2](matches[i], context))
99
+ return false;
100
+ }
101
+ const { is12, day, month, year } = context;
102
+ if (day && month) {
103
+ const date = new Date(year || 2024, month - 1, day);
104
+ if (date.getDate() !== day || date.getMonth() !== month - 1)
105
+ return false;
106
+ }
107
+ if (is12 && 'hour' in context && context.hour > 11)
108
+ return false;
109
+ return true;
110
+ }
package/hash/fnv1A.js CHANGED
@@ -28,11 +28,11 @@ function fnv1A(data, offset = FNV_32) {
28
28
  if (data === null) {
29
29
  sanitized = REPL_NULL;
30
30
  }
31
- else if (Array.isArray(data) || data.toString() === '[object Object]') {
31
+ else if (Array.isArray(data) || Object.prototype.toString.call(data) === '[object Object]') {
32
32
  sanitized = JSON.stringify(data);
33
33
  }
34
34
  else if (data instanceof RegExp) {
35
- sanitized = data.toString();
35
+ sanitized = String(data);
36
36
  }
37
37
  else if (data instanceof Date) {
38
38
  sanitized = String(data.getTime());
package/index.d.ts CHANGED
@@ -147,6 +147,10 @@ declare module "date/format" {
147
147
  function format(val: Date, spec: string, locale?: string, zone?: string): string;
148
148
  export { format, format as default };
149
149
  }
150
+ declare module "date/isFormat" {
151
+ function isDateFormat(input: unknown, spec: string): input is string;
152
+ export { isDateFormat, isDateFormat as default };
153
+ }
150
154
  declare module "date/nowUnix" {
151
155
  function nowUnix(): number;
152
156
  export { nowUnix, nowUnix as default };
@@ -186,6 +190,7 @@ declare module "date/index" {
186
190
  import { diff } from "date/diff";
187
191
  import { endOfUTC } from "date/endOfUTC";
188
192
  import { format } from "date/format";
193
+ import { isDateFormat } from "date/isFormat";
189
194
  import { isDate } from "date/is";
190
195
  import { nowUnix } from "date/nowUnix";
191
196
  import { nowUnixMs } from "date/nowUnixMs";
@@ -193,7 +198,7 @@ declare module "date/index" {
193
198
  import { startOfUTC } from "date/startOfUTC";
194
199
  import { toUnix } from "date/toUnix";
195
200
  import { toUTC } from "date/toUTC";
196
- export { addUTC, diff, endOfUTC, format, isDate, isDate as is, nowUnix, nowUnixMs, setTimeUTC, startOfUTC, toUnix, toUTC };
201
+ export { addUTC, diff, endOfUTC, format, isDateFormat as isFormat, isDateFormat, isDate, isDate as is, nowUnix, nowUnixMs, setTimeUTC, startOfUTC, toUnix, toUTC };
197
202
  }
198
203
  declare module "formdata/is" {
199
204
  function isFormData(val: unknown): val is FormData;
package/object/pick.js CHANGED
@@ -18,7 +18,7 @@ function pick(obj, keys) {
18
18
  sanitized = key.trim();
19
19
  if (!sanitized.length)
20
20
  continue;
21
- if (sanitized.includes('.')) {
21
+ if (sanitized.indexOf('.') >= 0) {
22
22
  val = (0, get_1.deepGet)(obj, sanitized);
23
23
  if (val === undefined)
24
24
  continue;
@@ -27,9 +27,8 @@ function pick(obj, keys) {
27
27
  let cursor = map;
28
28
  for (let y = 0; y < parts_len - 1; y++) {
29
29
  const part = parts[y].trim();
30
- if (!cursor[part]) {
30
+ if (!cursor[part])
31
31
  cursor[part] = {};
32
- }
33
32
  cursor = cursor[part];
34
33
  }
35
34
  cursor[parts[parts_len - 1].trim()] = val;
package/package.json CHANGED
@@ -1 +1 @@
1
- { "name": "@valkyriestudios/utils", "version": "12.19.0", "description": "A collection of single-function utilities for common tasks", "author": { "name": "Peter Vermeulen", "url": "https://www.linkedin.com/in/petervermeulen1/" }, "keywords": [ "utility", "library", "javascript", "js", "node", "bun" ], "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/ValkyrieStudios/utils.git" }, "bugs": { "url": "https://github.com/ValkyrieStudios/utils/issues" }, "homepage": "https://github.com/ValkyrieStudios/utils#readme", "types": "index.d.ts" }
1
+ { "name": "@valkyriestudios/utils", "version": "12.20.0", "description": "A collection of single-function utilities for common tasks", "author": { "name": "Peter Vermeulen", "url": "https://www.linkedin.com/in/petervermeulen1/" }, "keywords": [ "utility", "library", "javascript", "js", "node", "bun" ], "license": "MIT", "repository": { "type": "git", "url": "git+https://github.com/ValkyrieStudios/utils.git" }, "bugs": { "url": "https://github.com/ValkyrieStudios/utils/issues" }, "homepage": "https://github.com/ValkyrieStudios/utils#readme", "types": "index.d.ts" }
@@ -3,21 +3,15 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.humanizeBytes = humanizeBytes;
4
4
  exports.default = humanizeBytes;
5
5
  const humanizeNumber_1 = require("./humanizeNumber");
6
- const isIntegerAboveOrEqual_1 = require("../number/isIntegerAboveOrEqual");
6
+ const DEFAULT_UNITS = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB'];
7
7
  function humanizeBytes(val, options = {}) {
8
8
  return (0, humanizeNumber_1.humanizeNumber)(val, {
9
- delim: typeof options?.delim === 'string'
10
- ? options.delim
11
- : ',',
12
- separator: typeof options?.separator === 'string' && options.separator.trim().length
13
- ? options.separator
14
- : '.',
15
- precision: (0, isIntegerAboveOrEqual_1.isIntegerAboveOrEqual)(options?.precision, 0)
16
- ? options.precision
17
- : 2,
9
+ delim: 'delim' in options ? options.delim : ',',
10
+ separator: 'separator' in options ? options.separator : '.',
11
+ precision: 'precision' in options ? options.precision : 2,
18
12
  units: Array.isArray(options?.units) && options.units.length
19
13
  ? options.units
20
- : [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB'],
14
+ : DEFAULT_UNITS,
21
15
  divider: 1024,
22
16
  real: true,
23
17
  });
@@ -16,31 +16,29 @@ function humanizeNumber(val, options = {}) {
16
16
  : options?.units === false
17
17
  ? false
18
18
  : DEFAULT_UNITS;
19
- let normalized = 0;
20
- if (typeof val === 'string') {
21
- normalized = REAL ? parseInt(val.trim(), 10) : parseFloat(val);
22
- }
23
- else if (Number.isFinite(val)) {
24
- normalized = REAL ? Math.round(val) : val;
25
- }
19
+ let normalized = typeof val === 'string' ? parseFloat(val.trim()) : val;
26
20
  if (!Number.isFinite(normalized) || normalized === 0)
27
21
  return UNITS ? `0${UNITS[0]}` : '0';
22
+ if (REAL)
23
+ normalized = Math.round(normalized);
28
24
  const sign = normalized < 0 ? '-' : '';
29
25
  normalized = Math.abs(normalized);
30
26
  let unit_ix = 0;
31
27
  if (UNITS) {
32
- for (unit_ix; normalized >= DIVIDER && unit_ix < UNITS.length - 1; unit_ix++) {
28
+ while (normalized >= DIVIDER && unit_ix < UNITS.length - 1) {
33
29
  normalized /= DIVIDER;
30
+ unit_ix++;
34
31
  }
35
32
  }
36
33
  const humanized = `${(0, round_1.round)(normalized, PRECISION)}`.split('.', 2);
37
- const integerPart = humanized[0];
34
+ const integer_part = humanized[0];
35
+ const integer_part_len = integer_part.length;
38
36
  let formattedIntegerPart = '';
39
- for (let i = 0; i < integerPart.length; i++) {
40
- if (i > 0 && (integerPart.length - i) % 3 === 0) {
37
+ for (let i = 0; i < integer_part_len; i++) {
38
+ if (i > 0 && (integer_part_len - i) % 3 === 0) {
41
39
  formattedIntegerPart += DELIM;
42
40
  }
43
- formattedIntegerPart += integerPart[i];
41
+ formattedIntegerPart += integer_part[i];
44
42
  }
45
- return `${sign}${formattedIntegerPart}${humanized[1] ? SEPARATOR + humanized[1] : ''}${UNITS ? UNITS[unit_ix] : ''}`;
43
+ return sign + formattedIntegerPart + (humanized[1] ? SEPARATOR + humanized[1] : '') + (UNITS ? UNITS[unit_ix] : '');
46
44
  }
package/string/shorten.js CHANGED
@@ -17,8 +17,5 @@ function shorten(val, length, postfix = '...', truncate_words = true) {
17
17
  while (end > 0 && sanitized[end] !== ' ' && sanitized[end - 1] !== ' ') {
18
18
  end--;
19
19
  }
20
- if (end === 0) {
21
- return sanitized.substring(0, length) + postfix;
22
- }
23
20
  return sanitized.substring(0, end).trim() + postfix;
24
21
  }