@naturalcycles/js-lib 14.98.1 → 14.99.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.
@@ -87,16 +87,24 @@ export class LocalDate {
87
87
  }
88
88
  static earliest(items) {
89
89
  _assert(items.length, 'LocalDate.earliest called on empty array');
90
- return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item));
90
+ return items
91
+ .map(i => LocalDate.of(i))
92
+ .reduce((min, item) => (min.isSameOrBefore(item) ? min : item));
91
93
  }
92
94
  static latestOrUndefined(items) {
93
95
  return items.length ? LocalDate.latest(items) : undefined;
94
96
  }
95
97
  static latest(items) {
96
98
  _assert(items.length, 'LocalDate.latest called on empty array');
97
- return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
99
+ return items
100
+ .map(i => LocalDate.of(i))
101
+ .reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
98
102
  }
99
103
  static range(min, max, incl = '[)', step = 1, stepUnit = 'day') {
104
+ if (stepUnit === 'week') {
105
+ step *= 7;
106
+ stepUnit = 'day';
107
+ }
100
108
  const dates = [];
101
109
  const $min = LocalDate.of(min);
102
110
  const $max = LocalDate.of(max).startOf(stepUnit);
@@ -206,7 +214,7 @@ export class LocalDate {
206
214
  if (unit === 'month') {
207
215
  return (this.$year - d.$year) * 12 + (this.$month - d.$month);
208
216
  }
209
- // unit is 'day'
217
+ // unit is 'day' or 'week'
210
218
  let days = this.$day - d.$day;
211
219
  if (d.$year < this.$year) {
212
220
  for (let year = d.$year; year < this.$year; year++) {
@@ -228,10 +236,17 @@ export class LocalDate {
228
236
  days -= LocalDate.getMonthLength(d.$year, month);
229
237
  }
230
238
  }
239
+ if (unit === 'week') {
240
+ return Math.floor(days / 7);
241
+ }
231
242
  return days;
232
243
  }
233
244
  add(num, unit, mutate = false) {
234
245
  let { $day, $month, $year } = this;
246
+ if (unit === 'week') {
247
+ num *= 7;
248
+ unit = 'day';
249
+ }
235
250
  if (unit === 'day') {
236
251
  $day += num;
237
252
  }
@@ -331,6 +346,12 @@ export class LocalDate {
331
346
  toISODate() {
332
347
  return this.toString();
333
348
  }
349
+ /**
350
+ * Returns e.g: `1984-06-21T17:56:21`
351
+ */
352
+ toISODateTime() {
353
+ return this.toString() + 'T00:00:00';
354
+ }
334
355
  toString() {
335
356
  return [
336
357
  String(this.$year).padStart(4, '0'),
@@ -355,6 +376,9 @@ export class LocalDate {
355
376
  toJSON() {
356
377
  return this.toString();
357
378
  }
379
+ format(fmt) {
380
+ return fmt(this);
381
+ }
358
382
  }
359
383
  /**
360
384
  * Shortcut wrapper around `LocalDate.parse` / `LocalDate.today`
@@ -1,6 +1,21 @@
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 MILLISECONDS_IN_DAY = 86400000
17
+ // const MILLISECONDS_IN_MINUTE = 60000
18
+ const VALID_DAYS_OF_WEEK = new Set([1, 2, 3, 4, 5, 6, 7]);
4
19
  /* eslint-disable no-dupe-class-members */
5
20
  /**
6
21
  * @experimental
@@ -104,6 +119,9 @@ export class LocalTime {
104
119
  if (unit === 'minute') {
105
120
  return this.utcMode ? this.$date.getUTCMinutes() : this.$date.getMinutes();
106
121
  }
122
+ if (unit === 'week') {
123
+ return getWeek(this.$date);
124
+ }
107
125
  // second
108
126
  return this.utcMode ? this.$date.getUTCSeconds() : this.$date.getSeconds();
109
127
  }
@@ -128,6 +146,9 @@ export class LocalTime {
128
146
  else if (unit === 'second') {
129
147
  this.utcMode ? t.$date.setUTCSeconds(v) : t.$date.setSeconds(v);
130
148
  }
149
+ else if (unit === 'week') {
150
+ setWeek(t.$date, v, true);
151
+ }
131
152
  /* eslint-enable @typescript-eslint/no-unused-expressions */
132
153
  return t;
133
154
  }
@@ -137,9 +158,21 @@ export class LocalTime {
137
158
  month(v) {
138
159
  return v === undefined ? this.get('month') : this.set('month', v);
139
160
  }
161
+ week(v) {
162
+ return v === undefined ? getWeek(this.$date) : this.set('week', v);
163
+ }
140
164
  day(v) {
141
165
  return v === undefined ? this.get('day') : this.set('day', v);
142
166
  }
167
+ dayOfWeek(v) {
168
+ const dow = (this.$date.getDay() || 7);
169
+ if (v === undefined) {
170
+ return dow;
171
+ }
172
+ if (!VALID_DAYS_OF_WEEK.has(v))
173
+ throw new Error(`Invalid dayOfWeek: ${v}`);
174
+ return this.add(v - dow, 'day');
175
+ }
143
176
  hour(v) {
144
177
  return v === undefined ? this.get('hour') : this.set('hour', v);
145
178
  }
@@ -174,6 +207,10 @@ export class LocalTime {
174
207
  return mutate ? this : new LocalTime(d, this.utcMode);
175
208
  }
176
209
  add(num, unit, mutate = false) {
210
+ if (unit === 'week') {
211
+ num *= 7;
212
+ unit = 'day';
213
+ }
177
214
  return this.set(unit, this.get(unit) + num, mutate);
178
215
  }
179
216
  subtract(num, unit, mutate = false) {
@@ -197,6 +234,9 @@ export class LocalTime {
197
234
  if (unit === 'day') {
198
235
  r = secDiff / (24 * 60 * 60);
199
236
  }
237
+ else if (unit === 'week') {
238
+ r = secDiff / (7 * 24 * 60 * 60);
239
+ }
200
240
  else if (unit === 'hour') {
201
241
  r = secDiff / (60 * 60);
202
242
  }
@@ -207,7 +247,7 @@ export class LocalTime {
207
247
  // unit === 'second'
208
248
  r = secDiff;
209
249
  }
210
- r = r < 0 ? -Math.floor(-r) : Math.floor(r);
250
+ r = Math.trunc(r);
211
251
  if (Object.is(r, -0))
212
252
  return 0;
213
253
  return r;
@@ -216,16 +256,53 @@ export class LocalTime {
216
256
  if (unit === 'second')
217
257
  return this;
218
258
  const d = mutate ? this.$date : new Date(this.$date);
259
+ d.setSeconds(0, 0);
219
260
  /* eslint-disable @typescript-eslint/no-unused-expressions */
220
- this.utcMode ? d.setUTCSeconds(0) : d.setSeconds(0);
221
261
  if (unit !== 'minute') {
222
262
  this.utcMode ? d.setUTCMinutes(0) : d.setMinutes(0);
223
263
  if (unit !== 'hour') {
224
264
  this.utcMode ? d.setUTCHours(0) : d.setHours(0);
225
265
  if (unit !== 'day') {
226
- this.utcMode ? d.setUTCDate(0) : d.setDate(0);
227
- if (unit !== 'month') {
266
+ // year, month or week
267
+ if (unit === 'year') {
228
268
  this.utcMode ? d.setUTCMonth(0) : d.setMonth(0);
269
+ this.utcMode ? d.setUTCDate(1) : d.setDate(1);
270
+ }
271
+ else if (unit === 'month') {
272
+ this.utcMode ? d.setUTCDate(1) : d.setDate(1);
273
+ }
274
+ else {
275
+ // week
276
+ startOfWeek(d, true);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ /* eslint-enable @typescript-eslint/no-unused-expressions */
282
+ return mutate ? this : new LocalTime(d, this.utcMode);
283
+ }
284
+ endOf(unit, mutate = false) {
285
+ if (unit === 'second')
286
+ return this;
287
+ const d = mutate ? this.$date : new Date(this.$date);
288
+ d.setSeconds(59, 0);
289
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
290
+ if (unit !== 'minute') {
291
+ this.utcMode ? d.setUTCMinutes(59) : d.setMinutes(59);
292
+ if (unit !== 'hour') {
293
+ this.utcMode ? d.setUTCHours(23) : d.setHours(23);
294
+ if (unit !== 'day') {
295
+ // year, month or week
296
+ if (unit === 'year') {
297
+ this.utcMode ? d.setUTCMonth(11) : d.setMonth(11);
298
+ }
299
+ if (unit === 'week') {
300
+ endOfWeek(d, true);
301
+ }
302
+ else {
303
+ // year or month
304
+ const lastDay = LocalDate.getMonthLength(d.getFullYear(), d.getMonth() + 1);
305
+ this.utcMode ? d.setUTCDate(lastDay) : d.setDate(lastDay);
229
306
  }
230
307
  }
231
308
  }
@@ -248,14 +325,18 @@ export class LocalTime {
248
325
  }
249
326
  static earliest(items) {
250
327
  _assert(items.length, 'LocalTime.earliest called on empty array');
251
- return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item));
328
+ return items
329
+ .map(i => LocalTime.of(i))
330
+ .reduce((min, item) => (min.isSameOrBefore(item) ? min : item));
252
331
  }
253
332
  static latestOrUndefined(items) {
254
333
  return items.length ? LocalTime.latest(items) : undefined;
255
334
  }
256
335
  static latest(items) {
257
336
  _assert(items.length, 'LocalTime.latest called on empty array');
258
- return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
337
+ return items
338
+ .map(i => LocalTime.of(i))
339
+ .reduce((max, item) => (max.isSameOrAfter(item) ? max : item));
259
340
  }
260
341
  isSame(d) {
261
342
  return this.cmp(d) === 0;
@@ -295,7 +376,6 @@ export class LocalTime {
295
376
  return 0;
296
377
  return t1 < t2 ? -1 : 1;
297
378
  }
298
- // todo: endOf
299
379
  components() {
300
380
  if (this.utcMode) {
301
381
  return {
@@ -368,7 +448,7 @@ export class LocalTime {
368
448
  // .join(' ')
369
449
  }
370
450
  /**
371
- * Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
451
+ * Returns e.g: `1984-06-21T17:56:21`
372
452
  */
373
453
  toISODateTime() {
374
454
  return this.$date.toISOString().slice(0, 19);
@@ -420,6 +500,9 @@ export class LocalTime {
420
500
  toJSON() {
421
501
  return this.unix();
422
502
  }
503
+ format(fmt) {
504
+ return fmt(this);
505
+ }
423
506
  }
424
507
  /**
425
508
  * Shortcut wrapper around `LocalDate.parse` / `LocalDate.today`
@@ -427,3 +510,83 @@ export class LocalTime {
427
510
  export function localTime(d) {
428
511
  return d ? LocalTime.of(d) : LocalTime.now();
429
512
  }
513
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/getISOWeek/index.ts
514
+ function getWeek(date) {
515
+ const diff = startOfWeek(date).getTime() - startOfWeekYear(date).getTime();
516
+ return Math.round(diff / MILLISECONDS_IN_WEEK) + 1;
517
+ }
518
+ function setWeek(date, week, mutate = false) {
519
+ const d = mutate ? date : new Date(date);
520
+ const diff = getWeek(d) - week;
521
+ d.setDate(d.getDate() - diff * 7);
522
+ return d;
523
+ }
524
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/startOfISOWeekYear/index.ts
525
+ function startOfWeekYear(date) {
526
+ const year = getWeekYear(date);
527
+ const fourthOfJanuary = new Date(0);
528
+ fourthOfJanuary.setFullYear(year, 0, 4);
529
+ fourthOfJanuary.setHours(0, 0, 0, 0);
530
+ return startOfWeek(fourthOfJanuary, true);
531
+ }
532
+ // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/getISOWeekYear/index.ts
533
+ function getWeekYear(date) {
534
+ const year = date.getFullYear();
535
+ const fourthOfJanuaryOfNextYear = new Date(0);
536
+ fourthOfJanuaryOfNextYear.setFullYear(year + 1, 0, 4);
537
+ fourthOfJanuaryOfNextYear.setHours(0, 0, 0, 0);
538
+ const startOfNextYear = startOfWeek(fourthOfJanuaryOfNextYear, true);
539
+ const fourthOfJanuaryOfThisYear = new Date(0);
540
+ fourthOfJanuaryOfThisYear.setFullYear(year, 0, 4);
541
+ fourthOfJanuaryOfThisYear.setHours(0, 0, 0, 0);
542
+ const startOfThisYear = startOfWeek(fourthOfJanuaryOfThisYear, true);
543
+ if (date.getTime() >= startOfNextYear.getTime()) {
544
+ return year + 1;
545
+ }
546
+ else if (date.getTime() >= startOfThisYear.getTime()) {
547
+ return year;
548
+ }
549
+ else {
550
+ return year - 1;
551
+ }
552
+ }
553
+ // function setWeekYear(
554
+ // date: Date,
555
+ // year: number,
556
+ // ): Date {
557
+ // const diff = differenceInCalendarDays(date, startOfWeekYear(date))
558
+ // const fourthOfJanuary = new Date(0)
559
+ // fourthOfJanuary.setFullYear(year, 0, 4)
560
+ // fourthOfJanuary.setHours(0, 0, 0, 0)
561
+ // date = startOfWeekYear(fourthOfJanuary)
562
+ // date.setDate(date.getDate() + diff)
563
+ // return date
564
+ // }
565
+ // function differenceInCalendarDays(
566
+ // dateLeft: Date,
567
+ // dateRight: Date,
568
+ // ): number {
569
+ // return Math.round((startOfDay(dateLeft).getTime() - startOfDay(dateRight).getTime()) / MILLISECONDS_IN_DAY)
570
+ // }
571
+ // function startOfDay(date: Date, mutate = false): Date {
572
+ // const d = mutate ? date : new Date(date)
573
+ // d.setHours(0, 0, 0, 0)
574
+ // return d
575
+ // }
576
+ // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/startOfWeek/index.ts
577
+ function startOfWeek(date, mutate = false) {
578
+ const d = mutate ? date : new Date(date);
579
+ const day = d.getDay();
580
+ const diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn;
581
+ d.setDate(d.getDate() - diff);
582
+ d.setHours(0, 0, 0, 0);
583
+ return d;
584
+ }
585
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/endOfWeek/index.ts
586
+ function endOfWeek(date, mutate = false) {
587
+ const d = mutate ? date : new Date(date);
588
+ const day = d.getDay();
589
+ const diff = (day < weekStartsOn ? -7 : 0) + 6 - (day - weekStartsOn);
590
+ d.setDate(d.getDate() + diff);
591
+ return d;
592
+ }
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.1",
3
+ "version": "14.99.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -16,7 +16,7 @@
16
16
  "@naturalcycles/nodejs-lib": "^12.33.4",
17
17
  "@naturalcycles/time-lib": "^3.5.1",
18
18
  "@types/node": "^17.0.4",
19
- "jest": "^27.0.1",
19
+ "jest": "^28.0.3",
20
20
  "patch-package": "^6.2.1",
21
21
  "prettier": "^2.1.2",
22
22
  "rxjs": "^7.0.1",
@@ -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
+ }
@@ -1,14 +1,16 @@
1
1
  import { _assert } from '../error/assert'
2
- import { IsoDateString, UnixTimestampNumber } from '../types'
2
+ import { IsoDateString, IsoDateTimeString, UnixTimestampNumber } from '../types'
3
3
  import { LocalTime } from './localTime'
4
4
 
5
- export type LocalDateUnit = 'year' | 'month' | 'day'
5
+ export type LocalDateUnit = LocalDateUnitStrict | 'week'
6
+ export type LocalDateUnitStrict = 'year' | 'month' | 'day'
6
7
  export type Inclusiveness = '()' | '[]' | '[)' | '(]'
7
8
 
8
9
  const MDAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
9
10
  const DATE_REGEX = /^(\d\d\d\d)-(\d\d)-(\d\d)$/
10
11
 
11
12
  export type LocalDateConfig = LocalDate | IsoDateString
13
+ export type LocalDateFormatter = (ld: LocalDate) => string
12
14
 
13
15
  /* eslint-disable no-dupe-class-members */
14
16
 
@@ -106,24 +108,28 @@ export class LocalDate {
106
108
  return (mutate ? items : [...items]).sort((a, b) => a.cmp(b) * mod)
107
109
  }
108
110
 
109
- static earliestOrUndefined(items: LocalDate[]): LocalDate | undefined {
111
+ static earliestOrUndefined(items: LocalDateConfig[]): LocalDate | undefined {
110
112
  return items.length ? LocalDate.earliest(items) : undefined
111
113
  }
112
114
 
113
- static earliest(items: LocalDate[]): LocalDate {
115
+ static earliest(items: LocalDateConfig[]): LocalDate {
114
116
  _assert(items.length, 'LocalDate.earliest called on empty array')
115
117
 
116
- return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
118
+ return items
119
+ .map(i => LocalDate.of(i))
120
+ .reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
117
121
  }
118
122
 
119
- static latestOrUndefined(items: LocalDate[]): LocalDate | undefined {
123
+ static latestOrUndefined(items: LocalDateConfig[]): LocalDate | undefined {
120
124
  return items.length ? LocalDate.latest(items) : undefined
121
125
  }
122
126
 
123
- static latest(items: LocalDate[]): LocalDate {
127
+ static latest(items: LocalDateConfig[]): LocalDate {
124
128
  _assert(items.length, 'LocalDate.latest called on empty array')
125
129
 
126
- return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
130
+ return items
131
+ .map(i => LocalDate.of(i))
132
+ .reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
127
133
  }
128
134
 
129
135
  static range(
@@ -133,6 +139,11 @@ export class LocalDate {
133
139
  step = 1,
134
140
  stepUnit: LocalDateUnit = 'day',
135
141
  ): LocalDate[] {
142
+ if (stepUnit === 'week') {
143
+ step *= 7
144
+ stepUnit = 'day'
145
+ }
146
+
136
147
  const dates: LocalDate[] = []
137
148
  const $min = LocalDate.of(min)
138
149
  const $max = LocalDate.of(max).startOf(stepUnit)
@@ -153,11 +164,11 @@ export class LocalDate {
153
164
  return dates
154
165
  }
155
166
 
156
- get(unit: LocalDateUnit): number {
167
+ get(unit: LocalDateUnitStrict): number {
157
168
  return unit === 'year' ? this.$year : unit === 'month' ? this.$month : this.$day
158
169
  }
159
170
 
160
- set(unit: LocalDateUnit, v: number, mutate = false): LocalDate {
171
+ set(unit: LocalDateUnitStrict, v: number, mutate = false): LocalDate {
161
172
  const t = mutate ? this : this.clone()
162
173
 
163
174
  if (unit === 'year') {
@@ -257,7 +268,7 @@ export class LocalDate {
257
268
  return (this.$year - d.$year) * 12 + (this.$month - d.$month)
258
269
  }
259
270
 
260
- // unit is 'day'
271
+ // unit is 'day' or 'week'
261
272
  let days = this.$day - d.$day
262
273
 
263
274
  if (d.$year < this.$year) {
@@ -280,12 +291,21 @@ export class LocalDate {
280
291
  }
281
292
  }
282
293
 
294
+ if (unit === 'week') {
295
+ return Math.floor(days / 7)
296
+ }
297
+
283
298
  return days
284
299
  }
285
300
 
286
301
  add(num: number, unit: LocalDateUnit, mutate = false): LocalDate {
287
302
  let { $day, $month, $year } = this
288
303
 
304
+ if (unit === 'week') {
305
+ num *= 7
306
+ unit = 'day'
307
+ }
308
+
289
309
  if (unit === 'day') {
290
310
  $day += num
291
311
  } else if (unit === 'month') {
@@ -346,14 +366,14 @@ export class LocalDate {
346
366
  return this.add(-num, unit, mutate)
347
367
  }
348
368
 
349
- startOf(unit: LocalDateUnit): LocalDate {
369
+ startOf(unit: LocalDateUnitStrict): LocalDate {
350
370
  if (unit === 'day') return this
351
371
  if (unit === 'month') return LocalDate.create(this.$year, this.$month, 1)
352
372
  // year
353
373
  return LocalDate.create(this.$year, 1, 1)
354
374
  }
355
375
 
356
- endOf(unit: LocalDateUnit): LocalDate {
376
+ endOf(unit: LocalDateUnitStrict): LocalDate {
357
377
  if (unit === 'day') return this
358
378
  if (unit === 'month')
359
379
  return LocalDate.create(
@@ -400,6 +420,13 @@ export class LocalDate {
400
420
  return this.toString()
401
421
  }
402
422
 
423
+ /**
424
+ * Returns e.g: `1984-06-21T17:56:21`
425
+ */
426
+ toISODateTime(): IsoDateTimeString {
427
+ return this.toString() + 'T00:00:00'
428
+ }
429
+
403
430
  toString(): IsoDateString {
404
431
  return [
405
432
  String(this.$year).padStart(4, '0'),
@@ -428,6 +455,10 @@ export class LocalDate {
428
455
  toJSON(): IsoDateString {
429
456
  return this.toString()
430
457
  }
458
+
459
+ format(fmt: LocalDateFormatter): string {
460
+ return fmt(this)
461
+ }
431
462
  }
432
463
 
433
464
  /**