Skip to content

Latest commit

 

History

7 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Timeleap

Turn duration text into next, previous, and backoff timestamps

Node Deno Module type: Deno/ESM License

What is Timeleap?

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).

Use Cases

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.

Installation

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>

Usage

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

Duration Styles

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

Example

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

API

backoff

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 between 1 and 1024.
  • Returns: number[] - cumulative UTC timestamps.

next

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.

parse

timeleap.parse(duration)
  • duration <string> The duration string to validate.
  • Returns: boolean - true when the string is a valid duration.

prev

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.

skip (optional)

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, day and month also accept numbers.
  • Throws RangeError when a value is out of range or the rules block a whole dimension (every minute, hour, day, week, or month).

Build

deno task build

Testing

deno task test

License

This project is licensed under the MIT license. See the LICENSE file for details.

About

Turn human duration strings into next, previous, and backoff timestamps, with skip rules that avoid blocked minutes, days, or months.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Contributors

Languages