@naturalcycles/js-lib 14.99.3 → 14.101.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.
@@ -1,4 +1,4 @@
1
- import { IsoDateString, IsoDateTimeString, UnixTimestampNumber } from '../types';
1
+ import { IsoDateString, IsoDateTimeString, UnixTimestampMillisNumber, UnixTimestampNumber } from '../types';
2
2
  import { LocalTime } from './localTime';
3
3
  export declare type LocalDateUnit = LocalDateUnitStrict | 'week';
4
4
  export declare type LocalDateUnitStrict = 'year' | 'month' | 'day';
@@ -89,7 +89,7 @@ export declare class LocalDate {
89
89
  toString(): IsoDateString;
90
90
  toStringCompact(): string;
91
91
  unix(): UnixTimestampNumber;
92
- unixMillis(): number;
92
+ unixMillis(): UnixTimestampMillisNumber;
93
93
  toJSON(): IsoDateString;
94
94
  format(fmt: LocalDateFormatter): string;
95
95
  }
@@ -218,15 +218,23 @@ class LocalDate {
218
218
  const [big, small] = sign === 1 ? [this, d] : [d, this];
219
219
  if (unit === 'year') {
220
220
  let years = big.$year - small.$year;
221
- if (big.$month < small.$month || (big.$month === small.$month && big.$day < small.$day)) {
221
+ if (big.$month < small.$month ||
222
+ (big.$month === small.$month &&
223
+ big.$day < small.$day &&
224
+ !(big.$day === LocalDate.getMonthLength(big.$year, big.$month) &&
225
+ small.$day === LocalDate.getMonthLength(small.$year, small.$month)))) {
222
226
  years--;
223
227
  }
224
228
  return years * sign || 0;
225
229
  }
226
230
  if (unit === 'month') {
227
231
  let months = (big.$year - small.$year) * 12 + (big.$month - small.$month);
228
- if (big.$day < small.$day)
229
- months--;
232
+ if (big.$day < small.$day) {
233
+ const bigMonthLen = LocalDate.getMonthLength(big.$year, big.$month);
234
+ if (big.$day !== bigMonthLen || small.$day < bigMonthLen) {
235
+ months--;
236
+ }
237
+ }
230
238
  return months * sign || 0;
231
239
  }
232
240
  // unit is 'day' or 'week'
@@ -266,20 +274,36 @@ class LocalDate {
266
274
  else if (unit === 'year') {
267
275
  $year += num;
268
276
  }
277
+ // check month overflow
278
+ while ($month > 12) {
279
+ $year += 1;
280
+ $month -= 12;
281
+ }
282
+ while ($month < 1) {
283
+ $year -= 1;
284
+ $month += 12;
285
+ }
269
286
  // check day overflow
270
- if (unit === 'day') {
271
- if ($day < 1) {
272
- while ($day < 1) {
273
- $month -= 1;
274
- if ($month < 1) {
275
- $year -= 1;
276
- $month += 12;
277
- }
278
- $day += LocalDate.getMonthLength($year, $month);
287
+ // Applies not only for 'day' unit, but also e.g 2022-05-31 plus 1 month should be 2022-06-30 (not 31!)
288
+ if ($day < 1) {
289
+ while ($day < 1) {
290
+ $month -= 1;
291
+ if ($month < 1) {
292
+ $year -= 1;
293
+ $month += 12;
294
+ }
295
+ $day += LocalDate.getMonthLength($year, $month);
296
+ }
297
+ }
298
+ else {
299
+ let monLen = LocalDate.getMonthLength($year, $month);
300
+ if (unit !== 'day') {
301
+ if ($day > monLen) {
302
+ // Case of 2022-05-31 plus 1 month should be 2022-06-30, not 31
303
+ $day = monLen;
279
304
  }
280
305
  }
281
306
  else {
282
- let monLen = LocalDate.getMonthLength($year, $month);
283
307
  while ($day > monLen) {
284
308
  $day -= monLen;
285
309
  $month += 1;
@@ -291,15 +315,6 @@ class LocalDate {
291
315
  }
292
316
  }
293
317
  }
294
- // check month overflow
295
- while ($month > 12) {
296
- $year += 1;
297
- $month -= 12;
298
- }
299
- while ($month < 1) {
300
- $year -= 1;
301
- $month += 12;
302
- }
303
318
  if (mutate) {
304
319
  this.$year = $year;
305
320
  this.$month = $month;
@@ -1,4 +1,4 @@
1
- import { IsoDateString, IsoDateTimeString, UnixTimestampNumber } from '../types';
1
+ import { IsoDateString, IsoDateTimeString, UnixTimestampMillisNumber, UnixTimestampNumber } from '../types';
2
2
  import { Inclusiveness, LocalDate } from './localDate';
3
3
  export declare type LocalTimeUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second';
4
4
  export declare enum ISODayOfWeek {
@@ -31,6 +31,10 @@ export declare class LocalTime {
31
31
  * Input can already be a LocalDate - it is returned as-is in that case.
32
32
  */
33
33
  static of(d: LocalTimeConfig): LocalTime;
34
+ /**
35
+ * Create LocalTime from unixTimestamp in milliseconds (not in seconds).
36
+ */
37
+ static ofMillis(millis: UnixTimestampMillisNumber): LocalTime;
34
38
  /**
35
39
  * Returns null if invalid
36
40
  */
@@ -93,7 +97,7 @@ export declare class LocalTime {
93
97
  getDate(): Date;
94
98
  clone(): LocalTime;
95
99
  unix(): UnixTimestampNumber;
96
- unixMillis(): number;
100
+ unixMillis(): UnixTimestampMillisNumber;
97
101
  valueOf(): UnixTimestampNumber;
98
102
  toLocalDate(): LocalDate;
99
103
  toPretty(seconds?: boolean): IsoDateTimeString;
@@ -39,6 +39,12 @@ class LocalTime {
39
39
  }
40
40
  return t;
41
41
  }
42
+ /**
43
+ * Create LocalTime from unixTimestamp in milliseconds (not in seconds).
44
+ */
45
+ static ofMillis(millis) {
46
+ return LocalTime.of(new Date(millis));
47
+ }
42
48
  /**
43
49
  * Returns null if invalid
44
50
  */
@@ -177,14 +183,9 @@ class LocalTime {
177
183
  }
178
184
  setComponents(c, mutate = false) {
179
185
  const d = mutate ? this.$date : new Date(this.$date);
180
- if (c.year) {
181
- d.setFullYear(c.year);
182
- }
183
- if (c.month) {
184
- d.setMonth(c.month - 1);
185
- }
186
- if (c.day) {
187
- d.setDate(c.day);
186
+ // Year, month and day set all-at-once, to avoid 30/31 (and 28/29) mishap
187
+ if (c.day || c.month !== undefined || c.year !== undefined) {
188
+ d.setFullYear(c.year ?? d.getFullYear(), c.month ? c.month - 1 : d.getMonth(), c.day || d.getDate());
188
189
  }
189
190
  if (c.hour !== undefined) {
190
191
  d.setHours(c.hour);
@@ -202,6 +203,10 @@ class LocalTime {
202
203
  num *= 7;
203
204
  unit = 'day';
204
205
  }
206
+ if (unit === 'year' || unit === 'month') {
207
+ const d = addMonths(this.$date, unit === 'month' ? num : num * 12, mutate);
208
+ return mutate ? this : LocalTime.of(d);
209
+ }
205
210
  return this.set(unit, this.get(unit) + num, mutate);
206
211
  }
207
212
  subtract(num, unit, mutate = false) {
@@ -215,33 +220,14 @@ class LocalTime {
215
220
  const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000;
216
221
  if (!secDiff)
217
222
  return 0;
218
- if (unit === 'year' || unit === 'month') {
219
- const sign = secDiff > 0 ? 1 : -1;
220
- // Put items in descending order: "big minus small"
221
- const [big, small] = sign === 1 ? [this.$date, date2] : [date2, this.$date];
222
- if (unit === 'year') {
223
- let years = big.getFullYear() - small.getFullYear();
224
- const big2 = new Date(big);
225
- const small2 = new Date(small);
226
- big2.setFullYear(1584);
227
- small2.setFullYear(1584);
228
- if (big2 < small2)
229
- years--;
230
- return years * sign || 0;
231
- }
232
- if (unit === 'month') {
233
- let months = (big.getFullYear() - small.getFullYear()) * 12 + big.getMonth() - small.getMonth();
234
- const big2 = new Date(big);
235
- const small2 = new Date(small);
236
- big2.setFullYear(1584, 0);
237
- small2.setFullYear(1584, 0);
238
- if (big2 < small2)
239
- months--;
240
- return months * sign || 0;
241
- }
242
- }
243
223
  let r;
244
- if (unit === 'day') {
224
+ if (unit === 'year') {
225
+ r = differenceInMonths(this.getDate(), date2) / 12;
226
+ }
227
+ else if (unit === 'month') {
228
+ r = differenceInMonths(this.getDate(), date2);
229
+ }
230
+ else if (unit === 'day') {
245
231
  r = secDiff / SECONDS_IN_DAY;
246
232
  }
247
233
  else if (unit === 'week') {
@@ -486,7 +472,7 @@ class LocalTime {
486
472
  ].join('');
487
473
  }
488
474
  toString() {
489
- return String(this.unix());
475
+ return this.toISODateTime();
490
476
  }
491
477
  toJSON() {
492
478
  return this.unix();
@@ -543,29 +529,6 @@ function getWeekYear(date) {
543
529
  return year - 1;
544
530
  }
545
531
  }
546
- // function setWeekYear(
547
- // date: Date,
548
- // year: number,
549
- // ): Date {
550
- // const diff = differenceInCalendarDays(date, startOfWeekYear(date))
551
- // const fourthOfJanuary = new Date(0)
552
- // fourthOfJanuary.setFullYear(year, 0, 4)
553
- // fourthOfJanuary.setHours(0, 0, 0, 0)
554
- // date = startOfWeekYear(fourthOfJanuary)
555
- // date.setDate(date.getDate() + diff)
556
- // return date
557
- // }
558
- // function differenceInCalendarDays(
559
- // dateLeft: Date,
560
- // dateRight: Date,
561
- // ): number {
562
- // return Math.round((startOfDay(dateLeft).getTime() - startOfDay(dateRight).getTime()) / MILLISECONDS_IN_DAY)
563
- // }
564
- // function startOfDay(date: Date, mutate = false): Date {
565
- // const d = mutate ? date : new Date(date)
566
- // d.setHours(0, 0, 0, 0)
567
- // return d
568
- // }
569
532
  // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/startOfWeek/index.ts
570
533
  function startOfWeek(date, mutate = false) {
571
534
  const d = mutate ? date : new Date(date);
@@ -583,3 +546,36 @@ function endOfWeek(date, mutate = false) {
583
546
  d.setDate(d.getDate() + diff);
584
547
  return d;
585
548
  }
549
+ function addMonths(d, num, mutate = false) {
550
+ if (!mutate)
551
+ d = new Date(d);
552
+ let day = d.getDate();
553
+ let month = d.getMonth() + 1 + num;
554
+ if (day < 29) {
555
+ d.setMonth(month - 1);
556
+ return d;
557
+ }
558
+ let year = d.getFullYear();
559
+ while (month > 12) {
560
+ year++;
561
+ month -= 12;
562
+ }
563
+ while (month < 1) {
564
+ year--;
565
+ month += 12;
566
+ }
567
+ const monthLen = localDate_1.LocalDate.getMonthLength(year, month);
568
+ if (day > monthLen)
569
+ day = monthLen;
570
+ d.setFullYear(year, month - 1, day);
571
+ return d;
572
+ }
573
+ function differenceInMonths(a, b) {
574
+ if (a.getDate() < b.getDate())
575
+ return -differenceInMonths(b, a);
576
+ const wholeMonthDiff = (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth());
577
+ const anchor = addMonths(a, wholeMonthDiff).getTime();
578
+ const sign = b.getTime() - anchor >= 0 ? 1 : -1;
579
+ const anchor2 = addMonths(a, wholeMonthDiff + sign).getTime();
580
+ return -(wholeMonthDiff + ((b.getTime() - anchor) / (anchor2 - anchor)) * sign);
581
+ }
package/dist/index.d.ts CHANGED
@@ -51,7 +51,7 @@ export * from './string/string.util';
51
51
  import { JsonStringifyFunction, StringifyAnyOptions, _stringifyAny } from './string/stringifyAny';
52
52
  export * from './time/time.util';
53
53
  import { Class, ConditionalExcept, ConditionalPick, Merge, Promisable, ReadonlyDeep, Simplify } from './typeFest';
54
- import { AsyncMapper, AsyncPredicate, BaseDBEntity, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, Saved, Unsaved, UnsavedId, BatchResult, InstanceId, IsoDate, IsoDateString, IsoDateTimeString, KeyValueTuple, Mapper, ObjectMapper, ObjectPredicate, Predicate, PromiseMap, AnyObject, AnyFunction, Reviver, SavedDBEntity, StringMap, UnixTimestampNumber, UnixTimestamp, Integer, ValueOf, ValuesOf, AbortableMapper, AbortableAsyncPredicate, AbortableAsyncMapper, AbortablePredicate, END, SKIP, _noop, _objectKeys, _passNothingPredicate, _passthroughMapper, _passthroughPredicate, _passUndefinedMapper, _stringMapEntries, _stringMapValues } from './types';
54
+ import { AsyncMapper, AsyncPredicate, BaseDBEntity, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, Saved, Unsaved, UnsavedId, BatchResult, InstanceId, IsoDate, IsoDateString, IsoDateTimeString, KeyValueTuple, Mapper, ObjectMapper, ObjectPredicate, Predicate, PromiseMap, AnyObject, AnyFunction, Reviver, SavedDBEntity, StringMap, UnixTimestampNumber, UnixTimestampMillisNumber, UnixTimestamp, Integer, ValueOf, ValuesOf, AbortableMapper, AbortableAsyncPredicate, AbortableAsyncMapper, AbortablePredicate, END, SKIP, _noop, _objectKeys, _passNothingPredicate, _passthroughMapper, _passthroughPredicate, _passUndefinedMapper, _stringMapEntries, _stringMapValues } from './types';
55
55
  export * from './unit/size.util';
56
56
  import { is } from './vendor/is';
57
57
  import { CommonLogLevel, CommonLogFunction, CommonLogger, commonLoggerMinLevel, commonLoggerNoop, commonLogLevelNumber, commonLoggerPipe, commonLoggerPrefix, CommonLogWithLevelFunction, commonLoggerCreate } from './log/commonLogger';
@@ -68,5 +68,5 @@ import { LocalDateConfig, LocalDateFormatter, LocalDateUnit, LocalDateUnitStrict
68
68
  import { LocalTimeConfig, LocalTimeFormatter, LocalTimeUnit, LocalTimeComponents, ISODayOfWeek } from './datetime/localTime';
69
69
  import { DateIntervalConfig, DateIntervalString } from './datetime/dateInterval';
70
70
  import { TimeIntervalConfig, TimeIntervalString } from './datetime/timeInterval';
71
- export type { DateIntervalConfig, DateIntervalString, TimeIntervalConfig, TimeIntervalString, LocalDateConfig, LocalDateFormatter, LocalDateUnit, LocalDateUnitStrict, Inclusiveness, LocalTimeConfig, LocalTimeFormatter, LocalTimeUnit, ISODayOfWeek, LocalTimeComponents, AbortableMapper, AbortablePredicate, AbortableAsyncPredicate, AbortableAsyncMapper, PQueueCfg, MemoCache, AsyncMemoCache, PromiseDecoratorCfg, PromiseDecoratorResp, ErrorData, ErrorObject, HttpErrorData, HttpErrorResponse, Admin401ErrorData, Admin403ErrorData, StringMap, PromiseMap, AnyObject, AnyFunction, ValuesOf, ValueOf, KeyValueTuple, ObjectMapper, ObjectPredicate, InstanceId, IsoDate, IsoDateString, IsoDateTimeString, Reviver, PMapOptions, Mapper, AsyncMapper, Predicate, AsyncPredicate, BatchResult, DeferredPromise, PRetryOptions, PTimeoutOptions, TryCatchOptions, StringifyAnyOptions, JsonStringifyFunction, Merge, ReadonlyDeep, Promisable, Simplify, ConditionalPick, ConditionalExcept, Class, UnixTimestampNumber, UnixTimestamp, Integer, BaseDBEntity, SavedDBEntity, Saved, Unsaved, UnsavedId, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, JsonSchema, JsonSchemaAny, JsonSchemaOneOf, JsonSchemaAllOf, JsonSchemaAnyOf, JsonSchemaNot, JsonSchemaRef, JsonSchemaConst, JsonSchemaEnum, JsonSchemaString, JsonSchemaNumber, JsonSchemaBoolean, JsonSchemaNull, JsonSchemaRootObject, JsonSchemaObject, JsonSchemaArray, JsonSchemaTuple, JsonSchemaBuilder, CommonLogLevel, CommonLogWithLevelFunction, CommonLogFunction, CommonLogger, };
71
+ export type { DateIntervalConfig, DateIntervalString, TimeIntervalConfig, TimeIntervalString, LocalDateConfig, LocalDateFormatter, LocalDateUnit, LocalDateUnitStrict, Inclusiveness, LocalTimeConfig, LocalTimeFormatter, LocalTimeUnit, ISODayOfWeek, LocalTimeComponents, AbortableMapper, AbortablePredicate, AbortableAsyncPredicate, AbortableAsyncMapper, PQueueCfg, MemoCache, AsyncMemoCache, PromiseDecoratorCfg, PromiseDecoratorResp, ErrorData, ErrorObject, HttpErrorData, HttpErrorResponse, Admin401ErrorData, Admin403ErrorData, StringMap, PromiseMap, AnyObject, AnyFunction, ValuesOf, ValueOf, KeyValueTuple, ObjectMapper, ObjectPredicate, InstanceId, IsoDate, IsoDateString, IsoDateTimeString, Reviver, PMapOptions, Mapper, AsyncMapper, Predicate, AsyncPredicate, BatchResult, DeferredPromise, PRetryOptions, PTimeoutOptions, TryCatchOptions, StringifyAnyOptions, JsonStringifyFunction, Merge, ReadonlyDeep, Promisable, Simplify, ConditionalPick, ConditionalExcept, Class, UnixTimestampNumber, UnixTimestampMillisNumber, UnixTimestamp, Integer, BaseDBEntity, SavedDBEntity, Saved, Unsaved, UnsavedId, CreatedUpdated, CreatedUpdatedId, ObjectWithId, AnyObjectWithId, JsonSchema, JsonSchemaAny, JsonSchemaOneOf, JsonSchemaAllOf, JsonSchemaAnyOf, JsonSchemaNot, JsonSchemaRef, JsonSchemaConst, JsonSchemaEnum, JsonSchemaString, JsonSchemaNumber, JsonSchemaBoolean, JsonSchemaNull, JsonSchemaRootObject, JsonSchemaObject, JsonSchemaArray, JsonSchemaTuple, JsonSchemaBuilder, CommonLogLevel, CommonLogWithLevelFunction, CommonLogFunction, CommonLogger, };
72
72
  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/dist/types.d.ts CHANGED
@@ -154,6 +154,12 @@ export declare type IsoDateTimeString = string;
154
154
  * @example 1628945450
155
155
  */
156
156
  export declare type UnixTimestampNumber = number;
157
+ /**
158
+ * Interface explicitly states that the value is a "Unix timestamp in **milleseconds**" (not seconds)
159
+ *
160
+ * @example 1628945450000
161
+ */
162
+ export declare type UnixTimestampMillisNumber = number;
157
163
  /**
158
164
  * @deprecated use UnixTimestampNumber
159
165
  */
@@ -215,15 +215,23 @@ export class LocalDate {
215
215
  const [big, small] = sign === 1 ? [this, d] : [d, this];
216
216
  if (unit === 'year') {
217
217
  let years = big.$year - small.$year;
218
- if (big.$month < small.$month || (big.$month === small.$month && big.$day < small.$day)) {
218
+ if (big.$month < small.$month ||
219
+ (big.$month === small.$month &&
220
+ big.$day < small.$day &&
221
+ !(big.$day === LocalDate.getMonthLength(big.$year, big.$month) &&
222
+ small.$day === LocalDate.getMonthLength(small.$year, small.$month)))) {
219
223
  years--;
220
224
  }
221
225
  return years * sign || 0;
222
226
  }
223
227
  if (unit === 'month') {
224
228
  let months = (big.$year - small.$year) * 12 + (big.$month - small.$month);
225
- if (big.$day < small.$day)
226
- months--;
229
+ if (big.$day < small.$day) {
230
+ const bigMonthLen = LocalDate.getMonthLength(big.$year, big.$month);
231
+ if (big.$day !== bigMonthLen || small.$day < bigMonthLen) {
232
+ months--;
233
+ }
234
+ }
227
235
  return months * sign || 0;
228
236
  }
229
237
  // unit is 'day' or 'week'
@@ -263,20 +271,36 @@ export class LocalDate {
263
271
  else if (unit === 'year') {
264
272
  $year += num;
265
273
  }
274
+ // check month overflow
275
+ while ($month > 12) {
276
+ $year += 1;
277
+ $month -= 12;
278
+ }
279
+ while ($month < 1) {
280
+ $year -= 1;
281
+ $month += 12;
282
+ }
266
283
  // check day overflow
267
- if (unit === 'day') {
268
- if ($day < 1) {
269
- while ($day < 1) {
270
- $month -= 1;
271
- if ($month < 1) {
272
- $year -= 1;
273
- $month += 12;
274
- }
275
- $day += LocalDate.getMonthLength($year, $month);
284
+ // Applies not only for 'day' unit, but also e.g 2022-05-31 plus 1 month should be 2022-06-30 (not 31!)
285
+ if ($day < 1) {
286
+ while ($day < 1) {
287
+ $month -= 1;
288
+ if ($month < 1) {
289
+ $year -= 1;
290
+ $month += 12;
291
+ }
292
+ $day += LocalDate.getMonthLength($year, $month);
293
+ }
294
+ }
295
+ else {
296
+ let monLen = LocalDate.getMonthLength($year, $month);
297
+ if (unit !== 'day') {
298
+ if ($day > monLen) {
299
+ // Case of 2022-05-31 plus 1 month should be 2022-06-30, not 31
300
+ $day = monLen;
276
301
  }
277
302
  }
278
303
  else {
279
- let monLen = LocalDate.getMonthLength($year, $month);
280
304
  while ($day > monLen) {
281
305
  $day -= monLen;
282
306
  $month += 1;
@@ -288,15 +312,6 @@ export class LocalDate {
288
312
  }
289
313
  }
290
314
  }
291
- // check month overflow
292
- while ($month > 12) {
293
- $year += 1;
294
- $month -= 12;
295
- }
296
- while ($month < 1) {
297
- $year -= 1;
298
- $month += 12;
299
- }
300
315
  if (mutate) {
301
316
  this.$year = $year;
302
317
  this.$month = $month;
@@ -36,6 +36,12 @@ export class LocalTime {
36
36
  }
37
37
  return t;
38
38
  }
39
+ /**
40
+ * Create LocalTime from unixTimestamp in milliseconds (not in seconds).
41
+ */
42
+ static ofMillis(millis) {
43
+ return LocalTime.of(new Date(millis));
44
+ }
39
45
  /**
40
46
  * Returns null if invalid
41
47
  */
@@ -173,15 +179,11 @@ export class LocalTime {
173
179
  return v === undefined ? this.get('second') : this.set('second', v);
174
180
  }
175
181
  setComponents(c, mutate = false) {
182
+ var _a;
176
183
  const d = mutate ? this.$date : new Date(this.$date);
177
- if (c.year) {
178
- d.setFullYear(c.year);
179
- }
180
- if (c.month) {
181
- d.setMonth(c.month - 1);
182
- }
183
- if (c.day) {
184
- d.setDate(c.day);
184
+ // Year, month and day set all-at-once, to avoid 30/31 (and 28/29) mishap
185
+ if (c.day || c.month !== undefined || c.year !== undefined) {
186
+ d.setFullYear((_a = c.year) !== null && _a !== void 0 ? _a : d.getFullYear(), c.month ? c.month - 1 : d.getMonth(), c.day || d.getDate());
185
187
  }
186
188
  if (c.hour !== undefined) {
187
189
  d.setHours(c.hour);
@@ -199,6 +201,10 @@ export class LocalTime {
199
201
  num *= 7;
200
202
  unit = 'day';
201
203
  }
204
+ if (unit === 'year' || unit === 'month') {
205
+ const d = addMonths(this.$date, unit === 'month' ? num : num * 12, mutate);
206
+ return mutate ? this : LocalTime.of(d);
207
+ }
202
208
  return this.set(unit, this.get(unit) + num, mutate);
203
209
  }
204
210
  subtract(num, unit, mutate = false) {
@@ -212,33 +218,14 @@ export class LocalTime {
212
218
  const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000;
213
219
  if (!secDiff)
214
220
  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
- }
240
221
  let r;
241
- if (unit === 'day') {
222
+ if (unit === 'year') {
223
+ r = differenceInMonths(this.getDate(), date2) / 12;
224
+ }
225
+ else if (unit === 'month') {
226
+ r = differenceInMonths(this.getDate(), date2);
227
+ }
228
+ else if (unit === 'day') {
242
229
  r = secDiff / SECONDS_IN_DAY;
243
230
  }
244
231
  else if (unit === 'week') {
@@ -483,7 +470,7 @@ export class LocalTime {
483
470
  ].join('');
484
471
  }
485
472
  toString() {
486
- return String(this.unix());
473
+ return this.toISODateTime();
487
474
  }
488
475
  toJSON() {
489
476
  return this.unix();
@@ -538,29 +525,6 @@ function getWeekYear(date) {
538
525
  return year - 1;
539
526
  }
540
527
  }
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
528
  // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/startOfWeek/index.ts
565
529
  function startOfWeek(date, mutate = false) {
566
530
  const d = mutate ? date : new Date(date);
@@ -578,3 +542,36 @@ function endOfWeek(date, mutate = false) {
578
542
  d.setDate(d.getDate() + diff);
579
543
  return d;
580
544
  }
545
+ function addMonths(d, num, mutate = false) {
546
+ if (!mutate)
547
+ d = new Date(d);
548
+ let day = d.getDate();
549
+ let month = d.getMonth() + 1 + num;
550
+ if (day < 29) {
551
+ d.setMonth(month - 1);
552
+ return d;
553
+ }
554
+ let year = d.getFullYear();
555
+ while (month > 12) {
556
+ year++;
557
+ month -= 12;
558
+ }
559
+ while (month < 1) {
560
+ year--;
561
+ month += 12;
562
+ }
563
+ const monthLen = LocalDate.getMonthLength(year, month);
564
+ if (day > monthLen)
565
+ day = monthLen;
566
+ d.setFullYear(year, month - 1, day);
567
+ return d;
568
+ }
569
+ function differenceInMonths(a, b) {
570
+ if (a.getDate() < b.getDate())
571
+ return -differenceInMonths(b, a);
572
+ const wholeMonthDiff = (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth());
573
+ const anchor = addMonths(a, wholeMonthDiff).getTime();
574
+ const sign = b.getTime() - anchor >= 0 ? 1 : -1;
575
+ const anchor2 = addMonths(a, wholeMonthDiff + sign).getTime();
576
+ return -(wholeMonthDiff + ((b.getTime() - anchor) / (anchor2 - anchor)) * sign);
577
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@naturalcycles/js-lib",
3
- "version": "14.99.3",
3
+ "version": "14.101.0",
4
4
  "scripts": {
5
5
  "prepare": "husky install",
6
6
  "build-prod": "build-prod-esm-cjs",
@@ -1,5 +1,10 @@
1
1
  import { _assert } from '../error/assert'
2
- import { IsoDateString, IsoDateTimeString, UnixTimestampNumber } from '../types'
2
+ import {
3
+ IsoDateString,
4
+ IsoDateTimeString,
5
+ UnixTimestampMillisNumber,
6
+ UnixTimestampNumber,
7
+ } from '../types'
3
8
  import { LocalTime } from './localTime'
4
9
 
5
10
  export type LocalDateUnit = LocalDateUnitStrict | 'week'
@@ -269,7 +274,15 @@ export class LocalDate {
269
274
  if (unit === 'year') {
270
275
  let years = big.$year - small.$year
271
276
 
272
- if (big.$month < small.$month || (big.$month === small.$month && big.$day < small.$day)) {
277
+ if (
278
+ big.$month < small.$month ||
279
+ (big.$month === small.$month &&
280
+ big.$day < small.$day &&
281
+ !(
282
+ big.$day === LocalDate.getMonthLength(big.$year, big.$month) &&
283
+ small.$day === LocalDate.getMonthLength(small.$year, small.$month)
284
+ ))
285
+ ) {
273
286
  years--
274
287
  }
275
288
 
@@ -278,7 +291,12 @@ export class LocalDate {
278
291
 
279
292
  if (unit === 'month') {
280
293
  let months = (big.$year - small.$year) * 12 + (big.$month - small.$month)
281
- if (big.$day < small.$day) months--
294
+ if (big.$day < small.$day) {
295
+ const bigMonthLen = LocalDate.getMonthLength(big.$year, big.$month)
296
+ if (big.$day !== bigMonthLen || small.$day < bigMonthLen) {
297
+ months--
298
+ }
299
+ }
282
300
  return months * sign || 0
283
301
  }
284
302
 
@@ -324,21 +342,37 @@ export class LocalDate {
324
342
  $year += num
325
343
  }
326
344
 
345
+ // check month overflow
346
+ while ($month > 12) {
347
+ $year += 1
348
+ $month -= 12
349
+ }
350
+ while ($month < 1) {
351
+ $year -= 1
352
+ $month += 12
353
+ }
354
+
327
355
  // check day overflow
328
- if (unit === 'day') {
329
- if ($day < 1) {
330
- while ($day < 1) {
331
- $month -= 1
332
- if ($month < 1) {
333
- $year -= 1
334
- $month += 12
335
- }
356
+ // Applies not only for 'day' unit, but also e.g 2022-05-31 plus 1 month should be 2022-06-30 (not 31!)
357
+ if ($day < 1) {
358
+ while ($day < 1) {
359
+ $month -= 1
360
+ if ($month < 1) {
361
+ $year -= 1
362
+ $month += 12
363
+ }
336
364
 
337
- $day += LocalDate.getMonthLength($year, $month)
365
+ $day += LocalDate.getMonthLength($year, $month)
366
+ }
367
+ } else {
368
+ let monLen = LocalDate.getMonthLength($year, $month)
369
+
370
+ if (unit !== 'day') {
371
+ if ($day > monLen) {
372
+ // Case of 2022-05-31 plus 1 month should be 2022-06-30, not 31
373
+ $day = monLen
338
374
  }
339
375
  } else {
340
- let monLen = LocalDate.getMonthLength($year, $month)
341
-
342
376
  while ($day > monLen) {
343
377
  $day -= monLen
344
378
  $month += 1
@@ -352,16 +386,6 @@ export class LocalDate {
352
386
  }
353
387
  }
354
388
 
355
- // check month overflow
356
- while ($month > 12) {
357
- $year += 1
358
- $month -= 12
359
- }
360
- while ($month < 1) {
361
- $year -= 1
362
- $month += 12
363
- }
364
-
365
389
  if (mutate) {
366
390
  this.$year = $year
367
391
  this.$month = $month
@@ -458,7 +482,7 @@ export class LocalDate {
458
482
  return Math.floor(this.toDate().valueOf() / 1000)
459
483
  }
460
484
 
461
- unixMillis(): number {
485
+ unixMillis(): UnixTimestampMillisNumber {
462
486
  return this.toDate().valueOf()
463
487
  }
464
488
 
@@ -1,6 +1,11 @@
1
1
  import { _assert } from '../error/assert'
2
2
  import { _ms } from '../time/time.util'
3
- import { IsoDateString, IsoDateTimeString, UnixTimestampNumber } from '../types'
3
+ import {
4
+ IsoDateString,
5
+ IsoDateTimeString,
6
+ UnixTimestampMillisNumber,
7
+ UnixTimestampNumber,
8
+ } from '../types'
4
9
  import { Inclusiveness, LocalDate } from './localDate'
5
10
 
6
11
  export type LocalTimeUnit = 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second'
@@ -56,6 +61,13 @@ export class LocalTime {
56
61
  return t
57
62
  }
58
63
 
64
+ /**
65
+ * Create LocalTime from unixTimestamp in milliseconds (not in seconds).
66
+ */
67
+ static ofMillis(millis: UnixTimestampMillisNumber): LocalTime {
68
+ return LocalTime.of(new Date(millis))
69
+ }
70
+
59
71
  /**
60
72
  * Returns null if invalid
61
73
  */
@@ -229,15 +241,15 @@ export class LocalTime {
229
241
  setComponents(c: Partial<LocalTimeComponents>, mutate = false): LocalTime {
230
242
  const d = mutate ? this.$date : new Date(this.$date)
231
243
 
232
- if (c.year) {
233
- d.setFullYear(c.year)
234
- }
235
- if (c.month) {
236
- d.setMonth(c.month - 1)
237
- }
238
- if (c.day) {
239
- d.setDate(c.day)
244
+ // Year, month and day set all-at-once, to avoid 30/31 (and 28/29) mishap
245
+ if (c.day || c.month !== undefined || c.year !== undefined) {
246
+ d.setFullYear(
247
+ c.year ?? d.getFullYear(),
248
+ c.month ? c.month - 1 : d.getMonth(),
249
+ c.day || d.getDate(),
250
+ )
240
251
  }
252
+
241
253
  if (c.hour !== undefined) {
242
254
  d.setHours(c.hour)
243
255
  }
@@ -256,6 +268,12 @@ export class LocalTime {
256
268
  num *= 7
257
269
  unit = 'day'
258
270
  }
271
+
272
+ if (unit === 'year' || unit === 'month') {
273
+ const d = addMonths(this.$date, unit === 'month' ? num : num * 12, mutate)
274
+ return mutate ? this : LocalTime.of(d)
275
+ }
276
+
259
277
  return this.set(unit, this.get(unit) + num, mutate)
260
278
  }
261
279
 
@@ -273,37 +291,13 @@ export class LocalTime {
273
291
  const secDiff = (this.$date.valueOf() - date2.valueOf()) / 1000
274
292
  if (!secDiff) return 0
275
293
 
276
- if (unit === 'year' || unit === 'month') {
277
- const sign = secDiff > 0 ? 1 : -1
278
-
279
- // Put items in descending order: "big minus small"
280
- const [big, small] = sign === 1 ? [this.$date, date2] : [date2, this.$date]
281
-
282
- if (unit === 'year') {
283
- let years = big.getFullYear() - small.getFullYear()
284
- const big2 = new Date(big)
285
- const small2 = new Date(small)
286
- big2.setFullYear(1584)
287
- small2.setFullYear(1584)
288
- if (big2 < small2) years--
289
- return years * sign || 0
290
- }
291
-
292
- if (unit === 'month') {
293
- let months =
294
- (big.getFullYear() - small.getFullYear()) * 12 + big.getMonth() - small.getMonth()
295
- const big2 = new Date(big)
296
- const small2 = new Date(small)
297
- big2.setFullYear(1584, 0)
298
- small2.setFullYear(1584, 0)
299
- if (big2 < small2) months--
300
- return months * sign || 0
301
- }
302
- }
303
-
304
294
  let r
305
295
 
306
- if (unit === 'day') {
296
+ if (unit === 'year') {
297
+ r = differenceInMonths(this.getDate(), date2) / 12
298
+ } else if (unit === 'month') {
299
+ r = differenceInMonths(this.getDate(), date2)
300
+ } else if (unit === 'day') {
307
301
  r = secDiff / SECONDS_IN_DAY
308
302
  } else if (unit === 'week') {
309
303
  r = secDiff / (7 * 24 * 60 * 60)
@@ -489,7 +483,7 @@ export class LocalTime {
489
483
  return Math.floor(this.$date.valueOf() / 1000)
490
484
  }
491
485
 
492
- unixMillis(): number {
486
+ unixMillis(): UnixTimestampMillisNumber {
493
487
  return this.$date.valueOf()
494
488
  }
495
489
 
@@ -587,7 +581,7 @@ export class LocalTime {
587
581
  }
588
582
 
589
583
  toString(): string {
590
- return String(this.unix())
584
+ return this.toISODateTime()
591
585
  }
592
586
 
593
587
  toJSON(): UnixTimestampNumber {
@@ -651,32 +645,6 @@ function getWeekYear(date: Date): number {
651
645
  }
652
646
  }
653
647
 
654
- // function setWeekYear(
655
- // date: Date,
656
- // year: number,
657
- // ): Date {
658
- // const diff = differenceInCalendarDays(date, startOfWeekYear(date))
659
- // const fourthOfJanuary = new Date(0)
660
- // fourthOfJanuary.setFullYear(year, 0, 4)
661
- // fourthOfJanuary.setHours(0, 0, 0, 0)
662
- // date = startOfWeekYear(fourthOfJanuary)
663
- // date.setDate(date.getDate() + diff)
664
- // return date
665
- // }
666
-
667
- // function differenceInCalendarDays(
668
- // dateLeft: Date,
669
- // dateRight: Date,
670
- // ): number {
671
- // return Math.round((startOfDay(dateLeft).getTime() - startOfDay(dateRight).getTime()) / MILLISECONDS_IN_DAY)
672
- // }
673
-
674
- // function startOfDay(date: Date, mutate = false): Date {
675
- // const d = mutate ? date : new Date(date)
676
- // d.setHours(0, 0, 0, 0)
677
- // return d
678
- // }
679
-
680
648
  // based on: https://github.com/date-fns/date-fns/blob/fd6bb1a0bab143f2da068c05a9c562b9bee1357d/src/startOfWeek/index.ts
681
649
  function startOfWeek(date: Date, mutate = false): Date {
682
650
  const d = mutate ? date : new Date(date)
@@ -699,3 +667,41 @@ function endOfWeek(date: Date, mutate = false): Date {
699
667
  d.setDate(d.getDate() + diff)
700
668
  return d
701
669
  }
670
+
671
+ function addMonths(d: Date, num: number, mutate = false): Date {
672
+ if (!mutate) d = new Date(d)
673
+
674
+ let day = d.getDate()
675
+ let month = d.getMonth() + 1 + num
676
+
677
+ if (day < 29) {
678
+ d.setMonth(month - 1)
679
+ return d
680
+ }
681
+
682
+ let year = d.getFullYear()
683
+
684
+ while (month > 12) {
685
+ year++
686
+ month -= 12
687
+ }
688
+ while (month < 1) {
689
+ year--
690
+ month += 12
691
+ }
692
+
693
+ const monthLen = LocalDate.getMonthLength(year, month)
694
+ if (day > monthLen) day = monthLen
695
+
696
+ d.setFullYear(year, month - 1, day)
697
+ return d
698
+ }
699
+
700
+ function differenceInMonths(a: Date, b: Date): number {
701
+ if (a.getDate() < b.getDate()) return -differenceInMonths(b, a)
702
+ const wholeMonthDiff = (b.getFullYear() - a.getFullYear()) * 12 + (b.getMonth() - a.getMonth())
703
+ const anchor = addMonths(a, wholeMonthDiff).getTime()
704
+ const sign = b.getTime() - anchor >= 0 ? 1 : -1
705
+ const anchor2 = addMonths(a, wholeMonthDiff + sign).getTime()
706
+ return -(wholeMonthDiff + ((b.getTime() - anchor) / (anchor2 - anchor)) * sign)
707
+ }
package/src/index.ts CHANGED
@@ -120,6 +120,7 @@ import {
120
120
  SavedDBEntity,
121
121
  StringMap,
122
122
  UnixTimestampNumber,
123
+ UnixTimestampMillisNumber,
123
124
  UnixTimestamp,
124
125
  Integer,
125
126
  ValueOf,
@@ -243,6 +244,7 @@ export type {
243
244
  ConditionalExcept,
244
245
  Class,
245
246
  UnixTimestampNumber,
247
+ UnixTimestampMillisNumber,
246
248
  UnixTimestamp,
247
249
  Integer,
248
250
  BaseDBEntity,
package/src/types.ts CHANGED
@@ -213,6 +213,13 @@ export type IsoDateTimeString = string
213
213
  */
214
214
  export type UnixTimestampNumber = number
215
215
 
216
+ /**
217
+ * Interface explicitly states that the value is a "Unix timestamp in **milleseconds**" (not seconds)
218
+ *
219
+ * @example 1628945450000
220
+ */
221
+ export type UnixTimestampMillisNumber = number
222
+
216
223
  /**
217
224
  * @deprecated use UnixTimestampNumber
218
225
  */