time-machine-js 1.0.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.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # time-machine-js
2
+
3
+ A zero-dependency, framework-agnostic utility that monkey-patches `Date.now()` globally to simulate a different point in time. Works identically in browser and Node.js environments.
4
+
5
+ ## Features
6
+
7
+ - **Zero dependencies**: No external dependencies required.
8
+ - **Framework agnostic**: Use it with React, Vue, Vanilla JS, or Node.js.
9
+ - **Global patch**: Affects `Date.now()` globally, ensuring `new Date()` and other time-dependent utilities work as expected.
10
+ - **Dual Exports**: Ships with both ESM and CJS support.
11
+ - **TypeScript**: Fully typed with included definitions.
12
+
13
+ ## Installation
14
+
15
+ ```bash
16
+ npm install time-machine-js
17
+ ```
18
+
19
+ ## API
20
+
21
+ The core API consists of four main functions:
22
+
23
+ ### `travel(timestamp: number, mode: 'flowing' | 'frozen'): void`
24
+ Moves the environment time to the specified Unix timestamp.
25
+ - `flowing`: Time continues to advance normally from the specified point.
26
+ - `frozen`: Time remains fixed at the specify timestamp.
27
+
28
+ ### `returnToPresent(): void`
29
+ Restores the original `Date.now` and clears internal state.
30
+
31
+ ### `getOffset(): number | null`
32
+ Returns the current offset in ms (flowing mode) or the frozen timestamp (frozen mode). Returns `null` if inactive.
33
+
34
+ ### `isActive(): boolean`
35
+ Returns `true` if the time machine is currently active.
36
+
37
+ ## Persistence (Browser Only)
38
+
39
+ For environments with `localStorage`, you can manually save and restore the time machine state across page reloads:
40
+
41
+ ### `save(storageKey?: string): void`
42
+ Writes the current mode and offset/timestamp to `localStorage`. Defaults to `__timeMachine__`.
43
+
44
+ ### `restore(storageKey?: string): boolean`
45
+ Reads the state from `localStorage` and activates it. Returns `true` if a state was found and applied.
46
+
47
+ ## Usage
48
+
49
+ ### Basic Example
50
+ ```javascript
51
+ import { travel, returnToPresent } from 'time-machine-js';
52
+
53
+ // Travel to Jan 1st, 2025
54
+ travel(new Date('2025-01-01').getTime(), 'frozen');
55
+ console.log(new Date().toISOString()); // 2025-01-01T00:00:00.000Z
56
+
57
+ returnToPresent();
58
+ console.log(new Date().getFullYear()); // Current year
59
+ ```
60
+
61
+ ### Persistence
62
+ ```javascript
63
+ import { restore, save, travel } from 'time-machine-js';
64
+
65
+ // On app initialization
66
+ restore();
67
+
68
+ // Later
69
+ travel(Date.now() + 3600000, 'flowing');
70
+ save();
71
+ ```
72
+
73
+ ## Caveats
74
+
75
+ ### `new Date()`
76
+ In most modern JavaScript environments (V8, Node.js), the `Date` constructor uses `Date.now()` internally to determine the current time. This means monkey-patching `Date.now()` is sufficient to "travel" for both `Date.now()` and `new Date()`.
77
+
78
+ ### `performance.now()`
79
+ `performance.now()` is **not** patched by this utility, as it represents a monotonic clock used for high-resolution profiling rather than wall-clock time.
80
+
81
+ ## License
82
+
83
+ MIT
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Valid modes for the time machine.
3
+ * - 'flowing': Time continues to advance normally but from a specified offset.
4
+ * - 'frozen': Time remains fixed at the specify timestamp.
5
+ */
6
+ type TimeMachineMode = 'flowing' | 'frozen';
7
+ /**
8
+ * Monkey-patches Date.now() to simulate a different point in time.
9
+ *
10
+ * @param timestamp - The Unix timestamp in milliseconds to travel to.
11
+ * @param mode - The mode of travel: 'flowing' or 'frozen'.
12
+ *
13
+ * @example
14
+ * travel(new Date('2025-01-01').getTime(), 'frozen');
15
+ */
16
+ declare function travel(timestamp: number, mode: TimeMachineMode): void;
17
+ /**
18
+ * Restores the original Date.now and clears the internal state.
19
+ */
20
+ declare function returnToPresent(): void;
21
+ /**
22
+ * Returns the current offset in milliseconds when in 'flowing' mode,
23
+ * the frozen timestamp when in 'frozen' mode, or null if inactive.
24
+ *
25
+ * @returns The offset, frozen timestamp, or null.
26
+ */
27
+ declare function getOffset(): number | null;
28
+ /**
29
+ * Returns whether the time machine is currently active.
30
+ *
31
+ * @returns True if active, false otherwise.
32
+ */
33
+ declare function isActive(): boolean;
34
+ /**
35
+ * Saves the current time machine state to localStorage (browser only).
36
+ *
37
+ * @param storageKey - The key to use in localStorage. Defaults to '__timeMachine__'.
38
+ */
39
+ declare function save(storageKey?: string): void;
40
+ /**
41
+ * Restores the time machine state from localStorage (browser only).
42
+ *
43
+ * @param storageKey - The key to search for in localStorage. Defaults to '__timeMachine__'.
44
+ * @returns True if state was found and restored, false otherwise.
45
+ */
46
+ declare function restore(storageKey?: string): boolean;
47
+
48
+ export { type TimeMachineMode, getOffset, isActive, restore, returnToPresent, save, travel };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Valid modes for the time machine.
3
+ * - 'flowing': Time continues to advance normally but from a specified offset.
4
+ * - 'frozen': Time remains fixed at the specify timestamp.
5
+ */
6
+ type TimeMachineMode = 'flowing' | 'frozen';
7
+ /**
8
+ * Monkey-patches Date.now() to simulate a different point in time.
9
+ *
10
+ * @param timestamp - The Unix timestamp in milliseconds to travel to.
11
+ * @param mode - The mode of travel: 'flowing' or 'frozen'.
12
+ *
13
+ * @example
14
+ * travel(new Date('2025-01-01').getTime(), 'frozen');
15
+ */
16
+ declare function travel(timestamp: number, mode: TimeMachineMode): void;
17
+ /**
18
+ * Restores the original Date.now and clears the internal state.
19
+ */
20
+ declare function returnToPresent(): void;
21
+ /**
22
+ * Returns the current offset in milliseconds when in 'flowing' mode,
23
+ * the frozen timestamp when in 'frozen' mode, or null if inactive.
24
+ *
25
+ * @returns The offset, frozen timestamp, or null.
26
+ */
27
+ declare function getOffset(): number | null;
28
+ /**
29
+ * Returns whether the time machine is currently active.
30
+ *
31
+ * @returns True if active, false otherwise.
32
+ */
33
+ declare function isActive(): boolean;
34
+ /**
35
+ * Saves the current time machine state to localStorage (browser only).
36
+ *
37
+ * @param storageKey - The key to use in localStorage. Defaults to '__timeMachine__'.
38
+ */
39
+ declare function save(storageKey?: string): void;
40
+ /**
41
+ * Restores the time machine state from localStorage (browser only).
42
+ *
43
+ * @param storageKey - The key to search for in localStorage. Defaults to '__timeMachine__'.
44
+ * @returns True if state was found and restored, false otherwise.
45
+ */
46
+ declare function restore(storageKey?: string): boolean;
47
+
48
+ export { type TimeMachineMode, getOffset, isActive, restore, returnToPresent, save, travel };
package/dist/index.js ADDED
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ getOffset: () => getOffset,
24
+ isActive: () => isActive,
25
+ restore: () => restore,
26
+ returnToPresent: () => returnToPresent,
27
+ save: () => save,
28
+ travel: () => travel
29
+ });
30
+ module.exports = __toCommonJS(index_exports);
31
+ var _realDateNow = Date.now.bind(Date);
32
+ var state = {
33
+ isActive: false,
34
+ mode: null,
35
+ offset: null,
36
+ frozenTimestamp: null
37
+ };
38
+ function travel(timestamp, mode) {
39
+ state.isActive = true;
40
+ state.mode = mode;
41
+ if (mode === "frozen") {
42
+ state.frozenTimestamp = timestamp;
43
+ state.offset = null;
44
+ Date.now = () => state.frozenTimestamp;
45
+ } else {
46
+ state.frozenTimestamp = null;
47
+ state.offset = timestamp - _realDateNow();
48
+ Date.now = () => _realDateNow() + state.offset;
49
+ }
50
+ }
51
+ function returnToPresent() {
52
+ state.isActive = false;
53
+ state.mode = null;
54
+ state.offset = null;
55
+ state.frozenTimestamp = null;
56
+ Date.now = _realDateNow;
57
+ }
58
+ function getOffset() {
59
+ if (!state.isActive) return null;
60
+ return state.mode === "frozen" ? state.frozenTimestamp : state.offset;
61
+ }
62
+ function isActive() {
63
+ return state.isActive;
64
+ }
65
+ function save(storageKey = "__timeMachine__") {
66
+ if (typeof localStorage === "undefined" || !state.isActive) return;
67
+ const savedState = {
68
+ mode: state.mode,
69
+ value: state.mode === "frozen" ? state.frozenTimestamp : state.offset
70
+ };
71
+ localStorage.setItem(storageKey, JSON.stringify(savedState));
72
+ }
73
+ function restore(storageKey = "__timeMachine__") {
74
+ if (typeof localStorage === "undefined") return false;
75
+ const data = localStorage.getItem(storageKey);
76
+ if (!data) return false;
77
+ try {
78
+ const savedState = JSON.parse(data);
79
+ if (savedState.mode === "frozen") {
80
+ travel(savedState.value, "frozen");
81
+ } else {
82
+ const targetTimestamp = _realDateNow() + savedState.value;
83
+ travel(targetTimestamp, "flowing");
84
+ }
85
+ return true;
86
+ } catch {
87
+ return false;
88
+ }
89
+ }
90
+ // Annotate the CommonJS export names for ESM import in node:
91
+ 0 && (module.exports = {
92
+ getOffset,
93
+ isActive,
94
+ restore,
95
+ returnToPresent,
96
+ save,
97
+ travel
98
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,68 @@
1
+ // src/index.ts
2
+ var _realDateNow = Date.now.bind(Date);
3
+ var state = {
4
+ isActive: false,
5
+ mode: null,
6
+ offset: null,
7
+ frozenTimestamp: null
8
+ };
9
+ function travel(timestamp, mode) {
10
+ state.isActive = true;
11
+ state.mode = mode;
12
+ if (mode === "frozen") {
13
+ state.frozenTimestamp = timestamp;
14
+ state.offset = null;
15
+ Date.now = () => state.frozenTimestamp;
16
+ } else {
17
+ state.frozenTimestamp = null;
18
+ state.offset = timestamp - _realDateNow();
19
+ Date.now = () => _realDateNow() + state.offset;
20
+ }
21
+ }
22
+ function returnToPresent() {
23
+ state.isActive = false;
24
+ state.mode = null;
25
+ state.offset = null;
26
+ state.frozenTimestamp = null;
27
+ Date.now = _realDateNow;
28
+ }
29
+ function getOffset() {
30
+ if (!state.isActive) return null;
31
+ return state.mode === "frozen" ? state.frozenTimestamp : state.offset;
32
+ }
33
+ function isActive() {
34
+ return state.isActive;
35
+ }
36
+ function save(storageKey = "__timeMachine__") {
37
+ if (typeof localStorage === "undefined" || !state.isActive) return;
38
+ const savedState = {
39
+ mode: state.mode,
40
+ value: state.mode === "frozen" ? state.frozenTimestamp : state.offset
41
+ };
42
+ localStorage.setItem(storageKey, JSON.stringify(savedState));
43
+ }
44
+ function restore(storageKey = "__timeMachine__") {
45
+ if (typeof localStorage === "undefined") return false;
46
+ const data = localStorage.getItem(storageKey);
47
+ if (!data) return false;
48
+ try {
49
+ const savedState = JSON.parse(data);
50
+ if (savedState.mode === "frozen") {
51
+ travel(savedState.value, "frozen");
52
+ } else {
53
+ const targetTimestamp = _realDateNow() + savedState.value;
54
+ travel(targetTimestamp, "flowing");
55
+ }
56
+ return true;
57
+ } catch {
58
+ return false;
59
+ }
60
+ }
61
+ export {
62
+ getOffset,
63
+ isActive,
64
+ restore,
65
+ returnToPresent,
66
+ save,
67
+ travel
68
+ };
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "time-machine-js",
3
+ "version": "1.0.0",
4
+ "description": "A zero-dependency utility to monkey-patch Date.now() globally.",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.mjs",
12
+ "require": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "README.md"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup src/index.ts --format cjs,esm --dts --clean",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "lint": "tsc --noEmit"
24
+ },
25
+ "keywords": [
26
+ "time",
27
+ "travel",
28
+ "date",
29
+ "manipulation"
30
+ ],
31
+ "author": "",
32
+ "license": "MIT",
33
+ "devDependencies": {
34
+ "tsup": "^8.0.2",
35
+ "typescript": "^5.3.3",
36
+ "vitest": "^1.3.1"
37
+ }
38
+ }