@byloth/core 2.0.0-rc.9 → 2.0.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/dist/core.js +4087 -621
- package/dist/core.js.map +1 -1
- package/dist/core.umd.cjs +2 -2
- package/dist/core.umd.cjs.map +1 -1
- package/package.json +17 -13
- package/src/core/types.ts +41 -0
- package/src/helpers.ts +11 -2
- package/src/index.ts +12 -9
- package/src/models/aggregators/aggregated-async-iterator.ts +920 -21
- package/src/models/aggregators/aggregated-iterator.ts +838 -22
- package/src/models/aggregators/reduced-iterator.ts +827 -11
- package/src/models/aggregators/types.ts +153 -10
- package/src/models/callbacks/callable-object.ts +42 -6
- package/src/models/callbacks/index.ts +2 -2
- package/src/models/callbacks/publisher.ts +160 -4
- package/src/models/callbacks/switchable-callback.ts +230 -23
- package/src/models/callbacks/types.ts +16 -0
- package/src/models/exceptions/core.ts +132 -3
- package/src/models/exceptions/index.ts +405 -13
- package/src/models/index.ts +4 -8
- package/src/models/iterators/smart-async-iterator.ts +827 -22
- package/src/models/iterators/smart-iterator.ts +755 -20
- package/src/models/iterators/types.ts +268 -9
- package/src/models/json/json-storage.ts +508 -110
- package/src/models/json/types.ts +10 -1
- package/src/models/promises/deferred-promise.ts +85 -5
- package/src/models/promises/index.ts +1 -3
- package/src/models/promises/smart-promise.ts +272 -4
- package/src/models/promises/timed-promise.ts +43 -1
- package/src/models/promises/types.ts +84 -2
- package/src/models/timers/clock.ts +109 -19
- package/src/models/timers/countdown.ts +176 -21
- package/src/models/timers/game-loop.ts +266 -0
- package/src/models/timers/index.ts +2 -1
- package/src/models/types.ts +6 -5
- package/src/utils/async.ts +43 -0
- package/src/utils/curve.ts +85 -0
- package/src/utils/date.ts +204 -10
- package/src/utils/dom.ts +16 -2
- package/src/utils/index.ts +3 -2
- package/src/utils/iterator.ts +200 -17
- package/src/utils/math.ts +55 -3
- package/src/utils/random.ts +139 -2
- package/src/utils/string.ts +11 -0
- package/src/models/game-loop.ts +0 -83
- package/src/models/promises/long-running-task.ts +0 -294
- package/src/models/promises/thenable.ts +0 -97
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import type { Interval } from "../../core/types.js";
|
|
2
|
+
import { isBrowser } from "../../helpers.js";
|
|
3
|
+
|
|
4
|
+
import Publisher from "../callbacks/publisher.js";
|
|
5
|
+
import { FatalErrorException, RuntimeException } from "../exceptions/index.js";
|
|
6
|
+
import type { Callback } from "../types.js";
|
|
7
|
+
|
|
8
|
+
interface GameLoopEventMap
|
|
9
|
+
{
|
|
10
|
+
start: () => void;
|
|
11
|
+
stop: () => void;
|
|
12
|
+
|
|
13
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
14
|
+
[key: string]: Callback<any[], any>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A class representing a {@link https://en.wikipedia.org/wiki/Video_game_programming#Game_structure|game loop} pattern
|
|
19
|
+
* that allows to run a function at a specific frame rate.
|
|
20
|
+
*
|
|
21
|
+
* In a browser environment, it uses the native {@link requestAnimationFrame}
|
|
22
|
+
* function to run the callback at the refresh rate of the screen.
|
|
23
|
+
* In a non-browser environment, however, it uses the {@link setInterval}
|
|
24
|
+
* function to run the callback at the specified fixed interval of time.
|
|
25
|
+
*
|
|
26
|
+
* Every time the callback is executed, it receives the
|
|
27
|
+
* elapsed time since the start of the game loop.
|
|
28
|
+
* It's also possible to subscribe to the `start` & `stop` events to receive notifications when they occur.
|
|
29
|
+
*
|
|
30
|
+
* ```ts
|
|
31
|
+
* const loop = new GameLoop((elapsedTime: number) =>
|
|
32
|
+
* {
|
|
33
|
+
* console.log(`The game loop has been running for ${elapsedTime}ms.`);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* loop.onStart(() => { console.log("The game loop has started."); });
|
|
37
|
+
* loop.onStop(() => { console.log("The game loop has stopped."); });
|
|
38
|
+
*
|
|
39
|
+
* loop.start();
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export default class GameLoop
|
|
43
|
+
{
|
|
44
|
+
/**
|
|
45
|
+
* The handle of the interval or the animation frame, depending on the environment.
|
|
46
|
+
* It's used to stop the game loop when the {@link GameLoop._stop} method is called.
|
|
47
|
+
*/
|
|
48
|
+
protected _handle?: number | Interval;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The time when the game loop has started.
|
|
52
|
+
* In addition to indicating the {@link https://en.wikipedia.org/wiki/Unix_time|Unix timestamp}
|
|
53
|
+
* of the start of the game loop, it's also used to calculate the elapsed time.
|
|
54
|
+
*
|
|
55
|
+
* This protected property is the only one that can be modified directly by the derived classes.
|
|
56
|
+
* If you're looking for the public and readonly property, use the {@link GameLoop.startTime} getter instead.
|
|
57
|
+
*/
|
|
58
|
+
protected _startTime: number;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* The time when the game loop has started.
|
|
62
|
+
* In addition to indicating the {@link https://en.wikipedia.org/wiki/Unix_time|Unix timestamp}
|
|
63
|
+
* of the start of the game loop, it's also used to calculate the elapsed time.
|
|
64
|
+
*/
|
|
65
|
+
public get startTime(): number
|
|
66
|
+
{
|
|
67
|
+
return this._startTime;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* A flag indicating whether the game loop is currently running or not.
|
|
72
|
+
*
|
|
73
|
+
* This protected property is the only one that can be modified directly by the derived classes.
|
|
74
|
+
* If you're looking for the public and readonly property, use the {@link GameLoop.isRunning} getter instead.
|
|
75
|
+
*/
|
|
76
|
+
protected _isRunning: boolean;
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* A flag indicating whether the game loop is currently running or not.
|
|
80
|
+
*/
|
|
81
|
+
public get isRunning(): boolean
|
|
82
|
+
{
|
|
83
|
+
return this._isRunning;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* The elapsed time since the start of the game loop.
|
|
88
|
+
* It's calculated as the difference between the current time and the {@link GameLoop.startTime}.
|
|
89
|
+
*/
|
|
90
|
+
public get elapsedTime(): number
|
|
91
|
+
{
|
|
92
|
+
return performance.now() - this._startTime;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* The {@link Publisher} object that will be used to publish the events of the game loop.
|
|
97
|
+
*/
|
|
98
|
+
protected _publisher: Publisher<GameLoopEventMap>;
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* The internal method actually responsible for starting the game loop.
|
|
102
|
+
*
|
|
103
|
+
* Depending on the current environment, it could use the
|
|
104
|
+
* {@link requestAnimationFrame} or the {@link setInterval} function.
|
|
105
|
+
*/
|
|
106
|
+
protected _start: () => void;
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* The internal method actually responsible for stopping the game loop.
|
|
110
|
+
*
|
|
111
|
+
* Depending on the current environment, it could use the
|
|
112
|
+
* {@link cancelAnimationFrame} or the {@link clearInterval} function.
|
|
113
|
+
*/
|
|
114
|
+
protected _stop: () => void;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Initializes a new instance of the {@link GameLoop} class.
|
|
118
|
+
*
|
|
119
|
+
* ---
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```ts
|
|
123
|
+
* const loop = new GameLoop((elapsedTime: number) => { [...] });
|
|
124
|
+
* ```
|
|
125
|
+
*
|
|
126
|
+
* ---
|
|
127
|
+
*
|
|
128
|
+
* @param callback The function that will be executed at each iteration of the game loop.
|
|
129
|
+
* @param msIfNotBrowser
|
|
130
|
+
* The interval in milliseconds that will be used if the current environment isn't a browser. Default is `40`.
|
|
131
|
+
*/
|
|
132
|
+
public constructor(callback: FrameRequestCallback, msIfNotBrowser = 40)
|
|
133
|
+
{
|
|
134
|
+
this._startTime = 0;
|
|
135
|
+
this._isRunning = false;
|
|
136
|
+
|
|
137
|
+
if (isBrowser)
|
|
138
|
+
{
|
|
139
|
+
this._start = () =>
|
|
140
|
+
{
|
|
141
|
+
callback(this.elapsedTime);
|
|
142
|
+
|
|
143
|
+
this._handle = window.requestAnimationFrame(this._start);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
this._stop = () => window.cancelAnimationFrame(this._handle as number);
|
|
147
|
+
}
|
|
148
|
+
else
|
|
149
|
+
{
|
|
150
|
+
// eslint-disable-next-line no-console
|
|
151
|
+
console.warn(
|
|
152
|
+
"Not a browser environment detected. " +
|
|
153
|
+
`Using setInterval@${msIfNotBrowser}ms instead of requestAnimationFrame...`
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
this._start = () =>
|
|
157
|
+
{
|
|
158
|
+
this._handle = setInterval(() => callback(this.elapsedTime), msIfNotBrowser);
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
this._stop = () => clearInterval(this._handle as Interval);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
this._publisher = new Publisher();
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Starts the execution of the game loop.
|
|
169
|
+
*
|
|
170
|
+
* If the game loop is already running, a {@link RuntimeException} will be thrown.
|
|
171
|
+
*
|
|
172
|
+
* ---
|
|
173
|
+
*
|
|
174
|
+
* @example
|
|
175
|
+
* ```ts
|
|
176
|
+
* loop.onStart(() => { [...] }); // This callback will be executed.
|
|
177
|
+
* loop.start();
|
|
178
|
+
* ```
|
|
179
|
+
*
|
|
180
|
+
* ---
|
|
181
|
+
*
|
|
182
|
+
* @param elapsedTime The elapsed time to set as default when the game loop starts. Default is `0`.
|
|
183
|
+
*/
|
|
184
|
+
public start(elapsedTime = 0): void
|
|
185
|
+
{
|
|
186
|
+
if (this._isRunning) { throw new RuntimeException("The game loop has already been started."); }
|
|
187
|
+
|
|
188
|
+
this._startTime = performance.now() - elapsedTime;
|
|
189
|
+
this._start();
|
|
190
|
+
this._isRunning = true;
|
|
191
|
+
|
|
192
|
+
this._publisher.publish("start");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Stops the execution of the game loop.
|
|
197
|
+
*
|
|
198
|
+
* If the game loop hasn't yet started, a {@link RuntimeException} will be thrown.
|
|
199
|
+
*
|
|
200
|
+
* ---
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```ts
|
|
204
|
+
* loop.onStop(() => { [...] }); // This callback will be executed.
|
|
205
|
+
* loop.stop();
|
|
206
|
+
* ```
|
|
207
|
+
*/
|
|
208
|
+
public stop(): void
|
|
209
|
+
{
|
|
210
|
+
if (!(this._isRunning))
|
|
211
|
+
{
|
|
212
|
+
throw new RuntimeException("The game loop had already stopped or hadn't yet started.");
|
|
213
|
+
}
|
|
214
|
+
if (!(this._handle)) { throw new FatalErrorException(); }
|
|
215
|
+
|
|
216
|
+
this._stop();
|
|
217
|
+
this._handle = undefined;
|
|
218
|
+
this._isRunning = false;
|
|
219
|
+
|
|
220
|
+
this._publisher.publish("stop");
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* Subscribes to the `start` event of the game loop.
|
|
225
|
+
*
|
|
226
|
+
* ---
|
|
227
|
+
*
|
|
228
|
+
* @example
|
|
229
|
+
* ```ts
|
|
230
|
+
* loop.onStart(() => { console.log("The game loop has started."); });
|
|
231
|
+
* ```
|
|
232
|
+
*
|
|
233
|
+
* ---
|
|
234
|
+
*
|
|
235
|
+
* @param callback The function that will be executed when the game loop starts.
|
|
236
|
+
*
|
|
237
|
+
* @returns A function that can be used to unsubscribe from the event.
|
|
238
|
+
*/
|
|
239
|
+
public onStart(callback: () => void): () => void
|
|
240
|
+
{
|
|
241
|
+
return this._publisher.subscribe("start", callback);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Subscribes to the `stop` event of the game loop.
|
|
246
|
+
*
|
|
247
|
+
* ---
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* ```ts
|
|
251
|
+
* loop.onStop(() => { console.log("The game loop has stopped."); });
|
|
252
|
+
* ```
|
|
253
|
+
*
|
|
254
|
+
* ---
|
|
255
|
+
*
|
|
256
|
+
* @param callback The function that will be executed when the game loop stops.
|
|
257
|
+
*
|
|
258
|
+
* @returns A function that can be used to unsubscribe from the event.
|
|
259
|
+
*/
|
|
260
|
+
public onStop(callback: () => void): () => void
|
|
261
|
+
{
|
|
262
|
+
return this._publisher.subscribe("stop", callback);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
public readonly [Symbol.toStringTag]: string = "GameLoop";
|
|
266
|
+
}
|
package/src/models/types.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export type {
|
|
2
2
|
KeyedIteratee,
|
|
3
|
+
AsyncKeyedIteratee,
|
|
3
4
|
MaybeAsyncKeyedIteratee,
|
|
4
|
-
|
|
5
|
-
MaybeAsyncKeyedTypeGuardIteratee,
|
|
5
|
+
KeyedTypeGuardPredicate,
|
|
6
6
|
KeyedReducer,
|
|
7
|
+
AsyncKeyedReducer,
|
|
7
8
|
MaybeAsyncKeyedReducer
|
|
8
9
|
|
|
9
10
|
} from "./aggregators/types.js";
|
|
@@ -13,10 +14,11 @@ export type {
|
|
|
13
14
|
AsyncGeneratorFunction,
|
|
14
15
|
MaybeAsyncGeneratorFunction,
|
|
15
16
|
Iteratee,
|
|
17
|
+
AsyncIteratee,
|
|
16
18
|
MaybeAsyncIteratee,
|
|
17
|
-
|
|
18
|
-
MaybeAsyncTypeGuardIteratee,
|
|
19
|
+
TypeGuardPredicate,
|
|
19
20
|
Reducer,
|
|
21
|
+
AsyncReducer,
|
|
20
22
|
MaybeAsyncReducer,
|
|
21
23
|
IteratorLike,
|
|
22
24
|
AsyncIteratorLike,
|
|
@@ -32,7 +34,6 @@ export type {
|
|
|
32
34
|
} from "./json/types.js";
|
|
33
35
|
|
|
34
36
|
export type {
|
|
35
|
-
LongRunningTaskOptions,
|
|
36
37
|
MaybePromise,
|
|
37
38
|
FulfilledHandler,
|
|
38
39
|
RejectedHandler,
|
package/src/utils/async.ts
CHANGED
|
@@ -1,13 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a promise that resolves after a certain number of milliseconds.
|
|
3
|
+
* It can be used to pause or delay the execution of an asynchronous function.
|
|
4
|
+
*
|
|
5
|
+
* ```ts
|
|
6
|
+
* doSomething();
|
|
7
|
+
* await delay(1_000);
|
|
8
|
+
* doSomethingElse();
|
|
9
|
+
* ```
|
|
10
|
+
*
|
|
11
|
+
* @param milliseconds The number of milliseconds to wait before resolving the promise.
|
|
12
|
+
*
|
|
13
|
+
* @returns A {@link Promise} that resolves after the specified number of milliseconds.
|
|
14
|
+
*/
|
|
1
15
|
export function delay(milliseconds: number): Promise<void>
|
|
2
16
|
{
|
|
3
17
|
return new Promise((resolve) => setTimeout(resolve, milliseconds));
|
|
4
18
|
}
|
|
5
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Returns a promise that resolves on the next animation frame.
|
|
22
|
+
* It can be used to synchronize operations with the browser's rendering cycle.
|
|
23
|
+
*
|
|
24
|
+
* ```ts
|
|
25
|
+
* const $el = document.querySelector(".element");
|
|
26
|
+
*
|
|
27
|
+
* $el.classList.add("animate");
|
|
28
|
+
* await nextAnimationFrame();
|
|
29
|
+
* $el.style.opacity = "1";
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @returns A {@link Promise} that resolves on the next animation frame.
|
|
33
|
+
*/
|
|
6
34
|
export function nextAnimationFrame(): Promise<void>
|
|
7
35
|
{
|
|
8
36
|
return new Promise((resolve) => requestAnimationFrame(() => resolve()));
|
|
9
37
|
}
|
|
10
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Returns a promise that resolves on the next microtask.
|
|
41
|
+
* It can be used to yield to the event loop in long-running operations to prevent blocking the main thread.
|
|
42
|
+
*
|
|
43
|
+
* ```ts
|
|
44
|
+
* for (let i = 0; i < 100_000_000; i += 1)
|
|
45
|
+
* {
|
|
46
|
+
* doSomething(i);
|
|
47
|
+
*
|
|
48
|
+
* if (i % 100 === 0) await yieldToEventLoop();
|
|
49
|
+
* }
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
* @returns A {@link Promise} that resolves on the next microtask.
|
|
53
|
+
*/
|
|
11
54
|
export function yieldToEventLoop(): Promise<void>
|
|
12
55
|
{
|
|
13
56
|
return new Promise((resolve) => setTimeout(resolve));
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { SmartIterator, ValueException } from "../models/index.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A utility class that provides a set of methods to generate sequences of numbers following specific curves.
|
|
5
|
+
* It can be used to generate sequences of values that can be
|
|
6
|
+
* used in animations, transitions and other different scenarios.
|
|
7
|
+
*
|
|
8
|
+
* It cannot be instantiated directly.
|
|
9
|
+
*/
|
|
10
|
+
export default class Curve
|
|
11
|
+
{
|
|
12
|
+
/**
|
|
13
|
+
* Generates a given number of values following a linear curve.
|
|
14
|
+
* The values are equally spaced and normalized between 0 and 1.
|
|
15
|
+
*
|
|
16
|
+
* ---
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* for (const value of Curve.Linear(5))
|
|
21
|
+
* {
|
|
22
|
+
* console.log(value); // 0, 0.25, 0.5, 0.75, 1
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* ---
|
|
27
|
+
*
|
|
28
|
+
* @param values The number of values to generate.
|
|
29
|
+
*
|
|
30
|
+
* @returns A {@link SmartIterator} object that generates the values following a linear curve.
|
|
31
|
+
*/
|
|
32
|
+
public static Linear(values: number): SmartIterator<number>
|
|
33
|
+
{
|
|
34
|
+
const steps = (values - 1);
|
|
35
|
+
|
|
36
|
+
return new SmartIterator<number>(function* ()
|
|
37
|
+
{
|
|
38
|
+
for (let index = 0; index < values; index += 1) { yield index / steps; }
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Generates a given number of values following an exponential curve.
|
|
44
|
+
* The values are equally spaced and normalized between 0 and 1.
|
|
45
|
+
*
|
|
46
|
+
* ---
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* for (const value of Curve.Exponential(6))
|
|
51
|
+
* {
|
|
52
|
+
* console.log(value); // 0, 0.04, 0.16, 0.36, 0.64, 1
|
|
53
|
+
* }
|
|
54
|
+
* ```
|
|
55
|
+
*
|
|
56
|
+
* ---
|
|
57
|
+
*
|
|
58
|
+
* @param values The number of values to generate.
|
|
59
|
+
* @param base
|
|
60
|
+
* The base of the exponential curve. Default is `2`.
|
|
61
|
+
*
|
|
62
|
+
* Also note that:
|
|
63
|
+
* - If it's equal to `1`, the curve will be linear.
|
|
64
|
+
* - If it's included between `0` and `1`, the curve will be logarithmic.
|
|
65
|
+
*
|
|
66
|
+
* The base cannot be negative. If so, a {@link ValueException} will be thrown.
|
|
67
|
+
*
|
|
68
|
+
* @returns A {@link SmartIterator} object that generates the values following an exponential curve.
|
|
69
|
+
*/
|
|
70
|
+
public static Exponential(values: number, base = 2): SmartIterator<number>
|
|
71
|
+
{
|
|
72
|
+
if (base < 0) { throw new ValueException("The base of the exponential curve cannot be negative."); }
|
|
73
|
+
|
|
74
|
+
const steps = (values - 1);
|
|
75
|
+
|
|
76
|
+
return new SmartIterator<number>(function* ()
|
|
77
|
+
{
|
|
78
|
+
for (let index = 0; index < values; index += 1) { yield Math.pow(index / steps, base); }
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
private constructor() { /* ... */ }
|
|
83
|
+
|
|
84
|
+
public readonly [Symbol.toStringTag]: string = "Curve";
|
|
85
|
+
}
|
package/src/utils/date.ts
CHANGED
|
@@ -1,50 +1,244 @@
|
|
|
1
1
|
|
|
2
|
-
import { SmartIterator } from "../models/index.js";
|
|
2
|
+
import { RangeException, SmartIterator } from "../models/index.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* An enumeration that represents the time units and their conversion factors.
|
|
6
|
+
* It can be used as utility to express time values in a more
|
|
7
|
+
* readable way or to convert time values between different units.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* setTimeout(() => { [...] }, 5 * TimeUnit.Minute);
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
4
13
|
export enum TimeUnit
|
|
5
14
|
{
|
|
6
15
|
/* eslint-disable @typescript-eslint/prefer-literal-enum-member */
|
|
7
16
|
|
|
17
|
+
/**
|
|
18
|
+
* A millisecond: the base time unit.
|
|
19
|
+
*/
|
|
8
20
|
Millisecond = 1,
|
|
9
|
-
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* A second: 1000 milliseconds.
|
|
24
|
+
*/
|
|
25
|
+
Second = 1_000,
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* A minute: 60 seconds.
|
|
29
|
+
*/
|
|
10
30
|
Minute = 60 * Second,
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* An hour: 60 minutes.
|
|
34
|
+
*/
|
|
11
35
|
Hour = 60 * Minute,
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* A day: 24 hours.
|
|
39
|
+
*/
|
|
12
40
|
Day = 24 * Hour,
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* A week: 7 days.
|
|
44
|
+
*/
|
|
13
45
|
Week = 7 * Day,
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* A month: 30 days.
|
|
49
|
+
*/
|
|
14
50
|
Month = 30 * Day,
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* A year: 365 days.
|
|
54
|
+
*/
|
|
15
55
|
Year = 365 * Day
|
|
16
56
|
}
|
|
17
57
|
|
|
58
|
+
/**
|
|
59
|
+
* An enumeration that represents the days of the week.
|
|
60
|
+
* It can be used as utility to identify the days of the week when working with dates.
|
|
61
|
+
*
|
|
62
|
+
* ```ts
|
|
63
|
+
* const today = new Date();
|
|
64
|
+
* if (today.getUTCDay() === WeekDay.Sunday)
|
|
65
|
+
* {
|
|
66
|
+
* // Today is Sunday. Do something...
|
|
67
|
+
* }
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
export enum WeekDay
|
|
71
|
+
{
|
|
72
|
+
/**
|
|
73
|
+
* Sunday
|
|
74
|
+
*/
|
|
75
|
+
Sunday = 0,
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Monday
|
|
79
|
+
*/
|
|
80
|
+
Monday = 1,
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Tuesday
|
|
84
|
+
*/
|
|
85
|
+
Tuesday = 2,
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Wednesday
|
|
89
|
+
*/
|
|
90
|
+
Wednesday = 3,
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Thursday
|
|
94
|
+
*/
|
|
95
|
+
Thursday = 4,
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Friday
|
|
99
|
+
*/
|
|
100
|
+
Friday = 5,
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Saturday
|
|
104
|
+
*/
|
|
105
|
+
Saturday = 6
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* An utility function that calculates the difference between two dates.
|
|
110
|
+
* The difference can be expressed in different time units.
|
|
111
|
+
*
|
|
112
|
+
* ```ts
|
|
113
|
+
* const start = new Date("2025-01-01");
|
|
114
|
+
* const end = new Date("2025-01-31");
|
|
115
|
+
*
|
|
116
|
+
* dateDifference(start, end, TimeUnit.Minute); // 43200
|
|
117
|
+
* ```
|
|
118
|
+
*
|
|
119
|
+
* @param start The start date.
|
|
120
|
+
* @param end The end date.
|
|
121
|
+
* @param unit The time unit to express the difference. `TimeUnit.Day` by default.
|
|
122
|
+
*
|
|
123
|
+
* @returns The difference between the two dates in the specified time unit.
|
|
124
|
+
*/
|
|
18
125
|
export function dateDifference(start: string | Date, end: string | Date, unit = TimeUnit.Day): number
|
|
19
126
|
{
|
|
127
|
+
let _round: (value: number) => number;
|
|
128
|
+
|
|
20
129
|
start = new Date(start);
|
|
21
130
|
end = new Date(end);
|
|
22
131
|
|
|
23
|
-
|
|
132
|
+
if (start < end) { _round = Math.floor; }
|
|
133
|
+
else { _round = Math.ceil; }
|
|
134
|
+
|
|
135
|
+
return _round((end.getTime() - start.getTime()) / unit);
|
|
24
136
|
}
|
|
25
137
|
|
|
26
|
-
|
|
138
|
+
/**
|
|
139
|
+
* An utility function that generates an iterator over a range of dates.
|
|
140
|
+
* The step between the dates can be expressed in different time units.
|
|
141
|
+
*
|
|
142
|
+
* ```ts
|
|
143
|
+
* const start = new Date("2025-01-01");
|
|
144
|
+
* const end = new Date("2025-01-31");
|
|
145
|
+
*
|
|
146
|
+
* for (const date of dateRange(start, end, TimeUnit.Week))
|
|
147
|
+
* {
|
|
148
|
+
* date.toISOString().slice(8, 10); // "01", "08", "15", "22", "29"
|
|
149
|
+
* }
|
|
150
|
+
* ```
|
|
151
|
+
*
|
|
152
|
+
* @param start The start date (included).
|
|
153
|
+
* @param end
|
|
154
|
+
* The end date (excluded).
|
|
155
|
+
*
|
|
156
|
+
* Must be greater than the start date. Otherwise, a {@link RangeException} will be thrown.
|
|
157
|
+
*
|
|
158
|
+
* @param step The time unit to express the step between the dates. `TimeUnit.Day` by default.
|
|
159
|
+
*
|
|
160
|
+
* @returns A {@link SmartIterator} object that generates the dates in the range.
|
|
161
|
+
*/
|
|
162
|
+
export function dateRange(start: string | Date, end: string | Date, step = TimeUnit.Day): SmartIterator<Date>
|
|
27
163
|
{
|
|
28
|
-
start
|
|
29
|
-
end = new Date(end);
|
|
164
|
+
if (start >= end) { throw new RangeException("The end date must be greater than the start date."); }
|
|
30
165
|
|
|
31
166
|
return new SmartIterator<Date>(function* ()
|
|
32
167
|
{
|
|
33
|
-
const endTime = end.getTime();
|
|
168
|
+
const endTime = new Date(end).getTime();
|
|
34
169
|
|
|
35
|
-
let unixTime: number = start.getTime();
|
|
170
|
+
let unixTime: number = new Date(start).getTime();
|
|
36
171
|
while (unixTime < endTime)
|
|
37
172
|
{
|
|
38
173
|
yield new Date(unixTime);
|
|
39
174
|
|
|
40
|
-
unixTime +=
|
|
175
|
+
unixTime += step;
|
|
41
176
|
}
|
|
42
177
|
});
|
|
43
178
|
}
|
|
44
179
|
|
|
180
|
+
/**
|
|
181
|
+
* An utility function that rounds a date to the nearest time unit.
|
|
182
|
+
* The rounding can be expressed in different time units.
|
|
183
|
+
*
|
|
184
|
+
* ```ts
|
|
185
|
+
* const date = new Date("2025-01-01T12:34:56.789Z");
|
|
186
|
+
*
|
|
187
|
+
* dateRound(date, TimeUnit.Hour); // 2025-01-01T12:00:00.000Z
|
|
188
|
+
* ```
|
|
189
|
+
*
|
|
190
|
+
* @param date The date to round.
|
|
191
|
+
* @param unit
|
|
192
|
+
* The time unit to express the rounding. `TimeUnit.Day` by default.
|
|
193
|
+
*
|
|
194
|
+
* Must be greater than a millisecond and less than or equal to a day.
|
|
195
|
+
* Otherwise, a {@link RangeException} will be thrown.
|
|
196
|
+
*
|
|
197
|
+
* @returns The rounded date.
|
|
198
|
+
*/
|
|
45
199
|
export function dateRound(date: string | Date, unit = TimeUnit.Day): Date
|
|
46
200
|
{
|
|
47
|
-
|
|
201
|
+
if (unit <= TimeUnit.Millisecond)
|
|
202
|
+
{
|
|
203
|
+
throw new RangeException(
|
|
204
|
+
"Rounding a timestamp by milliseconds or less makes no sense." +
|
|
205
|
+
"Use the timestamp value directly instead."
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
if (unit > TimeUnit.Day)
|
|
209
|
+
{
|
|
210
|
+
throw new RangeException(
|
|
211
|
+
"Rounding by more than a day leads to unexpected results. " +
|
|
212
|
+
"Consider using other methods to round dates by weeks, months or years."
|
|
213
|
+
);
|
|
214
|
+
}
|
|
48
215
|
|
|
216
|
+
date = new Date(date);
|
|
49
217
|
return new Date(Math.floor(date.getTime() / unit) * unit);
|
|
50
218
|
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* An utility function that gets the week of a date.
|
|
222
|
+
* The first day of the week can be optionally specified.
|
|
223
|
+
*
|
|
224
|
+
* ```ts
|
|
225
|
+
* const date = new Date("2025-01-01");
|
|
226
|
+
*
|
|
227
|
+
* getWeek(date, WeekDay.Monday); // 2024-12-30
|
|
228
|
+
* ```
|
|
229
|
+
*
|
|
230
|
+
* @param date The date to get the week of.
|
|
231
|
+
* @param firstDay The first day of the week. `WeekDay.Sunday` by default.
|
|
232
|
+
*
|
|
233
|
+
* @returns The first day of the week of the specified date.
|
|
234
|
+
*/
|
|
235
|
+
export function getWeek(date: string | Date, firstDay = WeekDay.Sunday): Date
|
|
236
|
+
{
|
|
237
|
+
date = new Date(date);
|
|
238
|
+
|
|
239
|
+
const startCorrector = 7 - firstDay;
|
|
240
|
+
const weekDayIndex = (date.getUTCDay() + startCorrector) % 7;
|
|
241
|
+
const firstDayTime = date.getTime() - (TimeUnit.Day * weekDayIndex);
|
|
242
|
+
|
|
243
|
+
return dateRound(new Date(firstDayTime));
|
|
244
|
+
}
|