@naturalcycles/js-lib 14.86.0 → 14.89.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.
@@ -0,0 +1,71 @@
1
+ const array = [];
2
+ const characterCodeCache = [];
3
+ /* eslint-disable unicorn/prefer-code-point, no-bitwise */
4
+ /**
5
+ * Modified version of: https://github.com/sindresorhus/leven/
6
+ *
7
+ * Returns a Levenshtein distance between first and second word.
8
+ */
9
+ export function _leven(first, second) {
10
+ if (first === second) {
11
+ return 0;
12
+ }
13
+ const swap = first;
14
+ // Swapping the strings if `a` is longer than `b` so we know which one is the
15
+ // shortest & which one is the longest
16
+ if (first.length > second.length) {
17
+ first = second;
18
+ second = swap;
19
+ }
20
+ let firstLength = first.length;
21
+ let secondLength = second.length;
22
+ // Performing suffix trimming:
23
+ // We can linearly drop suffix common to both strings since they
24
+ // don't increase distance at all
25
+ // Note: `~-` is the bitwise way to perform a `- 1` operation
26
+ while (firstLength > 0 && first.charCodeAt(~-firstLength) === second.charCodeAt(~-secondLength)) {
27
+ firstLength--;
28
+ secondLength--;
29
+ }
30
+ // Performing prefix trimming
31
+ // We can linearly drop prefix common to both strings since they
32
+ // don't increase distance at all
33
+ let start = 0;
34
+ while (start < firstLength && first.charCodeAt(start) === second.charCodeAt(start)) {
35
+ start++;
36
+ }
37
+ firstLength -= start;
38
+ secondLength -= start;
39
+ if (firstLength === 0) {
40
+ return secondLength;
41
+ }
42
+ let bCharacterCode;
43
+ let result;
44
+ let temporary;
45
+ let temporary2;
46
+ let index = 0;
47
+ let index2 = 0;
48
+ while (index < firstLength) {
49
+ characterCodeCache[index] = first.charCodeAt(start + index);
50
+ array[index] = ++index;
51
+ }
52
+ while (index2 < secondLength) {
53
+ bCharacterCode = second.charCodeAt(start + index2);
54
+ temporary = index2++;
55
+ result = index2;
56
+ for (index = 0; index < firstLength; index++) {
57
+ temporary2 = bCharacterCode === characterCodeCache[index] ? temporary : temporary + 1;
58
+ temporary = array[index];
59
+ // eslint-disable-next-line no-multi-assign
60
+ result = array[index] =
61
+ temporary > result
62
+ ? temporary2 > result
63
+ ? result + 1
64
+ : temporary2
65
+ : temporary2 > temporary
66
+ ? temporary + 1
67
+ : temporary2;
68
+ }
69
+ }
70
+ return result;
71
+ }
@@ -138,15 +138,15 @@ is.array = (value, assertion) => {
138
138
  }
139
139
  return value.every(assertion);
140
140
  };
141
- is.buffer = (value) => { var _a, _b, _c, _d; return (_d = (_c = (_b = (_a = value) === null || _a === void 0 ? void 0 : _a.constructor) === null || _b === void 0 ? void 0 : _b.isBuffer) === null || _c === void 0 ? void 0 : _c.call(_b, value)) !== null && _d !== void 0 ? _d : false; };
141
+ is.buffer = (value) => { var _a, _b, _c; return (_c = (_b = (_a = value === null || value === void 0 ? void 0 : value.constructor) === null || _a === void 0 ? void 0 : _a.isBuffer) === null || _b === void 0 ? void 0 : _b.call(_a, value)) !== null && _c !== void 0 ? _c : false; };
142
142
  is.nullOrUndefined = (value) => is.null_(value) || is.undefined(value);
143
143
  is.object = (value) => !is.null_(value) && (typeof value === 'object' || is.function_(value));
144
- is.iterable = (value) => { var _a; return is.function_((_a = value) === null || _a === void 0 ? void 0 : _a[Symbol.iterator]); };
145
- is.asyncIterable = (value) => { var _a; return is.function_((_a = value) === null || _a === void 0 ? void 0 : _a[Symbol.asyncIterator]); };
144
+ is.iterable = (value) => is.function_(value === null || value === void 0 ? void 0 : value[Symbol.iterator]);
145
+ is.asyncIterable = (value) => is.function_(value === null || value === void 0 ? void 0 : value[Symbol.asyncIterator]);
146
146
  is.generator = (value) => is.iterable(value) && is.function_(value.next) && is.function_(value.throw);
147
147
  is.asyncGenerator = (value) => is.asyncIterable(value) && is.function_(value.next) && is.function_(value.throw);
148
148
  is.nativePromise = (value) => isObjectOfType('Promise')(value);
149
- const hasPromiseAPI = (value) => { var _a, _b; return is.function_((_a = value) === null || _a === void 0 ? void 0 : _a.then) && is.function_((_b = value) === null || _b === void 0 ? void 0 : _b.catch); };
149
+ const hasPromiseAPI = (value) => is.function_(value === null || value === void 0 ? void 0 : value.then) && is.function_(value === null || value === void 0 ? void 0 : value.catch);
150
150
  is.promise = (value) => is.nativePromise(value) || hasPromiseAPI(value);
151
151
  is.generatorFunction = isObjectOfType('GeneratorFunction');
152
152
  is.asyncGeneratorFunction = (value) => getObjectType(value) === 'AsyncGeneratorFunction';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.86.0",
3
+ "version": "14.89.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -14,6 +14,7 @@
14
14
  "@naturalcycles/bench-lib": "^1.5.0",
15
15
  "@naturalcycles/dev-lib": "^12.0.0",
16
16
  "@naturalcycles/nodejs-lib": "^12.33.4",
17
+ "@naturalcycles/time-lib": "^3.5.1",
17
18
  "@types/node": "^17.0.4",
18
19
  "jest": "^27.0.1",
19
20
  "patch-package": "^6.2.1",
@@ -0,0 +1,73 @@
1
+ import { LocalDateUnit, LocalDate, LocalDateConfig } from '../datetime/localDate'
2
+
3
+ export class LazyLocalDate {
4
+ constructor(private str: string) {}
5
+
6
+ private ld?: LocalDate
7
+
8
+ eq(d: LocalDateConfig): boolean {
9
+ if (typeof d === 'string') return d === this.str
10
+ this.ld ||= LocalDate.of(this.str)
11
+ return this.ld.isSame(d)
12
+ }
13
+
14
+ lt(d: LocalDateConfig): boolean {
15
+ return this.cmp(d) === -1
16
+ }
17
+
18
+ lte(d: LocalDateConfig): boolean {
19
+ return this.cmp(d) <= 0
20
+ }
21
+
22
+ gt(d: LocalDateConfig): boolean {
23
+ return this.cmp(d) === 1
24
+ }
25
+
26
+ gte(d: LocalDateConfig): boolean {
27
+ return this.cmp(d) >= 0
28
+ }
29
+
30
+ cmp(d: LocalDateConfig): -1 | 0 | 1 {
31
+ if (typeof d === 'string') {
32
+ return this.str < d ? -1 : this.str > d ? 1 : 0
33
+ }
34
+
35
+ this.ld ||= LocalDate.of(this.str)
36
+ return this.ld.cmp(d)
37
+ }
38
+
39
+ absDiff(d: LocalDateConfig, unit: LocalDateUnit): number {
40
+ return Math.abs(this.diff(d, unit))
41
+ }
42
+
43
+ diff(d: LocalDateConfig, unit: LocalDateUnit): number {
44
+ this.ld ||= LocalDate.of(this.str)
45
+ return this.ld.diff(d, unit)
46
+ }
47
+
48
+ add(num: number, unit: LocalDateUnit): LocalDate {
49
+ this.ld ||= LocalDate.of(this.str)
50
+ return this.ld.add(num, unit)
51
+ }
52
+
53
+ subtract(num: number, unit: LocalDateUnit): LocalDate {
54
+ return this.add(-num, unit)
55
+ }
56
+
57
+ clone(): LazyLocalDate {
58
+ return new LazyLocalDate(this.str)
59
+ }
60
+
61
+ toDate(): Date {
62
+ this.ld ||= LocalDate.of(this.str)
63
+ return this.ld.toDate()
64
+ }
65
+
66
+ toString(): string {
67
+ return this.str
68
+ }
69
+
70
+ toJSON(): string {
71
+ return this.str
72
+ }
73
+ }
@@ -0,0 +1,355 @@
1
+ import { _assert } from '../error/assert'
2
+ import { Sequence } from '../seq/seq'
3
+ import { END, IsoDate } from '../types'
4
+
5
+ export type LocalDateUnit = 'year' | 'month' | 'day'
6
+
7
+ const m31 = new Set<number>([1, 3, 5, 7, 8, 10, 12])
8
+
9
+ export type LocalDateConfig = LocalDate | string
10
+
11
+ /**
12
+ * @experimental
13
+ */
14
+ export class LocalDate {
15
+ private constructor(public year: number, public month: number, public day: number) {}
16
+
17
+ static create(year: number, month: number, day: number): LocalDate {
18
+ return new LocalDate(year, month, day)
19
+ }
20
+
21
+ /**
22
+ * Parses input String into LocalDate.
23
+ * Input can already be a LocalDate - it is returned as-is in that case.
24
+ */
25
+ static of(d: LocalDateConfig): LocalDate {
26
+ if (d instanceof LocalDate) return d
27
+
28
+ const [year, month, day] = d.slice(0, 10).split('-').map(Number)
29
+
30
+ if (!day || !month || (!year && year !== 0)) {
31
+ throw new Error(`Cannot parse "${d}" into LocalDate`)
32
+ }
33
+
34
+ return new LocalDate(year, month, day)
35
+ }
36
+
37
+ static parseCompact(d: string): LocalDate {
38
+ const [year, month, day] = [d.slice(0, 4), d.slice(4, 2), d.slice(6, 2)].map(Number)
39
+
40
+ if (!day || !month || (!year && year !== 0)) {
41
+ throw new Error(`Cannot parse "${d}" into LocalDate`)
42
+ }
43
+
44
+ return new LocalDate(year, month, day)
45
+ }
46
+
47
+ static fromDate(d: Date): LocalDate {
48
+ return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
49
+ }
50
+
51
+ static today(): LocalDate {
52
+ return this.fromDate(new Date())
53
+ }
54
+
55
+ static sort(items: LocalDate[], mutate = false, descending = false): LocalDate[] {
56
+ const mod = descending ? -1 : 1
57
+ return (mutate ? items : [...items]).sort((a, b) => a.cmp(b) * mod)
58
+ }
59
+
60
+ static earliestOrUndefined(items: LocalDate[]): LocalDate | undefined {
61
+ return items.length ? LocalDate.earliest(items) : undefined
62
+ }
63
+
64
+ static earliest(items: LocalDate[]): LocalDate {
65
+ _assert(items.length, 'LocalDate.earliest called on empty array')
66
+
67
+ return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
68
+ }
69
+
70
+ static latestOrUndefined(items: LocalDate[]): LocalDate | undefined {
71
+ return items.length ? LocalDate.latest(items) : undefined
72
+ }
73
+
74
+ static latest(items: LocalDate[]): LocalDate {
75
+ _assert(items.length, 'LocalDate.latest called on empty array')
76
+
77
+ return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
78
+ }
79
+
80
+ static range(
81
+ minIncl: LocalDateConfig,
82
+ maxExcl: LocalDateConfig,
83
+ step = 1,
84
+ stepUnit: LocalDateUnit = 'day',
85
+ ): LocalDate[] {
86
+ const days: LocalDate[] = []
87
+ let current = LocalDate.of(minIncl).startOf(stepUnit)
88
+ const max = LocalDate.of(maxExcl).startOf(stepUnit)
89
+
90
+ do {
91
+ days.push(current)
92
+ current = current.add(step, stepUnit)
93
+ } while (current.isBefore(max))
94
+
95
+ return days
96
+ }
97
+
98
+ static rangeSeq(
99
+ minIncl: LocalDateConfig,
100
+ maxExcl: LocalDateConfig,
101
+ step = 1,
102
+ stepUnit: LocalDateUnit = 'day',
103
+ ): Sequence<LocalDate> {
104
+ const min = LocalDate.of(minIncl).startOf(stepUnit)
105
+ const max = LocalDate.of(maxExcl).startOf(stepUnit)
106
+ return Sequence.create(min, d => {
107
+ const next = d.add(step, stepUnit)
108
+ return next.isAfter(max) ? END : next
109
+ })
110
+ }
111
+
112
+ static rangeString(
113
+ minIncl: LocalDateConfig,
114
+ maxExcl: LocalDateConfig,
115
+ step = 1,
116
+ stepUnit: LocalDateUnit = 'day',
117
+ ): IsoDate[] {
118
+ return LocalDate.range(minIncl, maxExcl, step, stepUnit).map(ld => ld.toString())
119
+ }
120
+
121
+ static rangeIncl(
122
+ minIncl: LocalDateConfig,
123
+ maxIncl: LocalDateConfig,
124
+ step = 1,
125
+ stepUnit: LocalDateUnit = 'day',
126
+ ): LocalDate[] {
127
+ return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit)
128
+ }
129
+
130
+ static rangeInclString(
131
+ minIncl: LocalDateConfig,
132
+ maxIncl: LocalDateConfig,
133
+ step = 1,
134
+ stepUnit: LocalDateUnit = 'day',
135
+ ): IsoDate[] {
136
+ return LocalDate.range(minIncl, LocalDate.of(maxIncl).add(1, stepUnit), step, stepUnit).map(
137
+ ld => ld.toString(),
138
+ )
139
+ }
140
+
141
+ isSame(d: LocalDateConfig): boolean {
142
+ d = LocalDate.of(d)
143
+ return this.day === d.day && this.month === d.month && this.year === d.year
144
+ }
145
+
146
+ isBefore(d: LocalDateConfig): boolean {
147
+ return this.cmp(d) === -1
148
+ }
149
+
150
+ isSameOrBefore(d: LocalDateConfig): boolean {
151
+ return this.cmp(d) <= 0
152
+ }
153
+
154
+ isAfter(d: LocalDateConfig): boolean {
155
+ return this.cmp(d) === 1
156
+ }
157
+
158
+ isSameOrAfter(d: LocalDateConfig): boolean {
159
+ return this.cmp(d) >= 0
160
+ }
161
+
162
+ /**
163
+ * Returns 1 if this > d
164
+ * returns 0 if they are equal
165
+ * returns -1 if this < d
166
+ */
167
+ cmp(d: LocalDateConfig): -1 | 0 | 1 {
168
+ d = LocalDate.of(d)
169
+ if (this.year < d.year) return -1
170
+ if (this.year > d.year) return 1
171
+ if (this.month < d.month) return -1
172
+ if (this.month > d.month) return 1
173
+ if (this.day < d.day) return -1
174
+ if (this.day > d.day) return 1
175
+ return 0
176
+ }
177
+
178
+ /**
179
+ * Same as Math.abs( diff )
180
+ */
181
+ absDiff(d: LocalDateConfig, unit: LocalDateUnit): number {
182
+ return Math.abs(this.diff(d, unit))
183
+ }
184
+
185
+ /**
186
+ * Returns the number of **full** units difference (aka `Math.ceil`).
187
+ *
188
+ * a.diff(b) means "a minus b"
189
+ */
190
+ diff(d: LocalDateConfig, unit: LocalDateUnit): number {
191
+ d = LocalDate.of(d)
192
+
193
+ if (unit === 'year') {
194
+ return this.year - d.year
195
+ }
196
+
197
+ if (unit === 'month') {
198
+ return (this.year - d.year) * 12 + (this.month - d.month)
199
+ }
200
+
201
+ // unit is 'day'
202
+ let days = this.day - d.day
203
+
204
+ if (d.year < this.year) {
205
+ for (let year = d.year; year < this.year; year++) {
206
+ days += this.getYearDays(year)
207
+ }
208
+ } else if (this.year < d.year) {
209
+ for (let year = this.year; year < d.year; year++) {
210
+ days -= this.getYearDays(year)
211
+ }
212
+ }
213
+
214
+ if (d.month < this.month) {
215
+ for (let month = d.month; month < this.month; month++) {
216
+ days += this.getMonthLen(this.year, month)
217
+ }
218
+ } else if (this.month < d.month) {
219
+ for (let month = this.month; month < d.month; month++) {
220
+ days -= this.getMonthLen(d.year, month)
221
+ }
222
+ }
223
+
224
+ return days
225
+ }
226
+
227
+ add(num: number, unit: LocalDateUnit, mutate = false): LocalDate {
228
+ let { day, month, year } = this
229
+
230
+ if (unit === 'day') {
231
+ day += num
232
+ } else if (unit === 'month') {
233
+ month += num
234
+ } else if (unit === 'year') {
235
+ year += num
236
+ }
237
+
238
+ // check day overflow
239
+ let monLen = this.getMonthLen(year, month)
240
+ while (day > monLen) {
241
+ day -= monLen
242
+ month += 1
243
+ if (month > 12) {
244
+ year += 1
245
+ month -= 12
246
+ }
247
+
248
+ monLen = this.getMonthLen(year, month)
249
+ }
250
+ while (day < 1) {
251
+ day += monLen
252
+ month -= 1
253
+ if (month < 1) {
254
+ year -= 1
255
+ month += 12
256
+ }
257
+
258
+ monLen = this.getMonthLen(year, month)
259
+ }
260
+
261
+ // check month overflow
262
+ while (month > 12) {
263
+ year += 1
264
+ month -= 12
265
+ }
266
+ while (month < 1) {
267
+ year -= 1
268
+ month += 12
269
+ }
270
+
271
+ if (mutate) {
272
+ this.year = year
273
+ this.month = month
274
+ this.day = day
275
+ return this
276
+ }
277
+
278
+ return new LocalDate(year, month, day)
279
+ }
280
+
281
+ subtract(num: number, unit: LocalDateUnit, mutate = false): LocalDate {
282
+ return this.add(-num, unit, mutate)
283
+ }
284
+
285
+ startOf(unit: LocalDateUnit): LocalDate {
286
+ if (unit === 'day') return this
287
+ if (unit === 'month') return LocalDate.create(this.year, this.month, 1)
288
+ // year
289
+ return LocalDate.create(this.year, 1, 1)
290
+ }
291
+
292
+ endOf(unit: LocalDateUnit): LocalDate {
293
+ if (unit === 'day') return this
294
+ if (unit === 'month')
295
+ return LocalDate.create(this.year, this.month, this.getMonthLen(this.year, this.month))
296
+ // year
297
+ return LocalDate.create(this.year, 12, 31)
298
+ }
299
+
300
+ private getYearDays(year: number): number {
301
+ return this.isLeapYear(year) ? 366 : 365
302
+ }
303
+
304
+ private getMonthLen(year: number, month: number): number {
305
+ if (month === 2) return this.isLeapYear(year) ? 29 : 28
306
+ return m31.has(month) ? 31 : 30
307
+ }
308
+
309
+ private isLeapYear(year: number): boolean {
310
+ if (year % 4 !== 0) return false
311
+ if (year % 100 !== 0) return true
312
+ return year % 400 === 0
313
+ }
314
+
315
+ clone(): LocalDate {
316
+ return new LocalDate(this.year, this.month, this.day)
317
+ }
318
+
319
+ /**
320
+ * Converts LocalDate into instance of Date.
321
+ * Year, month and day will match.
322
+ * Hour, minute, second, ms will be 0.
323
+ * Timezone will match local timezone.
324
+ */
325
+ toDate(): Date {
326
+ return new Date(this.year, this.month - 1, this.day)
327
+ }
328
+
329
+ toString(): IsoDate {
330
+ return [
331
+ String(this.year).padStart(4, '0'),
332
+ String(this.month).padStart(2, '0'),
333
+ String(this.day).padStart(2, '0'),
334
+ ].join('-')
335
+ }
336
+
337
+ toStringCompact(): string {
338
+ return [
339
+ String(this.year).padStart(4, '0'),
340
+ String(this.month).padStart(2, '0'),
341
+ String(this.day).padStart(2, '0'),
342
+ ].join('')
343
+ }
344
+
345
+ toJSON(): IsoDate {
346
+ return this.toString()
347
+ }
348
+ }
349
+
350
+ /**
351
+ * Shortcut wrapper around `LocalDate.parse` / `LocalDate.today`
352
+ */
353
+ export function localDate(d?: LocalDateConfig): LocalDate {
354
+ return d ? LocalDate.of(d) : LocalDate.today()
355
+ }