@trap_stevo/timetide
Version:
Master the flow of time with the ultimate solution for precision scheduling, real-time synchronization, and temporal control. Designed for developers who demand flexibility and power, this system blends intuitive time parsing, dynamic countdowns, and pers
547 lines (408 loc) • 15.5 kB
Markdown
# ⏱️ @trap_stevo/timetide
**Master the flow of time with the ultimate solution for precision scheduling, real-time synchronization, and temporal control.**
Designed for developers who demand flexibility and power, TimeTide blends intuitive time parsing, dynamic countdowns, persistent task handling, natural-language date interpretation, and nanosecond-to-millennia resolution—all into a seamless, elegant toolkit.
Schedule events using human-readable commands, manage durations with clarity, and broadcast tick events to dashboards or distributed systems. Whether you build responsive interfaces, backend automation, or temporal workflows, **TimeTide equips you to orchestrate time itself—with legendary elegance and unmatched control.**
---
## 🚀 Features
- ⏱️ **Human-readable durations** — `"30m"`, `"1.5h"`, `"2w"`, `"10y"`, `"3gen"`, `"1mil"`
- 📅 **Natural-language dates** — `"next Friday"`, `"in 3 days"`, `"end of day"`, `"tomorrow"`
- 🔁 **Repeating & persistent timers** — recurring jobs that can survive restarts
- 📦 **Named timers** — pause, resume, extend, reduce, reschedule, chain follow-ups
- 📡 **Tick streams** — global ticks for dashboards, schedulers, analytics
- 🎯 **Threshold comparison helpers** — `exceedsDuration`, `exceedsTime`
- 🧠 **Advanced parsing helpers** — `normalizeDateInput`, `parseDateTime`
- 🧮 **High-precision units** — nanoseconds → millennia
- ⚙️ **Configurable persistence** — store timers anywhere
---
# ⚙️ Initialization
```js
const TimeTide = require("@trap_stevo/timetide");
TimeTide.initializeTimers({
persistPath : "./.timers.json",
disablePersistence : false
});
```
### Initialization Options
| Key | Type | Description | Default |
|-----|------|-------------|---------|
| `persistPath` | `string` | Where persistent timers are stored | `"./.timers.json"` |
| `disablePersistence` | `boolean` | Disable reading/writing named timers | `false` |
| `onLoadTimersError` | `function` | Called if persisted state cannot be loaded | `undefined` |
---
# 📡 Scheduling API (One-Time & Named Tasks)
### Methods
| Method | Description | Persisted? |
|--------|-------------|------------|
| `schedule(name, dur, cb, meta?)` | Named timer with persistence | ✔ |
| `schedule(dur, cb)` | Simple one-time timer | ❌ |
| `countdown(dur, cb)` | Alias of `schedule(dur, cb)` | ❌ |
| `then(name, cb)` | Chain a follow-up after a named timer fires | ✔ |
| `clear(idOrName)` | Cancel timer by ID or name | ✔ |
| `extend(name, dur)` | Add time to a named timer | ✔ |
| `reduce(name, dur)` | Subtract time from a named timer | ✔ |
| `reschedule(name, dur)` | Replace remaining time with new duration | ✔ |
| `pause(name)` | Freeze a named timer and store remaining time | ✔ |
| `resume(name)` | Resume a paused named timer | ✔ |
| `pauseAll()` | Pause all named timers | ✔ |
| `resumeAll()` | Resume all named timers | ✔ |
### Example — Named, persistent timer
```js
TimeTide.schedule("backupJob", "45m", () => {
console.log("Backup finished!");
}, { priority : "high" });
TimeTide.then("backupJob", () => {
console.log("Follow-up job triggered.");
});
```
---
# 🔁 Recurring Tasks
### Non-persistent recurring tasks
| Method | Description |
|--------|-------------|
| `onEvery(interval, cb)` | Run a callback every interval |
| `repeat(interval, cb)` | Alias of `onEvery` |
```js
const id = TimeTide.onEvery("10s", () => {
console.log("Heartbeat");
});
// Later
TimeTide.clear(id);
```
### Persistent recurring tasks (cron-like loop)
```js
function hourly()
{
console.log("Hourly job fired");
}
function scheduleHourly()
{
TimeTide.schedule("hourlyJob", "1h", () => {
hourly();
scheduleHourly();
});
}
scheduleHourly();
```
- Recurs forever
- Can be paused / resumed via `pause("hourlyJob")` / `resume("hourlyJob")`
- Keeps the same named timer
#### Example — Auto-grading loop (safer pattern)
```js
function autoGrade()
{
console.log("Auto-grade fired at", new Date().toISOString());
}
function chainAutoGrade()
{
TimeTide.schedule("auto-grade", "1m", () => {
try
{
autoGrade();
}
finally
{
// Always re-schedule, even if autoGrade throws
chainAutoGrade();
}
});
}
chainAutoGrade();
```
---
# 📡 Tick Events
| Method | Description |
|--------|-------------|
| `startTick(interval)` | Create a global ticker emitting events |
| `stopTick(interval)` | Stop a global ticker |
| `events.on("tick:interval", cb)` | Subscribe to a specific tick label |
| `events.on("tick", cb)` | Subscribe to all ticks, receives label |
```js
TimeTide.startTick("1s");
TimeTide.events.on("tick:1s", () => {
console.log("Second tick");
});
TimeTide.events.on("tick", (label) => {
// label could be "1s", "5m", "1000ms", etc.
});
```
Stop:
```js
TimeTide.stopTick("1s");
```
---
# ⏳ Countdown & Expiration Helpers
| Method | Description |
|--------|-------------|
| `countdown(dur, cb)` | One-shot countdown (alias of `schedule(dur, cb)`) |
| `expired(date)` | `true` if `date` is earlier than now |
| `getTimeUntil(date)` | Milliseconds until given date |
| `addTime(date, dur)` | Returns new Date: `date + duration` |
| `subtractTime(date, dur)` | Returns new Date: `date - duration` |
### Example — Time until a deadline
```js
const deadline = "2025-12-01T17:00:00Z";
const msUntil = TimeTide.getTimeUntil(deadline);
console.log("Remaining:", TimeTide.toReadable(msUntil));
if (TimeTide.expired(deadline))
{
console.log("Deadline already passed");
}
```
---
# ⚖️ Duration & Time Comparison
## `exceedsDuration(timeInput, threshold, options?)`
Generic threshold comparison between a time and a reference.
**Parameters**
| Name | Type | Description |
|------|------|-------------|
| `timeInput` | `Date \| number \| string` | Target time to compare |
| `threshold` | `number \| string` | Milliseconds or duration string (`"30m"`, `"1h"`, `"3d"`, `"5y"`, `"2gen"`, etc.) |
| `options.reference` | `Date \| number \| string` | Time to compare against (default: now) |
**Return**
```js
{
valid : boolean, // false if inputs could not be parsed
exceeded : boolean, // true if |target - reference| > threshold
diffMs : number | null, // absolute difference in milliseconds
thresholdMs : number | null
}
```
**Example**
```js
const result = TimeTide.exceedsDuration("in 90m", "1h");
if (result.valid && result.exceeded)
{
console.log("Over 1 hour difference");
}
else if (result.valid)
{
console.log("Within 1 hour:", TimeTide.toReadable(result.diffMs));
}
```
## `exceedsTime(timeInput, threshold, options?)`
Alias of `exceedsDuration`.
```js
const result = TimeTide.exceedsTime(lastPingAt, "30m", { reference : Date.now() });
if (result.exceeded)
{
console.log("Last ping is older than 30 minutes");
}
```
### Example — Eligibility gate within 1 hour
```js
function inspectEligibility(gradeAtInput)
{
// gradeAtInput can be Date, ms, numeric string, ISO string, or natural language
const result = TimeTide.exceedsDuration(gradeAtInput, "1h", { reference : Date.now() });
if (!result.valid)
{
return { eligible : false, reason : "Invalid gradeAt input" };
}
if (result.exceeded)
{
return {
eligible : true,
reason : "Grade time exceeded 1 hour threshold"
};
}
return {
eligible : false,
reason : `Still within 1 hour window (${TimeTide.toReadable(result.diffMs)} difference)`
};
}
```
---
# 🧠 Time Parsing, Conversion & Utilities
All time utilities are provided directly on `TimeTide`.
| Method | Description |
|--------|-------------|
| `parseDuration(str)` | Parse `"1h 30m"` → ms |
| `toMilliseconds(str)` | Duration string → milliseconds |
| `toSeconds(str)` | Duration string → seconds |
| `toReadable(ms)` | Convert ms to `"2h 3m 10s"` style string |
| `toETA(seconds)` | Convert seconds (numeric) to a readable ETA string |
| `parseDate(str)` | Parse natural-language / ISO date into `Date` |
| `normalizeDateInput(input)` | Normalize Date / ms / numeric / string input into `Date` or `null` |
| `parseDateTime(input, unit)` | Parse date-like input into a specific time unit |
| `validDate(input)` | Returns `true` if input can be parsed into a valid date |
---
## `parseDateTime(input, unit = "ms")`
Convert any supported date-like input into the desired time unit using a **unit + alias map** that mirrors the duration model.
**Supported input types**
- `Date`
- millisecond `number`
- numeric timestamp string (e.g. `"1763754901019"`)
- ISO date string
- natural language (`"in 1 hour"`, `"tomorrow"`, `"next Friday 6pm"`, etc.)
**Supported units**
| Unit | Aliases | Output |
|------|---------|--------|
| `ms` | `millisecond`, `milliseconds` | Milliseconds since epoch |
| `s` | `sec`, `second`, `seconds` | Whole seconds since epoch |
| `m` | `min`, `minute`, `minutes` | Whole minutes since epoch |
| `h` | `hr`, `hour`, `hours` | Whole hours since epoch |
| `d` | `day`, `days` | Whole days since epoch |
| `w` | `week`, `weeks` | Whole weeks since epoch |
| `mo` | `month`, `months` | Approx. months since epoch |
| `y` | `yr`, `year`, `years` | Approx. years since epoch |
| `dec` | `decade`, `decades` | Approx. decades since epoch |
| `cen` | `century`, `centuries` | Approx. centuries since epoch |
| `gen` | `generation`, `generations` | Approx. generations since epoch |
| `mil` | `millennium`, `millennia` | Approx. millennia since epoch |
| `iso` | — | ISO string |
| `date` | — | `Date` object |
Internally, `parseDateTime`:
1. Normalizes the input into a `Date`
2. Converts that `Date` to milliseconds (`ms`)
3. Uses a **factor map + alias map** to divide into the requested unit
4. Returns a rounded-down integer for numeric units
**Examples**
```js
// 1) Millisecond timestamp from ISO string
const ts = TimeTide.parseDateTime("2025-11-21T20:46:59.495Z", "ms");
// 2) Unix-style seconds from natural language
const unix = TimeTide.parseDateTime("next Friday", "seconds"); // "seconds" → "s"
// 3) Days since epoch
const days = TimeTide.parseDateTime("today", "days");
// 4) ISO from natural language
const iso = TimeTide.parseDateTime("in 2 weeks", "iso");
// 5) Using a numeric ms timestamp input and converting to days
const createdAtMs = Date.now() - TimeTide.toMilliseconds("3d 2h");
const ageDays = TimeTide.parseDateTime(createdAtMs, "days");
// ⭐ 6) Raw millisecond timestamp directly from DB / storage
const dbTs = 1763754901019;
const normalized = TimeTide.parseDateTime(dbTs, "ms");
console.log(normalized); // → 1763754901019
// 7) Gating based on a future point in time
const gradeAt = TimeTide.parseDateTime("in 1 hour", "ms");
const remainingMs = gradeAt - Date.now();
console.log("Remaining until grade:", TimeTide.toReadable(remainingMs));
```
---
# 🔥 Supported Duration Units
TimeTide supports mixed & stacked units in `parseDuration` / `toMilliseconds`:
```js
"1h 30m"
"2d 4h 20m 5s"
"1.5h"
"3gen 5y 2mo"
"10y 2w 3d 4h 5m 6s"
```
### Unit Table
| Unit | Tokens | Approximate ms |
|------|--------|----------------|
| Nanoseconds | `ns`, `nanoseconds` | `1e-6` |
| Milliseconds | `ms`, `milliseconds` | `1` |
| Seconds | `s`, `seconds` | `1000` |
| Minutes | `m`, `minutes` | `60000` |
| Hours | `h`, `hours` | `3600000` |
| Days | `d`, `days` | `86400000` |
| Weeks | `w`, `weeks` | `604800000` |
| Months | `mo`, `months` | `2629800000` |
| Years | `y`, `years` | `31557600000` |
| Decades | `dec`, `decades` | `315576000000` |
| Centuries | `cen`, `centuries` | `3155760000000` |
| Generations | `gen`, `generations` | `788940000000` |
| Millennia | `mil`, `millennia` | `31557600000000` |
---
# 🌙 Natural-Language Date Phrases
Examples of phrases `parseDate` / `parseDateTime` can understand include:
- `"now"`, `"today"`, `"tonight"`
- `"end of day"`, `"midnight"`, `"noon"`
- `"yesterday"`, `"tomorrow"`
- `"in 3 hours"`, `"in 5 days"`, `"in 2 weeks"`
- `"10 minutes ago"`, `"3 days ago"`
- `"next Monday"`, `"last Friday"`, `"this Friday"`
- `"this month"`, `"next month"`, `"last year"`
- `"next weekend"`
- `"January 6, 2025 13:45"`
- `"2025-01-06T13:45:00Z"`
---
## 📦 Installation
```bash
npm install @trap_stevo/timetide
```
---
## ⚡ Quick Start (Step-by-Step)
### 1) Initialize TimeTide
```js
const TimeTide = require("@trap_stevo/timetide");
TimeTide.initializeTimers({
persistPath : "./.timers.json",
disablePersistence : false
});
```
### 2) Schedule a simple one-time task
```js
TimeTide.schedule("10s", () => {
console.log("⏰ Fired after 10 seconds");
});
```
### 3) Create a named, persistent task
```js
TimeTide.schedule("daily-report", "24h", () => {
console.log("📊 Daily report generated");
}, { type : "report", priority : "high" });
TimeTide.then("daily-report", () => {
console.log("✅ Daily report follow-up");
});
```
If your app restarts and you call `initializeTimers` again with the same `persistPath`, `daily-report` will be restored (as long as it hasn’t fired yet and its callback is registered again).
### 4) Create a recurring heartbeat
```js
const heartbeatID = TimeTide.onEvery("5s", () => {
console.log("💓 Heartbeat:", new Date().toISOString());
});
// Later:
TimeTide.clear(heartbeatID);
```
### 5) Use natural-language dates and duration comparison
```js
// "next Friday 18:00" as a millisecond timestamp
const launchAtMs = TimeTide.parseDateTime("next Friday 6pm", "ms");
const remainingMs = launchAtMs - Date.now();
console.log("Time until launch:", TimeTide.toReadable(remainingMs));
// Check if something is more than 1 hour away from now
const result = TimeTide.exceedsDuration("in 90m", "1h");
if (result.valid && result.exceeded)
{
console.log("⏳ More than 1 hour difference");
}
```
---
# 🗂️ Inspect Active Timers
```js
const timers = TimeTide.getTimers();
console.log(timers);
/*
{
backupJob : {
createdAt : Date,
remainingMs : 120000,
readable : "2m",
meta : { priority : "high" }
}
}
*/
```
---
# 🧼 Cleanup Pattern
```js
const id = TimeTide.onEvery("5s", () => {
console.log("tick");
});
TimeTide.startTick("1m");
function cleanup()
{
TimeTide.clear(id);
TimeTide.stopTick("1m");
}
process.on("SIGINT", cleanup);
process.on("SIGTERM", cleanup);
```
---
# 📜 License
See [LICENSE.md](./LICENSE.md)
---
# ⏱️ Control Time. Shape Systems.
From millisecond-precision scheduling to natural-language parsing, from duration comparison to recurring, persistent, restart-safe timers—**TimeTide** gives you complete command over time itself.