Timeleap parses duration strings and shifts UTC timestamps forward or backward. It validates durations, advances a timestamp to the next or previous point, and builds cumulative backoff schedules. Fixed units (second to week) resolve to millisecond math, while calendar units (month, year) shift the UTC date and overflow to the next month when the day does not exist (for example Jan 31 plus 1 month lands on Mar 2 in a leap year or Mar 3 otherwise).
| Condition | Timeleap API |
|---|---|
| Validate duration input from a form, API request, or config file | parse |
| Set token, session, or cache TTL expiry from a duration string | next |
| Schedule the next run for a recurring job or cron-style task | next |
| Derive a subscription renewal date across months and years | next |
| Compute a rate-limit reset time from a fixed window | next |
| Skip weekends or blocked hours when picking the next valid slot | next |
| Roll a timestamp backward to a previous billing or reporting cycle | prev |
| Build a time range by pairing a backward start with a forward end | prev |
| Compute retry timestamps for failed calls using exponential growth | backoff |
| Generate a series of staged reminders or notifications before an event | backoff |
See USECASE.md for detailed scenarios and end-to-end examples.
Note
Prerequisites: For npm use Node.js (e.g. nodejs.org).
npm:
npm install @neabyte/timeleap
CDN (jsDelivr/unpkg/esm.sh):
<script type="module"> import timeleap from 'https://cdn.jsdelivr.net/npm/@neabyte/timeleap/dist/index.mjs' </script>
Or via esm.sh:
<script type="module"> import timeleap from 'https://esm.sh/@neabyte/timeleap' </script>
Or via importmap:
<script type="importmap"> { "imports": { "@neabyte/timeleap": "https://cdn.jsdelivr.net/npm/@neabyte/timeleap/dist/index.mjs" } } </script> <script type="module"> import timeleap from '@neabyte/timeleap' </script>
import timeleap from '@neabyte/timeleap' // Pick a fixed UTC base const base = Date.UTC(2077, 0, 1) // Validate a duration string timeleap.parse('1 hour') // true // Shift forward to the next point timeleap.next({ time: base, duration: '1 hour' }) // 3376688400000 // Shift backward to the previous point timeleap.prev({ time: base, duration: '1 hour' }) // 3376681200000 // Build a cumulative backoff schedule timeleap.backoff({ time: base, duration: '1 hour', type: 'exponential', limit: 3 }) // [3376688400000, 3376695600000, 3376710000000] // Shift forward but skip weekends timeleap.next({ time: base, duration: '1 day', skip: { day: ['saturday', 'sunday'] } }) // 3376944000000
Every unit accepts singular, plural, and short forms, with optional spacing:
timeleap.parse('1h') // short timeleap.parse('1 h') // short with space timeleap.parse('2 days') // plural with space timeleap.parse('3weeks') // plural no space timeleap.parse('2 months') // plural with space timeleap.parse('10minutes') // plural no space timeleap.parse('1x') // false, invalid unit
import timeleap from '@neabyte/timeleap' // Pick a fixed UTC base const base = Date.UTC(2077, 0, 1) const iso = (time: number): string => new Date(time).toISOString() // Shift and build a schedule const next = timeleap.next({ time: base, duration: '2 months' }) const prev = timeleap.prev({ time: base, duration: '2 months' }) const schedule = timeleap.backoff({ time: base, duration: '1 day', type: 'linear', limit: 3 }) // Print each result as ISO console.log(`Base: ${iso(base)}`) console.log(`Next: ${iso(next)}`) console.log(`Prev: ${iso(prev)}`) console.log(`Schedule: ${schedule.map(iso).join(', ')}`)
Base: 2077-01-01T00:00:00.000Z
Next: 2077-03-01T00:00:00.000Z
Prev: 2076-11-01T00:00:00.000Z
Schedule: 2077-01-02T00:00:00.000Z, 2077-01-04T00:00:00.000Z, 2077-01-07T00:00:00.000Z
timeleap.backoff(input)
input<TimeleapBackoff>Base time, duration, curve type, and limit.type<TimeleapCurve>Growth curve for the schedule.limit<number>Number of points, an integer between1and1024.
- Returns:
number[]- cumulative UTC timestamps.
timeleap.next(input)
input<TimeleapShift>Base time, duration, and optional skip.skip<TimeleapSkip>(Optional) Time dimensions to avoid landing on.
- Returns:
number- UTC timestamp one duration ahead.
timeleap.parse(duration)
duration<string>The duration string to validate.- Returns:
boolean-truewhen the string is a valid duration.
timeleap.prev(input)
input<TimeleapShift>Base time, duration, and optional skip.skip<TimeleapSkip>(Optional) Time dimensions to avoid landing on.
- Returns:
number- UTC timestamp one duration behind.
When the shifted result lands on a blocked point, it steps by the smallest blocked dimension in the shift direction until it is clear.
timeleap.next({ time: base, duration: '1 hour', skip: { minute: [0, 30], // 0..59 hour: [0, 1, 2, 3, 4, 5], // 0..23 day: ['saturday', 'sunday'], // 0..6 or weekday name week: [1, 5], // week of month, 1..5 month: ['january', 'december'] // 1..12 or month name } })
- Every field is optional, all checks run in UTC.
- Names are case-insensitive,
dayandmonthalso accept numbers. - Throws
RangeErrorwhen a value is out of range or the rules block a whole dimension (every minute, hour, day, week, or month).
deno task build
deno task testThis project is licensed under the MIT license. See the LICENSE file for details.