@naturalcycles/js-lib 14.98.3 → 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,17 +308,15 @@ 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)
270
- d.setMilliseconds(0)
271
- d.setSeconds(0)
319
+ d.setSeconds(0, 0)
272
320
 
273
321
  /* eslint-disable @typescript-eslint/no-unused-expressions */
274
322
  if (unit !== 'minute') {
@@ -276,9 +324,48 @@ export class LocalTime {
276
324
  if (unit !== 'hour') {
277
325
  this.utcMode ? d.setUTCHours(0) : d.setHours(0)
278
326
  if (unit !== 'day') {
279
- this.utcMode ? d.setUTCDate(0) : d.setDate(0)
280
- if (unit !== 'month') {
327
+ // year, month or week
328
+
329
+ if (unit === 'year') {
281
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)
282
369
  }
283
370
  }
284
371
  }
@@ -298,24 +385,28 @@ export class LocalTime {
298
385
  })
299
386
  }
300
387
 
301
- static earliestOrUndefined(items: LocalTime[]): LocalTime | undefined {
388
+ static earliestOrUndefined(items: LocalTimeConfig[]): LocalTime | undefined {
302
389
  return items.length ? LocalTime.earliest(items) : undefined
303
390
  }
304
391
 
305
- static earliest(items: LocalTime[]): LocalTime {
392
+ static earliest(items: LocalTimeConfig[]): LocalTime {
306
393
  _assert(items.length, 'LocalTime.earliest called on empty array')
307
394
 
308
- 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))
309
398
  }
310
399
 
311
- static latestOrUndefined(items: LocalTime[]): LocalTime | undefined {
400
+ static latestOrUndefined(items: LocalTimeConfig[]): LocalTime | undefined {
312
401
  return items.length ? LocalTime.latest(items) : undefined
313
402
  }
314
403
 
315
- static latest(items: LocalTime[]): LocalTime {
404
+ static latest(items: LocalTimeConfig[]): LocalTime {
316
405
  _assert(items.length, 'LocalTime.latest called on empty array')
317
406
 
318
- 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))
319
410
  }
320
411
 
321
412
  isSame(d: LocalTimeConfig): boolean {
@@ -360,8 +451,6 @@ export class LocalTime {
360
451
  return t1 < t2 ? -1 : 1
361
452
  }
362
453
 
363
- // todo: endOf
364
-
365
454
  components(): LocalTimeComponents {
366
455
  if (this.utcMode) {
367
456
  return {
@@ -520,6 +609,10 @@ export class LocalTime {
520
609
  toJSON(): UnixTimestampNumber {
521
610
  return this.unix()
522
611
  }
612
+
613
+ format(fmt: LocalTimeFormatter): string {
614
+ return fmt(this)
615
+ }
523
616
  }
524
617
 
525
618
  /**
@@ -528,3 +621,97 @@ export class LocalTime {
528
621
  export function localTime(d?: LocalTimeConfig): LocalTime {
529
622
  return d ? LocalTime.of(d) : LocalTime.now()
530
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
@@ -162,8 +162,20 @@ export * from './datetime/localDate'
162
162
  export * from './datetime/localTime'
163
163
  export * from './datetime/dateInterval'
164
164
  export * from './datetime/timeInterval'
165
- import { LocalDateConfig, LocalDateUnit, Inclusiveness } from './datetime/localDate'
166
- 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'
167
179
  import { DateIntervalConfig, DateIntervalString } from './datetime/dateInterval'
168
180
  import { TimeIntervalConfig, TimeIntervalString } from './datetime/timeInterval'
169
181
 
@@ -173,10 +185,14 @@ export type {
173
185
  TimeIntervalConfig,
174
186
  TimeIntervalString,
175
187
  LocalDateConfig,
188
+ LocalDateFormatter,
176
189
  LocalDateUnit,
190
+ LocalDateUnitStrict,
177
191
  Inclusiveness,
178
192
  LocalTimeConfig,
193
+ LocalTimeFormatter,
179
194
  LocalTimeUnit,
195
+ ISODayOfWeek,
180
196
  LocalTimeComponents,
181
197
  AbortableMapper,
182
198
  AbortablePredicate,