@zelgadis87/utils-core 4.1.1 → 4.2.1

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.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json.schemastore.org/package",
3
3
  "name": "@zelgadis87/utils-core",
4
- "version": "4.1.1",
4
+ "version": "4.2.1",
5
5
  "author": "Zelgadis87",
6
6
  "license": "ISC",
7
7
  "private": false,
@@ -138,6 +138,9 @@
138
138
  "vitest": "^2.1.4",
139
139
  "wireit": "^0.14.9"
140
140
  },
141
+ "dependencies": {
142
+ "small-date": "^2.0.1"
143
+ },
141
144
  "scripts": {
142
145
  "clean": "rimraf .wireit dist esbuild coverage",
143
146
  "build": "wireit",
@@ -1,4 +1,4 @@
1
- import Deferred, { ICancelablePromise } from "../async/Deferred.js";
1
+ import Deferred, { DeferredCanceledError, ICancelablePromise } from "../async/Deferred.js";
2
2
  import { randomInterval } from "../random";
3
3
  import { TComparisonResult } from "../sorting/index.js";
4
4
  import { TIntervalHandle, TMaybe, TTimeoutHandle, TVoidFunction, ensureNonNegativeNumber, ensurePositiveNumber } from "../types";
@@ -102,8 +102,10 @@ export class TimeDuration extends TimeBase<TimeDuration> {
102
102
  const deferred = this.promise();
103
103
  void deferred.then( () => {
104
104
  cb();
105
- }, _err => {
106
- return; // Do nothing on cancelation ( err is always an instance of DeferredCanceledError )
105
+ }, err => {
106
+ if ( err instanceof DeferredCanceledError )
107
+ return; // Do nothing on cancelation
108
+ throw err;
107
109
  } );
108
110
  return { cancel: () => deferred.cancel() };
109
111
  }
@@ -1,12 +1,12 @@
1
- import Deferred, { ICancelablePromise } from "../async/Deferred.js";
1
+ import { format } from "small-date";
2
+ import { ICancelablePromise } from "../async/Deferred.js";
2
3
  import { TComparisonResult } from "../sorting/index.js";
3
- import { TTimeoutHandle, TVoidFunction } from "../types";
4
- import { pad } from "../utils";
4
+ import { TVoidFunction } from "../types";
5
5
  import TimeBase from "./TimeBase";
6
6
  import TimeDuration from "./TimeDuration";
7
7
  import { TTimeInstantBuilder, TTimeInstantCreationParameters, createTimeInstantFromParameters, timeInstantBuilder } from "./TimeInstantBuilder.js";
8
8
  import TimeUnit from "./TimeUnit";
9
- import { TDayOfMonth, TDayOfWeek, TFourDigitsMillisecond, TFourDigitsYear, TIso8601DateString, TIso8601DateUtcString, TMonth, TThreeDigitsMillisecond, TTwoDigitsDate, TTwoDigitsHour, TTwoDigitsMinute, TTwoDigitsMonth, TTwoDigitsSecond, TWeekNumber } from "./types";
9
+ import { TDayOfMonth, TDayOfWeek, TIso8601DateString, TIso8601DateUtcString, TMonth, TWeekNumber } from "./types";
10
10
 
11
11
  export class TimeInstant extends TimeBase<TimeInstant> {
12
12
 
@@ -50,240 +50,116 @@ export class TimeInstant extends TimeBase<TimeInstant> {
50
50
  return this.atTime( { hours: 23, minutes: 59, seconds: 59, milliseconds: 999 } );
51
51
  }
52
52
 
53
- /** @deprecated[2024-10-24]: Use #promise() instead. */
54
- public timeout( cb: () => unknown ): TTimeoutHandle {
55
- if ( !this.isInTheFuture ) {
56
- throw new Error( 'Instant is in the past' );
57
- } else {
58
- return this.distanceFromNow().timeout( () => cb() );
59
- }
60
- }
61
-
62
53
  public promise(): ICancelablePromise<void> {
63
- const deferred = new Deferred<void>;
64
- if ( !this.isInTheFuture ) {
65
- deferred.resolve();
66
- } else {
67
- this.distanceFromNow().timeout( () => deferred.resolve() );
68
- }
69
- return deferred.asCancelablePromise;
54
+ this.ensureInTheFuture();
55
+ return this.distanceFromNow().promise()
70
56
  }
71
57
 
72
58
  public delay( cb: () => unknown ): { cancel: TVoidFunction } {
73
- const deferred = this.promise();
74
- void deferred.then( () => {
75
- cb();
76
- }, _err => {
77
- return; // Do nothing on cancelation ( err is always an instance of DeferredCanceledError )
78
- } );
79
- return { cancel: () => deferred.cancel() };
59
+ this.ensureInTheFuture();
60
+ return this.distanceFromNow().delay( cb )
61
+ }
62
+
63
+ public ensureInTheFuture() {
64
+ if ( !this.isInTheFuture )
65
+ throw new Error( 'Instant is in the past' );
66
+ }
67
+
68
+ public ensureInThePast() {
69
+ if ( !this.isInThePast )
70
+ throw new Error( 'Instant is in the future' );
80
71
  }
81
72
 
82
73
  public isToday(): boolean {
83
74
  return Math.floor( this.days ) === Math.floor( TimeInstant.now().days );
84
75
  }
85
76
 
77
+ /**
78
+ * @returns the time as HH:mm:ss
79
+ * @deprecated [2024-12-11] this method is too ambigous and should be removed.
80
+ */
86
81
  public asTimeString(): string {
87
- const date = this.asDate();
88
- return `${this.doGetTwoDigitsHours( date )}:${this.doGetTwoDigitsMinutes( date )}:${this.doGetTwoDigitsSeconds( date )}`;
82
+ return this.format( 'HH:mm:ss' );
89
83
  }
90
84
 
85
+ /**
86
+ * @returns the date as dd/MM/yyyy
87
+ * @deprecated [2024-12-11] this method is too ambigous and should be removed.
88
+ */
91
89
  public asDateString(): string {
92
- const date = this.asDate();
93
- return `${this.doGetTwoDigitsDays( date )}/${this.doGetTwoDigitsMonths( date )}/${this.doGetFourDigitsYears( date )}`;
90
+ return this.format( 'dd/MM/yyyy' );
94
91
  }
95
92
 
96
- /**
97
- * @returns this instant, in ISO 8601 format (eg, 2024-11-01T15:49:22.024+0200). The format is meant to always be realiable.
93
+ /**
94
+ * Formats this instant using the given pattern. The pattern can contain the following tokens:
95
+ *
96
+ * | Token | Description | Example |
97
+ * |:------|:--------------------------------|:------------------------------|
98
+ * | D | Weekday, 1 letter | W |
99
+ * | DD | Weekday, 3 letters | Wed |
100
+ * | DDD | Weekday, long | Wednesday |
101
+ * | d | Day of the month, no padding | 3 |
102
+ * | dd | Day of the month, padded to 2 | 03 |
103
+ * | M | Month, numeric | 3 |
104
+ * | MM | Month, 2 digits | 03 |
105
+ * | MMM | Month, 3 letters | Mar |
106
+ * | MMMM | Month, long | March |
107
+ * | y | Year, numeric | 2021 |
108
+ * | yy | Year, 2 digits | 21 |
109
+ * | yyyy | Year, numeric | 2021 |
110
+ * | h | Hours, no padding | 6 |
111
+ * | hh | Hours, padded to 2 | 06 |
112
+ * | H | Hours in 24-format, no padding | 18 |
113
+ * | HH | Hours in 24-format, padded to 2 | 18 |
114
+ * | m | Minutes, no padding | 7 |
115
+ * | m | Minutes, padded to 2 | 07 |
116
+ * | s | Seconds, no padding | 8 |
117
+ * | ss | Seconds, padded to 2 | 08 |
118
+ * | S | Milliseconds, no padding | 9 |
119
+ * | SS | Milliseconds, padded to 2 | 09 |
120
+ * | SSS | Milliseconds, padded to 3 | 009 |
121
+ * | G | Era, narrow | A |
122
+ * | GG | Era, short | AD |
123
+ * | GGG | Era, long | Anno Domino |
124
+ * | Z | Time zone, short | GMT+1 |
125
+ * | ZZ | Time short, long C | entral European Standard Time |
126
+ * | P | Period of the day, narrow | in the morning |
127
+ * | PP | Period of the day, short | in the morning |
128
+ * | PPP | Period of the day, long | in the morning |
129
+ * | a | Meridiem | pm |
130
+ * @param pattern The pattern to use. Refer to the token table above for details.
131
+ * @param config An optional locale and timeZone definition to use during the format.
132
+ * @returns a string, formatted using the given pattern, at the given timeZone with the given locale.
98
133
  */
99
- public asIso8601(): TIso8601DateString {
100
- const dash = '-', colon = '.', doublecolon = ':', T = 'T', date = this.asDate();
101
- return (
102
- this.doGetFourDigitsYears( date )
103
- + dash
104
- + this.doGetTwoDigitsMonths( date )
105
- + dash
106
- + this.doGetTwoDigitsDays( date )
107
- + T
108
- + this.doGetTwoDigitsHours( date )
109
- + doublecolon
110
- + this.doGetTwoDigitsMinutes( date )
111
- + doublecolon
112
- + this.doGetTwoDigitsSeconds( date )
113
- + colon
114
- + this.doGetThreeDigitsMilliseconds( date )
115
- + this.doGetFourDigitsTimezoneOffset( date )
116
- ) as TIso8601DateString;
134
+ public format( pattern: string, config: { locale?: string, timeZone?: string } = {} ) {
135
+ return format( this.toDate(), pattern, config );
117
136
  }
118
137
 
119
138
  /**
120
- * @returns this instant, in ISO 8601 format and UTC time zone (eg, 2024-11-01T15:49:22.024Z). The format is meant to always be realiable.
121
- */
122
- public asIso8601UTC(): TIso8601DateUtcString {
123
- const dash = '-', colon = '.', doublecolon = ':', T = 'T', utcDate = this.asDateUTC();
124
- return (
125
- this.doGetFourDigitsYears( utcDate )
126
- + dash
127
- + this.doGetTwoDigitsMonths( utcDate )
128
- + dash
129
- + this.doGetTwoDigitsDays( utcDate )
130
- + T
131
- + this.doGetTwoDigitsHours( utcDate )
132
- + doublecolon
133
- + this.doGetTwoDigitsMinutes( utcDate )
134
- + doublecolon
135
- + this.doGetTwoDigitsSeconds( utcDate )
136
- + colon
137
- + this.doGetThreeDigitsMilliseconds( utcDate )
138
- + 'Z'
139
- ) as TIso8601DateUtcString;
139
+ * @returns this instant, in ISO 8601 format (eg, 2024-11-01T15:49:22.024Z). The format is meant to always be realiable.
140
+ */
141
+ public asIso8601(): TIso8601DateUtcString {
142
+ return this.format( 'yyyy-MM-dd"T"HH:mm:ss.SSS"Z"', { timeZone: 'UTC' } ) as TIso8601DateUtcString;
140
143
  }
141
144
 
142
145
  /**
143
146
  * @returns this instant, in a human readable format. The format COULD change in the future, do NOT use this method for consistent outputs.
147
+ * @deprecated [2024-12-11] this method is too broad and ambigous and should be removed.
144
148
  */
145
149
  public asHumanTimestamp(): string {
146
- const dash = '-', doublecolon = ':', space = ' ', T = 'T', date = this.asDate();
147
- return (
148
- this.doGetFourDigitsYears( date )
149
- + dash
150
- + this.doGetTwoDigitsMonths( date )
151
- + dash
152
- + this.doGetTwoDigitsDays( date )
153
- + space
154
- + this.doGetTwoDigitsHours( date )
155
- + doublecolon
156
- + this.doGetTwoDigitsMinutes( date )
157
- + doublecolon
158
- + this.doGetTwoDigitsSeconds( date )
159
- );
160
- }
161
-
162
- // Please do not use this method internally, as it is not optimized for continued usage.
163
- public get twoDigitsDays(): TTwoDigitsDate {
164
- return this.doGetTwoDigitsDays( this.asDate() );
165
- }
166
-
167
- // Please do not use this method internally, as it is not optimized for continued usage.
168
- public get twoDigitsMonths(): TTwoDigitsMonth {
169
- return this.doGetTwoDigitsMonths( this.asDate() );
170
- }
171
-
172
- // Please do not use this method internally, as it is not optimized for continued usage.
173
- public get fourDigitsYears(): TFourDigitsYear {
174
- return this.doGetFourDigitsYears( this.asDate() );
175
- }
176
-
177
- // Please do not use this method internally, as it is not optimized for continued usage.
178
- public get twoDigitsHours(): TTwoDigitsHour {
179
- return this.doGetTwoDigitsHours( this.asDate() );
180
- }
181
-
182
- // Please do not use this method internally, as it is not optimized for continued usage.
183
- public get twoDigitsMinutes(): TTwoDigitsMinute {
184
- return this.doGetTwoDigitsMinutes( this.asDate() );
185
- }
186
-
187
- // Please do not use this method internally, as it is not optimized for continued usage.
188
- public get twoDigitsSeconds(): TTwoDigitsMinute {
189
- return this.doGetTwoDigitsSeconds( this.asDate() );
190
- }
191
-
192
- // Please do not use this method internally, as it is not optimized for continued usage.
193
- public get threeDigitsMilliseconds(): TThreeDigitsMillisecond {
194
- return this.doGetThreeDigitsMilliseconds( this.asDate() );
195
- }
196
-
197
- // Please do not use this method internally, as it is not optimized for continued usage.
198
- public get twoDigitsDaysUTC(): TTwoDigitsDate {
199
- return this.doGetTwoDigitsDays( this.asDateUTC() );
200
- }
201
-
202
- // Please do not use this method internally, as it is not optimized for continued usage.
203
- public get twoDigitsMonthsUTC(): TTwoDigitsMonth {
204
- return this.doGetTwoDigitsMonths( this.asDateUTC() );
205
- }
206
-
207
- // Please do not use this method internally, as it is not optimized for continued usage.
208
- public get fourDigitsYearsUTC(): TFourDigitsYear {
209
- return this.doGetFourDigitsYears( this.asDateUTC() );
210
- }
211
-
212
- // Please do not use this method internally, as it is not optimized for continued usage.
213
- public get twoDigitsHoursUTC(): TTwoDigitsHour {
214
- return this.doGetTwoDigitsHours( this.asDateUTC() );
215
- }
216
-
217
- // Please do not use this method internally, as it is not optimized for continued usage.
218
- public get twoDigitsMinutesUTC(): TTwoDigitsMinute {
219
- return this.doGetTwoDigitsMinutes( this.asDateUTC() );
220
- }
221
-
222
- // Please do not use this method internally, as it is not optimized for continued usage.
223
- public get twoDigitsSecondsUTC(): TTwoDigitsSecond {
224
- return this.doGetTwoDigitsSeconds( this.asDateUTC() );
225
- }
226
-
227
- // Please do not use this method internally, as it is not optimized for continued usage.
228
- public get threeDigitsMillisecondsUTC(): TThreeDigitsMillisecond {
229
- return this.doGetThreeDigitsMilliseconds( this.asDateUTC() );
230
- }
231
-
232
- // Please do not use this method internally, as it is not optimized for continued usage.
233
- public get fourDigitsTimezoneOffset(): TFourDigitsMillisecond {
234
- const offset = this.asDate().getTimezoneOffset();
235
- return ( ( offset >= 0 ? '+' : '-' )
236
- + pad( Math.floor( Math.abs( offset ) / 60 ).toString(), 2, '0' )
237
- + pad( ( Math.abs( offset ) % 60 ).toString(), 2, '0' )
238
- ) as TFourDigitsMillisecond;
239
- }
240
-
241
- protected doGetTwoDigitsDays( date: Date ): TTwoDigitsDate {
242
- return pad( date.getDate().toString(), 2, '0' ) as TTwoDigitsDate;
150
+ return this.format( 'yyyy-MM-dd HH:mm:ss' )
243
151
  }
244
152
 
245
- protected doGetTwoDigitsMonths( date: Date ): TTwoDigitsMonth {
246
- return pad( ( date.getMonth() + 1 ).toString(), 2, '0' ) as TTwoDigitsMonth;
247
- }
248
-
249
- protected doGetFourDigitsYears( date: Date ): TFourDigitsYear {
250
- return pad( date.getFullYear().toString(), 4, '0' ) as TFourDigitsYear;
251
- }
252
-
253
- protected doGetTwoDigitsHours( date: Date ): TTwoDigitsHour {
254
- return pad( date.getHours().toString(), 2, '0' ) as TTwoDigitsHour;
255
- }
256
-
257
- protected doGetTwoDigitsMinutes( date: Date ): TTwoDigitsMinute {
258
- return pad( date.getMinutes().toString(), 2, '0' ) as TTwoDigitsMinute;
259
- }
260
-
261
- protected doGetTwoDigitsSeconds( date: Date ): TTwoDigitsMinute {
262
- return pad( date.getSeconds().toString(), 2, '0' ) as TTwoDigitsMinute;
263
- }
264
-
265
- protected doGetThreeDigitsMilliseconds( date: Date ): TThreeDigitsMillisecond {
266
- return pad( date.getMilliseconds().toString(), 3, '0' ) as TThreeDigitsMillisecond;
267
- }
268
-
269
- protected doGetFourDigitsTimezoneOffset( date: Date ): TFourDigitsMillisecond {
270
- const offset = date.getTimezoneOffset();
271
- return ( ( offset >= 0 ? '+' : '-' )
272
- + pad( Math.floor( Math.abs( offset ) / 60 ).toString(), 2, '0' )
273
- + pad( ( Math.abs( offset ) % 60 ).toString(), 2, '0' )
274
- ) as TFourDigitsMillisecond;
275
- }
276
-
277
- public asUnixTimestamp(): number {
153
+ public toUnixTimestamp(): number {
278
154
  // Syntatic sugar for this.ms;
279
155
  return this.ms;
280
156
  }
281
157
 
282
- public asDate(): Date {
158
+ public toDate(): Date {
283
159
  return new Date( this.ms );
284
160
  }
285
161
 
286
- public asDateUTC(): Date {
162
+ public toDateUTC(): Date {
287
163
  return new Date( this.ms + new Date().getTimezoneOffset() * 60 * 1000 );
288
164
  }
289
165
 
@@ -396,6 +272,12 @@ export class TimeInstant extends TimeBase<TimeInstant> {
396
272
  return TimeInstant.fromUnixTimestamp( new Date( str ).getTime() );
397
273
  }
398
274
 
275
+ public static tryFromIso8601( str: string ) {
276
+ if ( !str.match( /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}:\d{2}(Z|([+-]\d{2}:\d{2})))?$/ ) )
277
+ throw new Error( 'Invalid date given: ' + str );
278
+ return TimeInstant.fromUnixTimestamp( new Date( str ).getTime() );
279
+ }
280
+
399
281
  public static now(): TimeInstant {
400
282
  return TimeInstant.fromUnixTimestamp( Date.now() );
401
283
  }
@@ -435,19 +317,19 @@ export class TimeInstant extends TimeBase<TimeInstant> {
435
317
  }
436
318
 
437
319
  public get dayOfMonth(): TDayOfMonth {
438
- return this.asDate().getDate() as TDayOfMonth;
320
+ return this.toDate().getDate() as TDayOfMonth;
439
321
  }
440
322
 
441
323
  public get dayOfWeek(): TDayOfWeek {
442
- return this.asDate().getDay() + 1 as TDayOfWeek;
324
+ return this.toDate().getDay() + 1 as TDayOfWeek;
443
325
  }
444
326
 
445
327
  public get month(): TMonth {
446
- return this.asDate().getMonth() + 1 as TMonth;
328
+ return this.toDate().getMonth() + 1 as TMonth;
447
329
  }
448
330
 
449
331
  public get year(): number {
450
- return this.asDate().getFullYear();
332
+ return this.toDate().getFullYear();
451
333
  }
452
334
 
453
335
  /**
@@ -460,7 +342,7 @@ export class TimeInstant extends TimeBase<TimeInstant> {
460
342
  * As such, we basically have to count how many Thursdays there has been in this year.
461
343
  * Please note that the thursdayOfThisWeek could be in the previous year.
462
344
  */
463
- const date = this.asDate();
345
+ const date = this.toDate();
464
346
  const oneDay = 1000 * 60 * 60 * 24;
465
347
  const thursdayOfThisWeek = new Date( date.getFullYear(), date.getMonth(), date.getDate() + 4 - ( date.getDay() || 7 ), 14, 0, 0 );
466
348
  const firstOfJanuary = new Date( thursdayOfThisWeek.getFullYear(), 0, 1, 14, 0, 0 );
@@ -67,7 +67,7 @@ const timeInstantResolveValue = ( getFromDate: TFunction<Date, number>, referenc
67
67
  if ( relativeTo === undefined || relativeTo === 'now' ) {
68
68
  return getFromDate( referenceDate ) + relative;
69
69
  } else {
70
- return getFromDate( relativeTo.asDateUTC() ) + relative;
70
+ return getFromDate( relativeTo.toDate() ) + relative;
71
71
  }
72
72
  } else if ( "absolute" in x ) {
73
73
  return x.absolute
@@ -86,7 +86,7 @@ const getFromDate = {
86
86
  } as const satisfies Record<string, TFunction<Date, number>>;
87
87
  const toReferenceDate = ( x: Date | TimeInstant ): Date => {
88
88
  ensureDefined( x );
89
- if ( isTimeInstant( x ) ) return x.asDateUTC();
89
+ if ( isTimeInstant( x ) ) return x.toDate();
90
90
  return x;
91
91
  }
92
92
 
@@ -107,7 +107,7 @@ export function createTimeInstantFromParameters( aParameters: Partial<TTimeInsta
107
107
  return TimeInstant.fromUnixTimestamp( timestamp );
108
108
  }
109
109
  export function timeInstantBuilder() {
110
- let referenceDate = TimeInstant.now().asDateUTC();
110
+ let referenceDate = TimeInstant.now().toDate();
111
111
  const value: TTimeInstantCreationParameters = { ...defaultTimeInstantCreationParameters };
112
112
  const ret = {
113
113
  year: ( x: TTimeInstantCreationParameters[ "year" ] ) => { value.year = x; return ret; },
package/src/types/json.ts CHANGED
@@ -1,5 +1,15 @@
1
+ import { asError } from "./errors.ts";
1
2
 
2
3
  export type TJsonPrimitive = string | number | boolean | null | undefined;
3
4
  export type TJsonArray = Array<TJsonSerializable> | ReadonlyArray<TJsonSerializable>;
4
5
  export type TJsonObject = { [ key: string ]: TJsonSerializable }
5
6
  export type TJsonSerializable = TJsonPrimitive | TJsonArray | TJsonObject;
7
+
8
+ export function tryToParseJson<T extends TJsonSerializable>( jsonContent: string ): [ T, null ] | [ null, Error ] {
9
+ try {
10
+ const data = JSON.parse( jsonContent );
11
+ return [ data as T, null ];
12
+ } catch ( err ) {
13
+ return [ null, asError( err ) ];
14
+ }
15
+ }
@@ -14,8 +14,13 @@ export function pick<T extends object, K extends keyof T>( o: T, keys: K[] ): Pi
14
14
  }
15
15
 
16
16
  export type TOptionsWithoutDefaults<T, R> = Partial<T> & Required<Omit<T, keyof R>>;
17
- export type TOptionalKeysForType<T> = { [K in keyof T]-?: {} extends Pick<T, K> ? K : never }[keyof T];
17
+ export type TOptionalKeysForType<T> = { [ K in keyof T ]-?: {} extends Pick<T, K> ? K : never }[ keyof T ];
18
18
  export type TRequiredKeysForType<T> = Exclude<keyof T, TOptionalKeysForType<T>>;
19
19
  export type TAllKeysOptional<T> = TRequiredKeysForType<T> extends never ? true : false;
20
20
  export type TConditionalParameter<T> = TAllKeysOptional<T> extends true ? T | undefined : T;
21
21
  export type TConditionalParameterOptions<T, R> = TConditionalParameter<TOptionsWithoutDefaults<T, R>>;
22
+
23
+ /**
24
+ * Given a type T, replaces all fields of type From to fields of type To.
25
+ */
26
+ export type TReplaceType<T, From, To> = { [ K in keyof T ]: T[ K ] extends From ? To : T[ K ]; };