@naturalcycles/js-lib 14.91.2 → 14.94.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,76 @@
1
+ import { LocalDate } from './localDate';
2
+ /**
3
+ * Class that supports ISO8601 "Time interval" standard that looks like `2022-02-24/2022-03-30`.
4
+ *
5
+ * @experimental
6
+ */
7
+ export class DateInterval {
8
+ constructor(start, end) {
9
+ this.start = start;
10
+ this.end = end;
11
+ }
12
+ static of(start, end) {
13
+ return new DateInterval(LocalDate.of(start), LocalDate.of(end));
14
+ }
15
+ /**
16
+ * Parses string like `2022-02-24/2023-03-30` into a DateInterval.
17
+ */
18
+ static parse(d) {
19
+ if (d instanceof DateInterval)
20
+ return d;
21
+ const [start, end] = d.split('/');
22
+ if (!end || !start) {
23
+ throw new Error(`Cannot parse "${d}" into DateInterval`);
24
+ }
25
+ return new DateInterval(LocalDate.of(start), LocalDate.of(end));
26
+ }
27
+ isSame(d) {
28
+ return this.cmp(d) === 0;
29
+ }
30
+ isBefore(d) {
31
+ return this.cmp(d) === -1;
32
+ }
33
+ isSameOrBefore(d) {
34
+ return this.cmp(d) <= 0;
35
+ }
36
+ isAfter(d) {
37
+ return this.cmp(d) === 1;
38
+ }
39
+ isSameOrAfter(d) {
40
+ return this.cmp(d) >= 0;
41
+ }
42
+ /**
43
+ * Ranges of DateInterval (start, end) are INCLUSIVE.
44
+ */
45
+ includes(d) {
46
+ d = LocalDate.of(d);
47
+ return d.isSameOrAfter(this.start) && d.isSameOrBefore(this.end);
48
+ }
49
+ /**
50
+ * DateIntervals compare by start date.
51
+ * If it's the same - then by end date.
52
+ */
53
+ cmp(d) {
54
+ d = DateInterval.parse(d);
55
+ return this.start.cmp(d.start) || this.end.cmp(d.end);
56
+ }
57
+ /**
58
+ * Returns an array of LocalDates that are included in the interval.
59
+ * Ranges are INCLUSIVE.
60
+ */
61
+ getDays() {
62
+ const days = [];
63
+ let current = this.start;
64
+ do {
65
+ days.push(current);
66
+ current = current.add(1, 'day');
67
+ } while (current.isSameOrBefore(this.end));
68
+ return days;
69
+ }
70
+ toString() {
71
+ return [this.start, this.end].join('/');
72
+ }
73
+ toJSON() {
74
+ return this.toString();
75
+ }
76
+ }
@@ -36,6 +36,9 @@ export class LocalDate {
36
36
  static fromDate(d) {
37
37
  return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate());
38
38
  }
39
+ static fromDateUTC(d) {
40
+ return new LocalDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate());
41
+ }
39
42
  /**
40
43
  * Returns null if invalid.
41
44
  */
@@ -61,6 +64,9 @@ export class LocalDate {
61
64
  static today() {
62
65
  return this.fromDate(new Date());
63
66
  }
67
+ static todayUTC() {
68
+ return this.fromDateUTC(new Date());
69
+ }
64
70
  static sort(items, mutate = false, descending = false) {
65
71
  const mod = descending ? -1 : 1;
66
72
  return (mutate ? items : [...items]).sort((a, b) => a.cmp(b) * mod);
@@ -122,6 +128,15 @@ export class LocalDate {
122
128
  isSameOrAfter(d) {
123
129
  return this.cmp(d) >= 0;
124
130
  }
131
+ isBetween(min, max, incl = '[)') {
132
+ let r = this.cmp(min);
133
+ if (r < 0 || (r === 0 && incl[0] === '('))
134
+ return false;
135
+ r = this.cmp(max);
136
+ if (r > 0 || (r === 0 && incl[1] === ')'))
137
+ return false;
138
+ return true;
139
+ }
125
140
  /**
126
141
  * Returns 1 if this > d
127
142
  * returns 0 if they are equal
@@ -1,4 +1,5 @@
1
1
  import { _assert } from '../error/assert';
2
+ import { _ms } from '../time/time.util';
2
3
  import { LocalDate } from './localDate';
3
4
  /* eslint-disable no-dupe-class-members */
4
5
  // Design choices:
@@ -10,12 +11,16 @@ import { LocalDate } from './localDate';
10
11
  // .valueOf returns unix timestamp (no millis)
11
12
  // Prevents dayjs(undefined) being dayjs.now()
12
13
  // Validates on parse, throws if invalid. Doesn't allow invalid objects
14
+ // No arbitrary .format('') (which is slow to parse) vs well-defined (opinionated) formats
15
+ // Separate LocalTime, powered by native js Date, and LocalDate - a smaller functionality class when
16
+ // you only need "whole dates", no time, no timezone worry
13
17
  /**
14
18
  * @experimental
15
19
  */
16
20
  export class LocalTime {
17
- constructor($date) {
21
+ constructor($date, utcMode) {
18
22
  this.$date = $date;
23
+ this.utcMode = utcMode;
19
24
  }
20
25
  /**
21
26
  * Parses input String into LocalDate.
@@ -28,6 +33,14 @@ export class LocalTime {
28
33
  }
29
34
  return t;
30
35
  }
36
+ utc() {
37
+ this.utcMode = true;
38
+ return this;
39
+ }
40
+ local() {
41
+ this.utcMode = false;
42
+ return this;
43
+ }
31
44
  /**
32
45
  * Returns null if invalid
33
46
  */
@@ -49,106 +62,110 @@ export class LocalTime {
49
62
  // throw new TypeError(`Cannot parse "${d}" into LocalTime`)
50
63
  return null;
51
64
  }
52
- return new LocalTime(date);
65
+ // if (utc) {
66
+ // date.setMinutes(date.getMinutes() + date.getTimezoneOffset())
67
+ // }
68
+ return new LocalTime(date, false);
53
69
  }
54
70
  static isValid(d) {
55
71
  return this.parseOrNull(d) !== null;
56
72
  }
57
- static unix(ts) {
58
- return new LocalTime(new Date(ts * 1000));
59
- }
60
73
  static now() {
61
- return this.of(new Date());
74
+ return new LocalTime(new Date(), false);
62
75
  }
63
76
  static fromComponents(c) {
64
- return new LocalTime(new Date(c.year, c.month - 1, c.day, c.hour, c.minute, c.second));
77
+ return new LocalTime(new Date(c.year, c.month - 1, c.day, c.hour, c.minute, c.second), false);
65
78
  }
66
79
  get(unit) {
67
80
  if (unit === 'year') {
68
- return this.$date.getFullYear();
81
+ return this.utcMode ? this.$date.getUTCFullYear() : this.$date.getFullYear();
69
82
  }
70
83
  if (unit === 'month') {
71
- return this.$date.getMonth() + 1;
84
+ return (this.utcMode ? this.$date.getUTCMonth() : this.$date.getMonth()) + 1;
72
85
  }
73
86
  if (unit === 'day') {
74
- return this.$date.getDate();
87
+ return this.utcMode ? this.$date.getUTCDate() : this.$date.getDate();
75
88
  }
76
89
  if (unit === 'hour') {
77
- return this.$date.getHours();
90
+ return this.utcMode ? this.$date.getUTCHours() : this.$date.getHours();
78
91
  }
79
92
  if (unit === 'minute') {
80
- return this.$date.getMinutes();
93
+ return this.utcMode ? this.$date.getUTCMinutes() : this.$date.getMinutes();
81
94
  }
82
95
  // second
83
- return this.$date.getSeconds();
96
+ return this.utcMode ? this.$date.getUTCSeconds() : this.$date.getSeconds();
84
97
  }
85
98
  set(unit, v, mutate = false) {
86
99
  const t = mutate ? this : this.clone();
100
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
87
101
  if (unit === 'year') {
88
- t.$date.setFullYear(v);
102
+ this.utcMode ? t.$date.setUTCFullYear(v) : t.$date.setFullYear(v);
89
103
  }
90
104
  else if (unit === 'month') {
91
- t.$date.setMonth(v - 1);
105
+ this.utcMode ? t.$date.setUTCMonth(v - 1) : t.$date.setMonth(v - 1);
92
106
  }
93
107
  else if (unit === 'day') {
94
- t.$date.setDate(v);
108
+ this.utcMode ? t.$date.setUTCDate(v) : t.$date.setDate(v);
95
109
  }
96
110
  else if (unit === 'hour') {
97
- t.$date.setHours(v);
111
+ this.utcMode ? t.$date.setUTCHours(v) : t.$date.setHours(v);
98
112
  }
99
113
  else if (unit === 'minute') {
100
- t.$date.setMinutes(v);
114
+ this.utcMode ? t.$date.setUTCMinutes(v) : t.$date.setMinutes(v);
101
115
  }
102
116
  else if (unit === 'second') {
103
- t.$date.setSeconds(v);
117
+ this.utcMode ? t.$date.setUTCSeconds(v) : t.$date.setSeconds(v);
104
118
  }
119
+ /* eslint-enable @typescript-eslint/no-unused-expressions */
105
120
  return t;
106
121
  }
107
122
  year(v) {
108
- return v === undefined ? this.$date.getFullYear() : this.set('year', v);
123
+ return v === undefined ? this.get('year') : this.set('year', v);
109
124
  }
110
125
  month(v) {
111
- return v === undefined ? this.$date.getMonth() + 1 : this.set('month', v);
126
+ return v === undefined ? this.get('month') : this.set('month', v);
112
127
  }
113
128
  date(v) {
114
- return v === undefined ? this.$date.getDate() : this.set('day', v);
129
+ return v === undefined ? this.get('day') : this.set('day', v);
115
130
  }
116
131
  hour(v) {
117
- return v === undefined ? this.$date.getHours() : this.set('hour', v);
132
+ return v === undefined ? this.get('hour') : this.set('hour', v);
118
133
  }
119
134
  minute(v) {
120
- return v === undefined ? this.$date.getMinutes() : this.set('minute', v);
135
+ return v === undefined ? this.get('minute') : this.set('minute', v);
121
136
  }
122
137
  second(v) {
123
- return v === undefined ? this.$date.getSeconds() : this.set('second', v);
138
+ return v === undefined ? this.get('second') : this.set('second', v);
124
139
  }
125
140
  setComponents(c, mutate = false) {
126
141
  const d = mutate ? this.$date : new Date(this.$date);
142
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
127
143
  if (c.year) {
128
- d.setFullYear(c.year);
144
+ this.utcMode ? d.setUTCFullYear(c.year) : d.setFullYear(c.year);
129
145
  }
130
146
  if (c.month) {
131
- d.setMonth(c.month - 1);
147
+ this.utcMode ? d.setUTCMonth(c.month - 1) : d.setMonth(c.month - 1);
132
148
  }
133
149
  if (c.day) {
134
- d.setDate(c.day);
150
+ this.utcMode ? d.setUTCDate(c.day) : d.setDate(c.day);
135
151
  }
136
152
  if (c.hour !== undefined) {
137
- d.setHours(c.hour);
153
+ this.utcMode ? d.setUTCHours(c.hour) : d.setHours(c.hour);
138
154
  }
139
155
  if (c.minute !== undefined) {
140
- d.setMinutes(c.minute);
156
+ this.utcMode ? d.setUTCMinutes(c.minute) : d.setMinutes(c.minute);
141
157
  }
142
158
  if (c.second !== undefined) {
143
- d.setSeconds(c.second);
159
+ this.utcMode ? d.setUTCSeconds(c.second) : d.setSeconds(c.second);
144
160
  }
145
- return mutate ? this : new LocalTime(d);
161
+ /* eslint-enable @typescript-eslint/no-unused-expressions */
162
+ return mutate ? this : new LocalTime(d, this.utcMode);
146
163
  }
147
164
  add(num, unit, mutate = false) {
148
165
  return this.set(unit, this.get(unit) + num, mutate);
149
166
  }
150
167
  subtract(num, unit, mutate = false) {
151
- return this.add(-num, unit, mutate);
168
+ return this.add(num * -1, unit, mutate);
152
169
  }
153
170
  absDiff(other, unit) {
154
171
  return Math.abs(this.diff(other, unit));
@@ -186,40 +203,23 @@ export class LocalTime {
186
203
  startOf(unit, mutate = false) {
187
204
  if (unit === 'second')
188
205
  return this;
189
- if (mutate) {
190
- const d = this.$date;
191
- d.setSeconds(0);
192
- if (unit === 'minute')
193
- return this;
194
- d.setMinutes(0);
195
- if (unit === 'hour')
196
- return this;
197
- d.setHours(0);
198
- if (unit === 'day')
199
- return this;
200
- d.setDate(0);
201
- if (unit === 'month')
202
- return this;
203
- d.setMonth(0);
204
- return this;
205
- }
206
- const c = this.components();
207
- c.second = 0;
208
- if (unit === 'year') {
209
- c.month = c.day = 1;
210
- c.hour = c.minute = 0;
211
- }
212
- else if (unit === 'month') {
213
- c.day = 1;
214
- c.hour = c.minute = 0;
215
- }
216
- else if (unit === 'day') {
217
- c.hour = c.minute = 0;
218
- }
219
- else if (unit === 'hour') {
220
- c.minute = 0;
221
- }
222
- return LocalTime.fromComponents(c);
206
+ const d = mutate ? this.$date : new Date(this.$date);
207
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
208
+ this.utcMode ? d.setUTCSeconds(0) : d.setSeconds(0);
209
+ if (unit !== 'minute') {
210
+ this.utcMode ? d.setUTCMinutes(0) : d.setMinutes(0);
211
+ if (unit !== 'hour') {
212
+ this.utcMode ? d.setUTCHours(0) : d.setHours(0);
213
+ if (unit !== 'day') {
214
+ this.utcMode ? d.setUTCDate(0) : d.setDate(0);
215
+ if (unit !== 'month') {
216
+ this.utcMode ? d.setUTCMonth(0) : d.setMonth(0);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ /* eslint-enable @typescript-eslint/no-unused-expressions */
222
+ return mutate ? this : new LocalTime(d, this.utcMode);
223
223
  }
224
224
  static sort(items, mutate = false, descending = false) {
225
225
  const mod = descending ? -1 : 1;
@@ -260,6 +260,15 @@ export class LocalTime {
260
260
  isSameOrAfter(d) {
261
261
  return this.cmp(d) >= 0;
262
262
  }
263
+ isBetween(min, max, incl = '[)') {
264
+ let r = this.cmp(min);
265
+ if (r < 0 || (r === 0 && incl[0] === '('))
266
+ return false;
267
+ r = this.cmp(max);
268
+ if (r > 0 || (r === 0 && incl[1] === ')'))
269
+ return false;
270
+ return true;
271
+ }
263
272
  /**
264
273
  * Returns 1 if this > d
265
274
  * returns 0 if they are equal
@@ -274,6 +283,16 @@ export class LocalTime {
274
283
  }
275
284
  // todo: endOf
276
285
  components() {
286
+ if (this.utcMode) {
287
+ return {
288
+ year: this.$date.getUTCFullYear(),
289
+ month: this.$date.getUTCMonth() + 1,
290
+ day: this.$date.getUTCDate(),
291
+ hour: this.$date.getUTCHours(),
292
+ minute: this.$date.getUTCMinutes(),
293
+ second: this.$date.getSeconds(),
294
+ };
295
+ }
277
296
  return {
278
297
  year: this.$date.getFullYear(),
279
298
  month: this.$date.getMonth() + 1,
@@ -283,11 +302,20 @@ export class LocalTime {
283
302
  second: this.$date.getSeconds(),
284
303
  };
285
304
  }
305
+ fromNow(now = LocalTime.now()) {
306
+ const msDiff = LocalTime.of(now).unixMillis() - this.unixMillis();
307
+ if (msDiff === 0)
308
+ return 'now';
309
+ if (msDiff >= 0) {
310
+ return `${_ms(msDiff)} ago`;
311
+ }
312
+ return `in ${_ms(msDiff * -1)}`;
313
+ }
286
314
  getDate() {
287
315
  return this.$date;
288
316
  }
289
317
  clone() {
290
- return new LocalTime(new Date(this.$date));
318
+ return new LocalTime(new Date(this.$date), this.utcMode);
291
319
  }
292
320
  unix() {
293
321
  return Math.floor(this.$date.valueOf() / 1000);
@@ -299,14 +327,31 @@ export class LocalTime {
299
327
  return Math.floor(this.$date.valueOf() / 1000);
300
328
  }
301
329
  toLocalDate() {
330
+ if (this.utcMode) {
331
+ return LocalDate.create(this.$date.getUTCFullYear(), this.$date.getUTCMonth() + 1, this.$date.getUTCDate());
332
+ }
302
333
  return LocalDate.create(this.$date.getFullYear(), this.$date.getMonth() + 1, this.$date.getDate());
303
334
  }
304
335
  toPretty(seconds = true) {
305
- return this.$date
306
- .toISOString()
307
- .slice(0, seconds ? 19 : 16)
308
- .split('T')
309
- .join(' ');
336
+ const { year, month, day, hour, minute, second } = this.components();
337
+ return ([
338
+ String(year).padStart(4, '0'),
339
+ String(month).padStart(2, '0'),
340
+ String(day).padStart(2, '0'),
341
+ ].join('-') +
342
+ ' ' +
343
+ [
344
+ String(hour).padStart(2, '0'),
345
+ String(minute).padStart(2, '0'),
346
+ seconds && String(second).padStart(2, '0'),
347
+ ]
348
+ .filter(Boolean)
349
+ .join(':'));
350
+ // return this.$date
351
+ // .toISOString()
352
+ // .slice(0, seconds ? 19 : 16)
353
+ // .split('T')
354
+ // .join(' ')
310
355
  }
311
356
  /**
312
357
  * Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
@@ -318,20 +363,41 @@ export class LocalTime {
318
363
  * Returns e.g: `1984-06-21`, only the date part of DateTime
319
364
  */
320
365
  toISODate() {
321
- return this.$date.toISOString().slice(0, 10);
366
+ const { year, month, day } = this.components();
367
+ return [
368
+ String(year).padStart(4, '0'),
369
+ String(month).padStart(2, '0'),
370
+ String(day).padStart(2, '0'),
371
+ ].join('-');
372
+ // return this.$date.toISOString().slice(0, 10)
373
+ }
374
+ /**
375
+ * Returns e.g: `17:03:15` (or `17:03` with seconds=false)
376
+ */
377
+ toISOTime(seconds = true) {
378
+ // return this.$date.toISOString().slice(11, seconds ? 19 : 16)
379
+ const { hour, minute, second } = this.components();
380
+ return [
381
+ String(hour).padStart(2, '0'),
382
+ String(minute).padStart(2, '0'),
383
+ seconds && String(second).padStart(2, '0'),
384
+ ]
385
+ .filter(Boolean)
386
+ .join(':');
322
387
  }
323
388
  /**
324
389
  * Returns e.g: `19840621_1705`
325
390
  */
326
391
  toStringCompact(seconds = false) {
392
+ const { year, month, day, hour, minute, second } = this.components();
327
393
  return [
328
- String(this.$date.getFullYear()).padStart(4, '0'),
329
- String(this.$date.getMonth() + 1).padStart(2, '0'),
330
- String(this.$date.getDate()).padStart(2, '0'),
394
+ String(year).padStart(4, '0'),
395
+ String(month).padStart(2, '0'),
396
+ String(day).padStart(2, '0'),
331
397
  '_',
332
- String(this.$date.getHours()).padStart(2, '0'),
333
- String(this.$date.getMinutes()).padStart(2, '0'),
334
- seconds ? String(this.$date.getSeconds()).padStart(2, '0') : '',
398
+ String(hour).padStart(2, '0'),
399
+ String(minute).padStart(2, '0'),
400
+ seconds ? String(second).padStart(2, '0') : '',
335
401
  ].join('');
336
402
  }
337
403
  toString() {
package/dist-esm/index.js CHANGED
@@ -59,4 +59,5 @@ export * from './math/stack.util';
59
59
  export * from './string/leven';
60
60
  export * from './datetime/localDate';
61
61
  export * from './datetime/localTime';
62
+ export * from './datetime/dateInterval';
62
63
  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.91.2",
3
+ "version": "14.94.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -0,0 +1,91 @@
1
+ import { LocalDate, LocalDateConfig } from './localDate'
2
+
3
+ export type DateIntervalConfig = DateInterval | string
4
+
5
+ /**
6
+ * Class that supports ISO8601 "Time interval" standard that looks like `2022-02-24/2022-03-30`.
7
+ *
8
+ * @experimental
9
+ */
10
+ export class DateInterval {
11
+ private constructor(public start: LocalDate, public end: LocalDate) {}
12
+
13
+ static of(start: LocalDateConfig, end: LocalDateConfig): DateInterval {
14
+ return new DateInterval(LocalDate.of(start), LocalDate.of(end))
15
+ }
16
+
17
+ /**
18
+ * Parses string like `2022-02-24/2023-03-30` into a DateInterval.
19
+ */
20
+ static parse(d: DateIntervalConfig): DateInterval {
21
+ if (d instanceof DateInterval) return d
22
+
23
+ const [start, end] = d.split('/')
24
+
25
+ if (!end || !start) {
26
+ throw new Error(`Cannot parse "${d}" into DateInterval`)
27
+ }
28
+
29
+ return new DateInterval(LocalDate.of(start), LocalDate.of(end))
30
+ }
31
+
32
+ isSame(d: DateIntervalConfig): boolean {
33
+ return this.cmp(d) === 0
34
+ }
35
+
36
+ isBefore(d: DateIntervalConfig): boolean {
37
+ return this.cmp(d) === -1
38
+ }
39
+
40
+ isSameOrBefore(d: DateIntervalConfig): boolean {
41
+ return this.cmp(d) <= 0
42
+ }
43
+
44
+ isAfter(d: DateIntervalConfig): boolean {
45
+ return this.cmp(d) === 1
46
+ }
47
+
48
+ isSameOrAfter(d: DateIntervalConfig): boolean {
49
+ return this.cmp(d) >= 0
50
+ }
51
+
52
+ /**
53
+ * Ranges of DateInterval (start, end) are INCLUSIVE.
54
+ */
55
+ includes(d: LocalDateConfig): boolean {
56
+ d = LocalDate.of(d)
57
+ return d.isSameOrAfter(this.start) && d.isSameOrBefore(this.end)
58
+ }
59
+
60
+ /**
61
+ * DateIntervals compare by start date.
62
+ * If it's the same - then by end date.
63
+ */
64
+ cmp(d: DateIntervalConfig): -1 | 0 | 1 {
65
+ d = DateInterval.parse(d)
66
+ return this.start.cmp(d.start) || this.end.cmp(d.end)
67
+ }
68
+
69
+ /**
70
+ * Returns an array of LocalDates that are included in the interval.
71
+ * Ranges are INCLUSIVE.
72
+ */
73
+ getDays(): LocalDate[] {
74
+ const days: LocalDate[] = []
75
+ let current = this.start
76
+ do {
77
+ days.push(current)
78
+ current = current.add(1, 'day')
79
+ } while (current.isSameOrBefore(this.end))
80
+
81
+ return days
82
+ }
83
+
84
+ toString(): string {
85
+ return [this.start, this.end].join('/')
86
+ }
87
+
88
+ toJSON(): string {
89
+ return this.toString()
90
+ }
91
+ }
@@ -4,6 +4,7 @@ import { END, IsoDate, UnixTimestamp } from '../types'
4
4
  import { LocalTime } from './localTime'
5
5
 
6
6
  export type LocalDateUnit = 'year' | 'month' | 'day'
7
+ export type Inclusiveness = '()' | '[]' | '[)' | '(]'
7
8
 
8
9
  const m31 = new Set<number>([1, 3, 5, 7, 8, 10, 12])
9
10
 
@@ -47,6 +48,10 @@ export class LocalDate {
47
48
  return new LocalDate(d.getFullYear(), d.getMonth() + 1, d.getDate())
48
49
  }
49
50
 
51
+ static fromDateUTC(d: Date): LocalDate {
52
+ return new LocalDate(d.getUTCFullYear(), d.getUTCMonth() + 1, d.getUTCDate())
53
+ }
54
+
50
55
  /**
51
56
  * Returns null if invalid.
52
57
  */
@@ -79,6 +84,10 @@ export class LocalDate {
79
84
  return this.fromDate(new Date())
80
85
  }
81
86
 
87
+ static todayUTC(): LocalDate {
88
+ return this.fromDateUTC(new Date())
89
+ }
90
+
82
91
  static sort(items: LocalDate[], mutate = false, descending = false): LocalDate[] {
83
92
  const mod = descending ? -1 : 1
84
93
  return (mutate ? items : [...items]).sort((a, b) => a.cmp(b) * mod)
@@ -186,6 +195,14 @@ export class LocalDate {
186
195
  return this.cmp(d) >= 0
187
196
  }
188
197
 
198
+ isBetween(min: LocalDateConfig, max: LocalDateConfig, incl: Inclusiveness = '[)'): boolean {
199
+ let r = this.cmp(min)
200
+ if (r < 0 || (r === 0 && incl[0] === '(')) return false
201
+ r = this.cmp(max)
202
+ if (r > 0 || (r === 0 && incl[1] === ')')) return false
203
+ return true
204
+ }
205
+
189
206
  /**
190
207
  * Returns 1 if this > d
191
208
  * returns 0 if they are equal