@naturalcycles/js-lib 14.98.3 → 14.99.2

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.
@@ -1,14 +1,29 @@
1
1
  import { _assert } from '../error/assert';
2
2
  import { _ms } from '../time/time.util';
3
3
  import { LocalDate } from './localDate';
4
+ export var ISODayOfWeek;
5
+ (function (ISODayOfWeek) {
6
+ ISODayOfWeek[ISODayOfWeek["MONDAY"] = 1] = "MONDAY";
7
+ ISODayOfWeek[ISODayOfWeek["TUESDAY"] = 2] = "TUESDAY";
8
+ ISODayOfWeek[ISODayOfWeek["WEDNESDAY"] = 3] = "WEDNESDAY";
9
+ ISODayOfWeek[ISODayOfWeek["THURSDAY"] = 4] = "THURSDAY";
10
+ ISODayOfWeek[ISODayOfWeek["FRIDAY"] = 5] = "FRIDAY";
11
+ ISODayOfWeek[ISODayOfWeek["SATURDAY"] = 6] = "SATURDAY";
12
+ ISODayOfWeek[ISODayOfWeek["SUNDAY"] = 7] = "SUNDAY";
13
+ })(ISODayOfWeek || (ISODayOfWeek = {}));
14
+ const weekStartsOn = 1; // mon, as per ISO
15
+ const MILLISECONDS_IN_WEEK = 604800000;
16
+ const SECONDS_IN_DAY = 86400;
17
+ // const MILLISECONDS_IN_DAY = 86400000
18
+ // const MILLISECONDS_IN_MINUTE = 60000
19
+ const VALID_DAYS_OF_WEEK = new Set([1, 2, 3, 4, 5, 6, 7]);
4
20
  /* eslint-disable no-dupe-class-members */
5
21
  /**
6
22
  * @experimental
7
23
  */
8
24
  export class LocalTime {
9
- constructor($date, utcMode) {
25
+ constructor($date) {
10
26
  this.$date = $date;
11
- this.utcMode = utcMode;
12
27
  }
13
28
  /**
14
29
  * Parses input String into LocalDate.
@@ -21,14 +36,6 @@ export class LocalTime {
21
36
  }
22
37
  return t;
23
38
  }
24
- utc() {
25
- this.utcMode = true;
26
- return this;
27
- }
28
- local() {
29
- this.utcMode = false;
30
- return this;
31
- }
32
39
  /**
33
40
  * Returns null if invalid
34
41
  */
@@ -55,7 +62,7 @@ export class LocalTime {
55
62
  // if (utc) {
56
63
  // date.setMinutes(date.getMinutes() + date.getTimezoneOffset())
57
64
  // }
58
- return new LocalTime(date, false);
65
+ return new LocalTime(date);
59
66
  }
60
67
  static parseToDate(d) {
61
68
  if (d instanceof LocalTime)
@@ -83,52 +90,56 @@ export class LocalTime {
83
90
  return this.parseOrNull(d) !== null;
84
91
  }
85
92
  static now() {
86
- return new LocalTime(new Date(), false);
93
+ return new LocalTime(new Date());
87
94
  }
88
95
  static fromComponents(c) {
89
- return new LocalTime(new Date(c.year, c.month - 1, c.day, c.hour, c.minute, c.second), false);
96
+ return new LocalTime(new Date(c.year, c.month - 1, c.day, c.hour, c.minute, c.second));
90
97
  }
91
98
  get(unit) {
92
99
  if (unit === 'year') {
93
- return this.utcMode ? this.$date.getUTCFullYear() : this.$date.getFullYear();
100
+ return this.$date.getFullYear();
94
101
  }
95
102
  if (unit === 'month') {
96
- return (this.utcMode ? this.$date.getUTCMonth() : this.$date.getMonth()) + 1;
103
+ return this.$date.getMonth() + 1;
97
104
  }
98
105
  if (unit === 'day') {
99
- return this.utcMode ? this.$date.getUTCDate() : this.$date.getDate();
106
+ return this.$date.getDate();
100
107
  }
101
108
  if (unit === 'hour') {
102
- return this.utcMode ? this.$date.getUTCHours() : this.$date.getHours();
109
+ return this.$date.getHours();
103
110
  }
104
111
  if (unit === 'minute') {
105
- return this.utcMode ? this.$date.getUTCMinutes() : this.$date.getMinutes();
112
+ return this.$date.getMinutes();
113
+ }
114
+ if (unit === 'week') {
115
+ return getWeek(this.$date);
106
116
  }
107
117
  // second
108
- return this.utcMode ? this.$date.getUTCSeconds() : this.$date.getSeconds();
118
+ return this.$date.getSeconds();
109
119
  }
110
120
  set(unit, v, mutate = false) {
111
121
  const t = mutate ? this : this.clone();
112
- /* eslint-disable @typescript-eslint/no-unused-expressions */
113
122
  if (unit === 'year') {
114
- this.utcMode ? t.$date.setUTCFullYear(v) : t.$date.setFullYear(v);
123
+ t.$date.setFullYear(v);
115
124
  }
116
125
  else if (unit === 'month') {
117
- this.utcMode ? t.$date.setUTCMonth(v - 1) : t.$date.setMonth(v - 1);
126
+ t.$date.setMonth(v - 1);
118
127
  }
119
128
  else if (unit === 'day') {
120
- this.utcMode ? t.$date.setUTCDate(v) : t.$date.setDate(v);
129
+ t.$date.setDate(v);
121
130
  }
122
131
  else if (unit === 'hour') {
123
- this.utcMode ? t.$date.setUTCHours(v) : t.$date.setHours(v);
132
+ t.$date.setHours(v);
124
133
  }
125
134
  else if (unit === 'minute') {
126
- this.utcMode ? t.$date.setUTCMinutes(v) : t.$date.setMinutes(v);
135
+ t.$date.setMinutes(v);
127
136
  }
128
137
  else if (unit === 'second') {
129
- this.utcMode ? t.$date.setUTCSeconds(v) : t.$date.setSeconds(v);
138
+ t.$date.setSeconds(v);
139
+ }
140
+ else if (unit === 'week') {
141
+ setWeek(t.$date, v, true);
130
142
  }
131
- /* eslint-enable @typescript-eslint/no-unused-expressions */
132
143
  return t;
133
144
  }
134
145
  year(v) {
@@ -137,9 +148,21 @@ export class LocalTime {
137
148
  month(v) {
138
149
  return v === undefined ? this.get('month') : this.set('month', v);
139
150
  }
151
+ week(v) {
152
+ return v === undefined ? getWeek(this.$date) : this.set('week', v);
153
+ }
140
154
  day(v) {
141
155
  return v === undefined ? this.get('day') : this.set('day', v);
142
156
  }
157
+ dayOfWeek(v) {
158
+ const dow = (this.$date.getDay() || 7);
159
+ if (v === undefined) {
160
+ return dow;
161
+ }
162
+ if (!VALID_DAYS_OF_WEEK.has(v))
163
+ throw new Error(`Invalid dayOfWeek: ${v}`);
164
+ return this.add(v - dow, 'day');
165
+ }
143
166
  hour(v) {
144
167
  return v === undefined ? this.get('hour') : this.set('hour', v);
145
168
  }
@@ -151,29 +174,31 @@ export class LocalTime {
151
174
  }
152
175
  setComponents(c, mutate = false) {
153
176
  const d = mutate ? this.$date : new Date(this.$date);
154
- /* eslint-disable @typescript-eslint/no-unused-expressions */
155
177
  if (c.year) {
156
- this.utcMode ? d.setUTCFullYear(c.year) : d.setFullYear(c.year);
178
+ d.setFullYear(c.year);
157
179
  }
158
180
  if (c.month) {
159
- this.utcMode ? d.setUTCMonth(c.month - 1) : d.setMonth(c.month - 1);
181
+ d.setMonth(c.month - 1);
160
182
  }
161
183
  if (c.day) {
162
- this.utcMode ? d.setUTCDate(c.day) : d.setDate(c.day);
184
+ d.setDate(c.day);
163
185
  }
164
186
  if (c.hour !== undefined) {
165
- this.utcMode ? d.setUTCHours(c.hour) : d.setHours(c.hour);
187
+ d.setHours(c.hour);
166
188
  }
167
189
  if (c.minute !== undefined) {
168
- this.utcMode ? d.setUTCMinutes(c.minute) : d.setMinutes(c.minute);
190
+ d.setMinutes(c.minute);
169
191
  }
170
192
  if (c.second !== undefined) {
171
- this.utcMode ? d.setUTCSeconds(c.second) : d.setSeconds(c.second);
193
+ d.setSeconds(c.second);
172
194
  }
173
- /* eslint-enable @typescript-eslint/no-unused-expressions */
174
- return mutate ? this : new LocalTime(d, this.utcMode);
195
+ return mutate ? this : new LocalTime(d);
175
196
  }
176
197
  add(num, unit, mutate = false) {
198
+ if (unit === 'week') {
199
+ num *= 7;
200
+ unit = 'day';
201
+ }
177
202
  return this.set(unit, this.get(unit) + num, mutate);
178
203
  }
179
204
  subtract(num, unit, mutate = false) {
@@ -184,21 +209,43 @@ export class LocalTime {
184
209
  }
185
210
  diff(other, unit) {
186
211
  const date2 = LocalTime.parseToDate(other);
187
- if (unit === 'year') {
188
- return this.$date.getFullYear() - date2.getFullYear();
189
- }
190
- if (unit === 'month') {
191
- return ((this.$date.getFullYear() - date2.getFullYear()) * 12 +
192
- this.$date.getMonth() -
193
- date2.getMonth());
194
- }
195
212
  const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000;
213
+ if (!secDiff)
214
+ return 0;
215
+ if (unit === 'year' || unit === 'month') {
216
+ const sign = secDiff > 0 ? 1 : -1;
217
+ // Put items in descending order: "big minus small"
218
+ const [big, small] = sign === 1 ? [this.$date, date2] : [date2, this.$date];
219
+ if (unit === 'year') {
220
+ let years = big.getFullYear() - small.getFullYear();
221
+ const big2 = new Date(big);
222
+ const small2 = new Date(small);
223
+ big2.setFullYear(1584);
224
+ small2.setFullYear(1584);
225
+ if (big2 < small2)
226
+ years--;
227
+ return years * sign || 0;
228
+ }
229
+ if (unit === 'month') {
230
+ let months = (big.getFullYear() - small.getFullYear()) * 12 + big.getMonth() - small.getMonth();
231
+ const big2 = new Date(big);
232
+ const small2 = new Date(small);
233
+ big2.setFullYear(1584, 0);
234
+ small2.setFullYear(1584, 0);
235
+ if (big2 < small2)
236
+ months--;
237
+ return months * sign || 0;
238
+ }
239
+ }
196
240
  let r;
197
241
  if (unit === 'day') {
198
- r = secDiff / (24 * 60 * 60);
242
+ r = secDiff / SECONDS_IN_DAY;
243
+ }
244
+ else if (unit === 'week') {
245
+ r = secDiff / (7 * 24 * 60 * 60);
199
246
  }
200
247
  else if (unit === 'hour') {
201
- r = secDiff / (60 * 60);
248
+ r = secDiff / 3600;
202
249
  }
203
250
  else if (unit === 'minute') {
204
251
  r = secDiff / 60;
@@ -207,32 +254,62 @@ export class LocalTime {
207
254
  // unit === 'second'
208
255
  r = secDiff;
209
256
  }
210
- r = r < 0 ? -Math.floor(-r) : Math.floor(r);
211
- if (Object.is(r, -0))
212
- return 0;
213
- return r;
257
+ // `|| 0` is to avoid returning -0
258
+ return Math.trunc(r) || 0;
214
259
  }
215
260
  startOf(unit, mutate = false) {
216
261
  if (unit === 'second')
217
262
  return this;
218
263
  const d = mutate ? this.$date : new Date(this.$date);
219
- d.setMilliseconds(0);
220
- d.setSeconds(0);
221
- /* eslint-disable @typescript-eslint/no-unused-expressions */
264
+ d.setSeconds(0, 0);
222
265
  if (unit !== 'minute') {
223
- this.utcMode ? d.setUTCMinutes(0) : d.setMinutes(0);
266
+ d.setMinutes(0);
224
267
  if (unit !== 'hour') {
225
- this.utcMode ? d.setUTCHours(0) : d.setHours(0);
268
+ d.setHours(0);
226
269
  if (unit !== 'day') {
227
- this.utcMode ? d.setUTCDate(0) : d.setDate(0);
228
- if (unit !== 'month') {
229
- this.utcMode ? d.setUTCMonth(0) : d.setMonth(0);
270
+ // year, month or week
271
+ if (unit === 'year') {
272
+ d.setMonth(0);
273
+ d.setDate(1);
274
+ }
275
+ else if (unit === 'month') {
276
+ d.setDate(1);
277
+ }
278
+ else {
279
+ // week
280
+ startOfWeek(d, true);
230
281
  }
231
282
  }
232
283
  }
233
284
  }
234
- /* eslint-enable @typescript-eslint/no-unused-expressions */
235
- return mutate ? this : new LocalTime(d, this.utcMode);
285
+ return mutate ? this : new LocalTime(d);
286
+ }
287
+ endOf(unit, mutate = false) {
288
+ if (unit === 'second')
289
+ return this;
290
+ const d = mutate ? this.$date : new Date(this.$date);
291
+ d.setSeconds(59, 0);
292
+ if (unit !== 'minute') {
293
+ d.setMinutes(59);
294
+ if (unit !== 'hour') {
295
+ d.setHours(23);
296
+ if (unit !== 'day') {
297
+ // year, month or week
298
+ if (unit === 'year') {
299
+ d.setMonth(11);
300
+ }
301
+ if (unit === 'week') {
302
+ endOfWeek(d, true);
303
+ }
304
+ else {
305
+ // year or month
306
+ const lastDay = LocalDate.getMonthLength(d.getFullYear(), d.getMonth() + 1);
307
+ d.setDate(lastDay);
308
+ }
309
+ }
310
+ }
311
+ }
312
+ return mutate ? this : new LocalTime(d);
236
313
  }
237
314
  static sort(items, mutate = false, descending = false) {
238
315
  const mod = descending ? -1 : 1;
@@ -249,14 +326,18 @@ export class LocalTime {
249
326
  }
250
327
  static earliest(items) {
251
328
  _assert(items.length, 'LocalTime.earliest called on empty array');
252
- return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item));
329
+ return items
330
+ .map(i => LocalTime.of(i))
331
+ .reduce((min, item) => (min.isSameOrBefore(item) ? min : item));
253
332
  }
254
333
  static latestOrUndefined(items) {
255
334
  return items.length ? LocalTime.latest(items) : undefined;
256
335
  }
257
336
  static latest(items) {
258
337
  _assert(items.length, 'LocalTime.latest called on empty array');
259
- return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
338
+ return items
339
+ .map(i => LocalTime.of(i))
340
+ .reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
260
341
  }
261
342
  isSame(d) {
262
343
  return this.cmp(d) === 0;
@@ -296,18 +377,7 @@ export class LocalTime {
296
377
  return 0;
297
378
  return t1 < t2 ? -1 : 1;
298
379
  }
299
- // todo: endOf
300
380
  components() {
301
- if (this.utcMode) {
302
- return {
303
- year: this.$date.getUTCFullYear(),
304
- month: this.$date.getUTCMonth() + 1,
305
- day: this.$date.getUTCDate(),
306
- hour: this.$date.getUTCHours(),
307
- minute: this.$date.getUTCMinutes(),
308
- second: this.$date.getSeconds(),
309
- };
310
- }
311
381
  return {
312
382
  year: this.$date.getFullYear(),
313
383
  month: this.$date.getMonth() + 1,
@@ -330,7 +400,7 @@ export class LocalTime {
330
400
  return this.$date;
331
401
  }
332
402
  clone() {
333
- return new LocalTime(new Date(this.$date), this.utcMode);
403
+ return new LocalTime(new Date(this.$date));
334
404
  }
335
405
  unix() {
336
406
  return Math.floor(this.$date.valueOf() / 1000);
@@ -342,9 +412,6 @@ export class LocalTime {
342
412
  return Math.floor(this.$date.valueOf() / 1000);
343
413
  }
344
414
  toLocalDate() {
345
- if (this.utcMode) {
346
- return LocalDate.create(this.$date.getUTCFullYear(), this.$date.getUTCMonth() + 1, this.$date.getUTCDate());
347
- }
348
415
  return LocalDate.create(this.$date.getFullYear(), this.$date.getMonth() + 1, this.$date.getDate());
349
416
  }
350
417
  toPretty(seconds = true) {
@@ -421,6 +488,9 @@ export class LocalTime {
421
488
  toJSON() {
422
489
  return this.unix();
423
490
  }
491
+ format(fmt) {
492
+ return fmt(this);
493
+ }
424
494
  }
425
495
  /**
426
496
  * Shortcut wrapper around `LocalDate.parse` / `LocalDate.today`
@@ -428,3 +498,83 @@ export class LocalTime {
428
498
  export function localTime(d) {
429
499
  return d ? LocalTime.of(d) : LocalTime.now();
430
500
  }
501
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/getISOWeek/index.ts
502
+ function getWeek(date) {
503
+ const diff = startOfWeek(date).getTime() - startOfWeekYear(date).getTime();
504
+ return Math.round(diff / MILLISECONDS_IN_WEEK) + 1;
505
+ }
506
+ function setWeek(date, week, mutate = false) {
507
+ const d = mutate ? date : new Date(date);
508
+ const diff = getWeek(d) - week;
509
+ d.setDate(d.getDate() - diff * 7);
510
+ return d;
511
+ }
512
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/startOfISOWeekYear/index.ts
513
+ function startOfWeekYear(date) {
514
+ const year = getWeekYear(date);
515
+ const fourthOfJanuary = new Date(0);
516
+ fourthOfJanuary.setFullYear(year, 0, 4);
517
+ fourthOfJanuary.setHours(0, 0, 0, 0);
518
+ return startOfWeek(fourthOfJanuary, true);
519
+ }
520
+ // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/getISOWeekYear/index.ts
521
+ function getWeekYear(date) {
522
+ const year = date.getFullYear();
523
+ const fourthOfJanuaryOfNextYear = new Date(0);
524
+ fourthOfJanuaryOfNextYear.setFullYear(year + 1, 0, 4);
525
+ fourthOfJanuaryOfNextYear.setHours(0, 0, 0, 0);
526
+ const startOfNextYear = startOfWeek(fourthOfJanuaryOfNextYear, true);
527
+ const fourthOfJanuaryOfThisYear = new Date(0);
528
+ fourthOfJanuaryOfThisYear.setFullYear(year, 0, 4);
529
+ fourthOfJanuaryOfThisYear.setHours(0, 0, 0, 0);
530
+ const startOfThisYear = startOfWeek(fourthOfJanuaryOfThisYear, true);
531
+ if (date.getTime() >= startOfNextYear.getTime()) {
532
+ return year + 1;
533
+ }
534
+ else if (date.getTime() >= startOfThisYear.getTime()) {
535
+ return year;
536
+ }
537
+ else {
538
+ return year - 1;
539
+ }
540
+ }
541
+ // function setWeekYear(
542
+ // date: Date,
543
+ // year: number,
544
+ // ): Date {
545
+ // const diff = differenceInCalendarDays(date, startOfWeekYear(date))
546
+ // const fourthOfJanuary = new Date(0)
547
+ // fourthOfJanuary.setFullYear(year, 0, 4)
548
+ // fourthOfJanuary.setHours(0, 0, 0, 0)
549
+ // date = startOfWeekYear(fourthOfJanuary)
550
+ // date.setDate(date.getDate() + diff)
551
+ // return date
552
+ // }
553
+ // function differenceInCalendarDays(
554
+ // dateLeft: Date,
555
+ // dateRight: Date,
556
+ // ): number {
557
+ // return Math.round((startOfDay(dateLeft).getTime() - startOfDay(dateRight).getTime()) / MILLISECONDS_IN_DAY)
558
+ // }
559
+ // function startOfDay(date: Date, mutate = false): Date {
560
+ // const d = mutate ? date : new Date(date)
561
+ // d.setHours(0, 0, 0, 0)
562
+ // return d
563
+ // }
564
+ // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/startOfWeek/index.ts
565
+ function startOfWeek(date, mutate = false) {
566
+ const d = mutate ? date : new Date(date);
567
+ const day = d.getDay();
568
+ const diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn;
569
+ d.setDate(d.getDate() - diff);
570
+ d.setHours(0, 0, 0, 0);
571
+ return d;
572
+ }
573
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/endOfWeek/index.ts
574
+ function endOfWeek(date, mutate = false) {
575
+ const d = mutate ? date : new Date(date);
576
+ const day = d.getDay();
577
+ const diff = (day < weekStartsOn ? -7 : 0) + 6 - (day - weekStartsOn);
578
+ d.setDate(d.getDate() + diff);
579
+ return d;
580
+ }
package/dist-esm/index.js CHANGED
@@ -60,4 +60,5 @@ export * from './datetime/localDate';
60
60
  export * from './datetime/localTime';
61
61
  export * from './datetime/dateInterval';
62
62
  export * from './datetime/timeInterval';
63
+ import { ISODayOfWeek, } from './datetime/localTime';
63
64
  export { is, _createPromiseDecorator, _stringMapValues, _stringMapEntries, _objectKeys, pMap, _passthroughMapper, _passUndefinedMapper, _passthroughPredicate, _passNothingPredicate, _noop, ErrorMode, pDefer, AggregatedError, pRetry, pRetryFn, pTimeout, pTimeoutFn, _tryCatch, _TryCatch, _stringifyAny, jsonSchema, JsonSchemaAnyBuilder, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, commonLoggerCreate, PQueue, END, SKIP, };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.98.3",
3
+ "version": "14.99.2",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -272,9 +272,38 @@ export function _shuffle<T>(array: T[], mutate = false): T[] {
272
272
  return a
273
273
  }
274
274
 
275
+ /**
276
+ * Returns last item of non-empty array.
277
+ * Throws if array is empty.
278
+ */
279
+ export function _last<T>(array: T[]): T {
280
+ if (!array.length) throw new Error('_last called on empty array')
281
+ return array[array.length - 1]!
282
+ }
283
+
275
284
  /**
276
285
  * Returns last item of the array (or undefined if array is empty).
277
286
  */
278
- export function _last<T>(array: T[]): T | undefined {
287
+ export function _lastOrUndefined<T>(array: T[]): T | undefined {
279
288
  return array[array.length - 1]
280
289
  }
290
+
291
+ export function _minOrUndefined<T>(array: T[]): T | undefined {
292
+ if (!array.length) return
293
+ return _min(array)
294
+ }
295
+
296
+ export function _min<T>(array: T[]): T {
297
+ if (!array.length) throw new Error('_min called on empty array')
298
+ return array.reduce((min, item) => (min <= item ? min : item))
299
+ }
300
+
301
+ export function _maxOrUndefined<T>(array: T[]): T | undefined {
302
+ if (!array.length) return
303
+ return _max(array)
304
+ }
305
+
306
+ export function _max<T>(array: T[]): T {
307
+ if (!array.length) throw new Error('_max called on empty array')
308
+ return array.reduce((max, item) => (max >= item ? max : item))
309
+ }