@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.
@@ -3,9 +3,20 @@ import { _ms } from '../time/time.util'
3
3
  import { IsoDateString, IsoDateTimeString, UnixTimestampNumber } from '../types'
4
4
  import { Inclusiveness, LocalDate } from './localDate'
5
5
 
6
- export type LocalTimeUnit = 'year' | 'month' | 'day' | 'hour' | 'minute' | 'second'
6
+ export type LocalTimeUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'
7
+
8
+ export enum ISODayOfWeek {
9
+ MONDAY = 1,
10
+ TUESDAY = 2,
11
+ WEDNESDAY = 3,
12
+ THURSDAY = 4,
13
+ FRIDAY = 5,
14
+ SATURDAY = 6,
15
+ SUNDAY = 7,
16
+ }
7
17
 
8
18
  export type LocalTimeConfig = LocalTime | Date | IsoDateTimeString | UnixTimestampNumber
19
+ export type LocalTimeFormatter = (ld: LocalTime) => string
9
20
 
10
21
  export interface LocalTimeComponents {
11
22
  year: number
@@ -16,6 +27,12 @@ export interface LocalTimeComponents {
16
27
  second: number
17
28
  }
18
29
 
30
+ const weekStartsOn = 1 // mon, as per ISO
31
+ const MILLISECONDS_IN_WEEK = 604800000
32
+ // const MILLISECONDS_IN_DAY = 86400000
33
+ // const MILLISECONDS_IN_MINUTE = 60000
34
+ const VALID_DAYS_OF_WEEK = new Set([1, 2, 3, 4, 5, 6, 7])
35
+
19
36
  /* eslint-disable no-dupe-class-members */
20
37
 
21
38
  /**
@@ -134,6 +151,9 @@ export class LocalTime {
134
151
  if (unit === 'minute') {
135
152
  return this.utcMode ? this.$date.getUTCMinutes() : this.$date.getMinutes()
136
153
  }
154
+ if (unit === 'week') {
155
+ return getWeek(this.$date)
156
+ }
137
157
  // second
138
158
  return this.utcMode ? this.$date.getUTCSeconds() : this.$date.getSeconds()
139
159
  }
@@ -154,6 +174,8 @@ export class LocalTime {
154
174
  this.utcMode ? t.$date.setUTCMinutes(v) : t.$date.setMinutes(v)
155
175
  } else if (unit === 'second') {
156
176
  this.utcMode ? t.$date.setUTCSeconds(v) : t.$date.setSeconds(v)
177
+ } else if (unit === 'week') {
178
+ setWeek(t.$date, v, true)
157
179
  }
158
180
  /* eslint-enable @typescript-eslint/no-unused-expressions */
159
181
 
@@ -170,11 +192,33 @@ export class LocalTime {
170
192
  month(v?: number): number | LocalTime {
171
193
  return v === undefined ? this.get('month') : this.set('month', v)
172
194
  }
195
+ week(): number
196
+ week(v: number): LocalTime
197
+ week(v?: number): number | LocalTime {
198
+ return v === undefined ? getWeek(this.$date) : this.set('week', v)
199
+ }
173
200
  day(): number
174
201
  day(v: number): LocalTime
175
202
  day(v?: number): number | LocalTime {
176
203
  return v === undefined ? this.get('day') : this.set('day', v)
177
204
  }
205
+
206
+ /**
207
+ * Based on ISO: 1-7 is Mon-Sun.
208
+ */
209
+ dayOfWeek(): ISODayOfWeek
210
+ dayOfWeek(v: ISODayOfWeek): LocalTime
211
+ dayOfWeek(v?: ISODayOfWeek): ISODayOfWeek | LocalTime {
212
+ const dow = (this.$date.getDay() || 7) as ISODayOfWeek
213
+
214
+ if (v === undefined) {
215
+ return dow
216
+ }
217
+
218
+ if (!VALID_DAYS_OF_WEEK.has(v)) throw new Error(`Invalid dayOfWeek: ${v}`)
219
+
220
+ return this.add(v - dow, 'day')
221
+ }
178
222
  hour(): number
179
223
  hour(v: number): LocalTime
180
224
  hour(v?: number): number | LocalTime {
@@ -219,6 +263,10 @@ export class LocalTime {
219
263
  }
220
264
 
221
265
  add(num: number, unit: LocalTimeUnit, mutate = false): LocalTime {
266
+ if (unit === 'week') {
267
+ num *= 7
268
+ unit = 'day'
269
+ }
222
270
  return this.set(unit, this.get(unit) + num, mutate)
223
271
  }
224
272
 
@@ -249,6 +297,8 @@ export class LocalTime {
249
297
 
250
298
  if (unit === 'day') {
251
299
  r = secDiff / (24 * 60 * 60)
300
+ } else if (unit === 'week') {
301
+ r = secDiff / (7 * 24 * 60 * 60)
252
302
  } else if (unit === 'hour') {
253
303
  r = secDiff / (60 * 60)
254
304
  } else if (unit === 'minute') {
@@ -258,26 +308,64 @@ export class LocalTime {
258
308
  r = secDiff
259
309
  }
260
310
 
261
- r = r < 0 ? -Math.floor(-r) : Math.floor(r)
311
+ r = Math.trunc(r)
262
312
  if (Object.is(r, -0)) return 0
263
313
  return r
264
314
  }
265
315
 
266
316
  startOf(unit: LocalTimeUnit, mutate = false): LocalTime {
267
317
  if (unit === 'second') return this
268
-
269
318
  const d = mutate ? this.$date : new Date(this.$date)
319
+ d.setSeconds(0, 0)
270
320
 
271
321
  /* eslint-disable @typescript-eslint/no-unused-expressions */
272
- this.utcMode ? d.setUTCSeconds(0) : d.setSeconds(0)
273
322
  if (unit !== 'minute') {
274
323
  this.utcMode ? d.setUTCMinutes(0) : d.setMinutes(0)
275
324
  if (unit !== 'hour') {
276
325
  this.utcMode ? d.setUTCHours(0) : d.setHours(0)
277
326
  if (unit !== 'day') {
278
- this.utcMode ? d.setUTCDate(0) : d.setDate(0)
279
- if (unit !== 'month') {
327
+ // year, month or week
328
+
329
+ if (unit === 'year') {
280
330
  this.utcMode ? d.setUTCMonth(0) : d.setMonth(0)
331
+ this.utcMode ? d.setUTCDate(1) : d.setDate(1)
332
+ } else if (unit === 'month') {
333
+ this.utcMode ? d.setUTCDate(1) : d.setDate(1)
334
+ } else {
335
+ // week
336
+ startOfWeek(d, true)
337
+ }
338
+ }
339
+ }
340
+ }
341
+ /* eslint-enable @typescript-eslint/no-unused-expressions */
342
+
343
+ return mutate ? this : new LocalTime(d, this.utcMode)
344
+ }
345
+
346
+ endOf(unit: LocalTimeUnit, mutate = false): LocalTime {
347
+ if (unit === 'second') return this
348
+ const d = mutate ? this.$date : new Date(this.$date)
349
+ d.setSeconds(59, 0)
350
+
351
+ /* eslint-disable @typescript-eslint/no-unused-expressions */
352
+ if (unit !== 'minute') {
353
+ this.utcMode ? d.setUTCMinutes(59) : d.setMinutes(59)
354
+ if (unit !== 'hour') {
355
+ this.utcMode ? d.setUTCHours(23) : d.setHours(23)
356
+ if (unit !== 'day') {
357
+ // year, month or week
358
+
359
+ if (unit === 'year') {
360
+ this.utcMode ? d.setUTCMonth(11) : d.setMonth(11)
361
+ }
362
+
363
+ if (unit === 'week') {
364
+ endOfWeek(d, true)
365
+ } else {
366
+ // year or month
367
+ const lastDay = LocalDate.getMonthLength(d.getFullYear(), d.getMonth() + 1)
368
+ this.utcMode ? d.setUTCDate(lastDay) : d.setDate(lastDay)
281
369
  }
282
370
  }
283
371
  }
@@ -297,24 +385,28 @@ export class LocalTime {
297
385
  })
298
386
  }
299
387
 
300
- static earliestOrUndefined(items: LocalTime[]): LocalTime | undefined {
388
+ static earliestOrUndefined(items: LocalTimeConfig[]): LocalTime | undefined {
301
389
  return items.length ? LocalTime.earliest(items) : undefined
302
390
  }
303
391
 
304
- static earliest(items: LocalTime[]): LocalTime {
392
+ static earliest(items: LocalTimeConfig[]): LocalTime {
305
393
  _assert(items.length, 'LocalTime.earliest called on empty array')
306
394
 
307
- return items.reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
395
+ return items
396
+ .map(i => LocalTime.of(i))
397
+ .reduce((min, item) => (min.isSameOrBefore(item) ? min : item))
308
398
  }
309
399
 
310
- static latestOrUndefined(items: LocalTime[]): LocalTime | undefined {
400
+ static latestOrUndefined(items: LocalTimeConfig[]): LocalTime | undefined {
311
401
  return items.length ? LocalTime.latest(items) : undefined
312
402
  }
313
403
 
314
- static latest(items: LocalTime[]): LocalTime {
404
+ static latest(items: LocalTimeConfig[]): LocalTime {
315
405
  _assert(items.length, 'LocalTime.latest called on empty array')
316
406
 
317
- return items.reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
407
+ return items
408
+ .map(i => LocalTime.of(i))
409
+ .reduce((max, item) => (max.isSameOrAfter(item) ? max : item))
318
410
  }
319
411
 
320
412
  isSame(d: LocalTimeConfig): boolean {
@@ -359,8 +451,6 @@ export class LocalTime {
359
451
  return t1 < t2 ? -1 : 1
360
452
  }
361
453
 
362
- // todo: endOf
363
-
364
454
  components(): LocalTimeComponents {
365
455
  if (this.utcMode) {
366
456
  return {
@@ -458,7 +548,7 @@ export class LocalTime {
458
548
  }
459
549
 
460
550
  /**
461
- * Returns e.g: `1984-06-21T17:56:21`, only the date part of DateTime
551
+ * Returns e.g: `1984-06-21T17:56:21`
462
552
  */
463
553
  toISODateTime(): IsoDateTimeString {
464
554
  return this.$date.toISOString().slice(0, 19)
@@ -519,6 +609,10 @@ export class LocalTime {
519
609
  toJSON(): UnixTimestampNumber {
520
610
  return this.unix()
521
611
  }
612
+
613
+ format(fmt: LocalTimeFormatter): string {
614
+ return fmt(this)
615
+ }
522
616
  }
523
617
 
524
618
  /**
@@ -527,3 +621,97 @@ export class LocalTime {
527
621
  export function localTime(d?: LocalTimeConfig): LocalTime {
528
622
  return d ? LocalTime.of(d) : LocalTime.now()
529
623
  }
624
+
625
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/getISOWeek/index.ts
626
+ function getWeek(date: Date): number {
627
+ const diff = startOfWeek(date).getTime() - startOfWeekYear(date).getTime()
628
+ return Math.round(diff / MILLISECONDS_IN_WEEK) + 1
629
+ }
630
+
631
+ function setWeek(date: Date, week: number, mutate = false): Date {
632
+ const d = mutate ? date : new Date(date)
633
+ const diff = getWeek(d) - week
634
+ d.setDate(d.getDate() - diff * 7)
635
+ return d
636
+ }
637
+
638
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/startOfISOWeekYear/index.ts
639
+ function startOfWeekYear(date: Date): Date {
640
+ const year = getWeekYear(date)
641
+ const fourthOfJanuary = new Date(0)
642
+ fourthOfJanuary.setFullYear(year, 0, 4)
643
+ fourthOfJanuary.setHours(0, 0, 0, 0)
644
+ return startOfWeek(fourthOfJanuary, true)
645
+ }
646
+
647
+ // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/getISOWeekYear/index.ts
648
+ function getWeekYear(date: Date): number {
649
+ const year = date.getFullYear()
650
+
651
+ const fourthOfJanuaryOfNextYear = new Date(0)
652
+ fourthOfJanuaryOfNextYear.setFullYear(year + 1, 0, 4)
653
+ fourthOfJanuaryOfNextYear.setHours(0, 0, 0, 0)
654
+ const startOfNextYear = startOfWeek(fourthOfJanuaryOfNextYear, true)
655
+
656
+ const fourthOfJanuaryOfThisYear = new Date(0)
657
+ fourthOfJanuaryOfThisYear.setFullYear(year, 0, 4)
658
+ fourthOfJanuaryOfThisYear.setHours(0, 0, 0, 0)
659
+ const startOfThisYear = startOfWeek(fourthOfJanuaryOfThisYear, true)
660
+
661
+ if (date.getTime() >= startOfNextYear.getTime()) {
662
+ return year + 1
663
+ } else if (date.getTime() >= startOfThisYear.getTime()) {
664
+ return year
665
+ } else {
666
+ return year - 1
667
+ }
668
+ }
669
+
670
+ // function setWeekYear(
671
+ // date: Date,
672
+ // year: number,
673
+ // ): Date {
674
+ // const diff = differenceInCalendarDays(date, startOfWeekYear(date))
675
+ // const fourthOfJanuary = new Date(0)
676
+ // fourthOfJanuary.setFullYear(year, 0, 4)
677
+ // fourthOfJanuary.setHours(0, 0, 0, 0)
678
+ // date = startOfWeekYear(fourthOfJanuary)
679
+ // date.setDate(date.getDate() + diff)
680
+ // return date
681
+ // }
682
+
683
+ // function differenceInCalendarDays(
684
+ // dateLeft: Date,
685
+ // dateRight: Date,
686
+ // ): number {
687
+ // return Math.round((startOfDay(dateLeft).getTime() - startOfDay(dateRight).getTime()) / MILLISECONDS_IN_DAY)
688
+ // }
689
+
690
+ // function startOfDay(date: Date, mutate = false): Date {
691
+ // const d = mutate ? date : new Date(date)
692
+ // d.setHours(0, 0, 0, 0)
693
+ // return d
694
+ // }
695
+
696
+ // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/startOfWeek/index.ts
697
+ function startOfWeek(date: Date, mutate = false): Date {
698
+ const d = mutate ? date : new Date(date)
699
+
700
+ const day = d.getDay()
701
+ const diff = (day < weekStartsOn ? 7 : 0) + day - weekStartsOn
702
+
703
+ d.setDate(d.getDate() - diff)
704
+ d.setHours(0, 0, 0, 0)
705
+ return d
706
+ }
707
+
708
+ // based on: https://github.com/date-fns/date-fns/blob/master/src/endOfWeek/index.ts
709
+ function endOfWeek(date: Date, mutate = false): Date {
710
+ const d = mutate ? date : new Date(date)
711
+
712
+ const day = d.getDay()
713
+ const diff = (day < weekStartsOn ? -7 : 0) + 6 - (day - weekStartsOn)
714
+
715
+ d.setDate(d.getDate() + diff)
716
+ return d
717
+ }
package/src/index.ts CHANGED
@@ -102,6 +102,7 @@ import {
102
102
  AnyObjectWithId,
103
103
  Saved,
104
104
  Unsaved,
105
+ UnsavedId,
105
106
  BatchResult,
106
107
  InstanceId,
107
108
  IsoDate,
@@ -161,8 +162,20 @@ export * from './datetime/localDate'
161
162
  export * from './datetime/localTime'
162
163
  export * from './datetime/dateInterval'
163
164
  export * from './datetime/timeInterval'
164
- import { LocalDateConfig, LocalDateUnit, Inclusiveness } from './datetime/localDate'
165
- import { LocalTimeConfig, LocalTimeUnit, LocalTimeComponents } from './datetime/localTime'
165
+ import {
166
+ LocalDateConfig,
167
+ LocalDateFormatter,
168
+ LocalDateUnit,
169
+ LocalDateUnitStrict,
170
+ Inclusiveness,
171
+ } from './datetime/localDate'
172
+ import {
173
+ LocalTimeConfig,
174
+ LocalTimeFormatter,
175
+ LocalTimeUnit,
176
+ LocalTimeComponents,
177
+ ISODayOfWeek,
178
+ } from './datetime/localTime'
166
179
  import { DateIntervalConfig, DateIntervalString } from './datetime/dateInterval'
167
180
  import { TimeIntervalConfig, TimeIntervalString } from './datetime/timeInterval'
168
181
 
@@ -172,10 +185,14 @@ export type {
172
185
  TimeIntervalConfig,
173
186
  TimeIntervalString,
174
187
  LocalDateConfig,
188
+ LocalDateFormatter,
175
189
  LocalDateUnit,
190
+ LocalDateUnitStrict,
176
191
  Inclusiveness,
177
192
  LocalTimeConfig,
193
+ LocalTimeFormatter,
178
194
  LocalTimeUnit,
195
+ ISODayOfWeek,
179
196
  LocalTimeComponents,
180
197
  AbortableMapper,
181
198
  AbortablePredicate,
@@ -232,6 +249,7 @@ export type {
232
249
  SavedDBEntity,
233
250
  Saved,
234
251
  Unsaved,
252
+ UnsavedId,
235
253
  CreatedUpdated,
236
254
  CreatedUpdatedId,
237
255
  ObjectWithId,
@@ -169,7 +169,7 @@ export async function pRetry<T>(
169
169
 
170
170
  const r = await fn(attempt)
171
171
 
172
- clearTimeout(timer!)
172
+ clearTimeout(timer)
173
173
 
174
174
  if (logSuccess) {
175
175
  logger.log(`${fname} attempt #${attempt} succeeded in ${_since(started)}`)
@@ -177,7 +177,7 @@ export async function pRetry<T>(
177
177
 
178
178
  resolve(r)
179
179
  } catch (err) {
180
- clearTimeout(timer!)
180
+ clearTimeout(timer)
181
181
 
182
182
  if (logFailures) {
183
183
  logger.warn(
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Merge, Promisable } from './typeFest'
1
+ import { Except, Merge, Promisable } from './typeFest'
2
2
 
3
3
  /**
4
4
  * Map from String to String (or <T>).
@@ -72,7 +72,14 @@ export type Saved<T extends Partial<ObjectWithId>> = Merge<
72
72
  SavedDBEntity<Exclude<T['id'], undefined>>
73
73
  >
74
74
 
75
- export type Unsaved<T extends ObjectWithId> = Merge<T, BaseDBEntity<T['id']>>
75
+ export type Unsaved<T extends Partial<ObjectWithId>> = Merge<
76
+ T,
77
+ BaseDBEntity<Exclude<T['id'], undefined>>
78
+ >
79
+
80
+ export type UnsavedId<T extends Partial<ObjectWithId>> = Except<T, 'id'> & {
81
+ id: Exclude<T['id'], undefined>
82
+ }
76
83
 
77
84
  /**
78
85
  * Convenience type shorthand.