@ggviana.eth/duration
v0.1.7
Published
Type-safe duration handling in TypeScript. Parse ISO 8601 strings, perform arithmetic, compare durations, and convert between units. Accepts numbers, objects, or strings. Immutable, zero dependencies.
Maintainers
Readme
Duration
A lightweight, fully-typed TypeScript library for working with time durations. Create, manipulate, and format durations with an intuitive, immutable API.
import Duration from '@ggviana.eth/duration'
const timer = Duration.fromMinutes(5).and(30, 'seconds')
console.log(timer.toString()) // PT5M30S
console.log(timer) // Duration { 5m 30s }
const extended = timer.add(Duration.fromSeconds(45))
console.log(extended.toMinutes()) // 6 minutesFeatures
- 🔷 Fully Typed - Complete TypeScript support with strict mode
- 🎯 Type-Safe Units - Prevent runtime errors with compile-time unit validation
- 🔒 Immutable - All operations return new instances
- 🌐 ISO 8601 - Parse and serialize to standard duration format
- 📦 Dual Format - Works with ESM and CommonJS
- 🪶 Zero Dependencies - Lightweight and self-contained
- ✅ 100% Tested - Comprehensive test coverage
- 🎨 Developer Friendly - Intuitive API with method chaining
Installation
npm install @ggviana.eth/durationQuick Start
import Duration from '@ggviana.eth/duration'
// Create durations
const d1 = Duration.fromHours(2)
const d2 = Duration.fromMinutes(30)
const d3 = Duration.of(45, 'seconds')
// Combine durations
const total = d1.add(d2).add(d3)
console.log(total.toString()) // PT2H30M45S
// Parse ISO 8601
const parsed = Duration.parse('P1DT6H30M')
console.log(parsed.toHours()) // 30
// Create from object
const duration = new Duration({
hours: 1,
minutes: 30,
seconds: 45
})
console.log(duration.toSeconds()) // 5445API Documentation
Creating Durations
Constructor
new Duration(milliseconds: number)
new Duration(duration: Duration)
new Duration(object: Partial<DurationLike>)Create a Duration from milliseconds, another Duration (copy), or a duration-like object with optional time components.
const d1 = new Duration(5000) // 5 seconds
const d2 = new Duration(d1) // Copy
const d3 = new Duration({
hours: 1,
minutes: 30,
seconds: 15
})Static Factory Methods
Duration.fromMilliseconds(ms: number): Duration
Duration.fromSeconds(seconds: number): Duration
Duration.fromMinutes(minutes: number): Duration
Duration.fromHours(hours: number): Duration
Duration.fromDays(days: number): Duration
Duration.fromWeeks(weeks: number): DurationCreate durations from specific time units.
const fiveMinutes = Duration.fromMinutes(5)
const oneDay = Duration.fromDays(1)
const twoWeeks = Duration.fromWeeks(2)Generic Factory
Duration.of(value: number, unit: TimeUnit): DurationCreate a duration with any supported time unit. Accepts both singular and plural forms.
Duration.of(30, 'second') // or 'seconds'
Duration.of(5, 'minute') // or 'minutes'
Duration.of(2, 'hour') // or 'hours'
Duration.of(1, 'day') // or 'days'
Duration.of(3, 'week') // or 'weeks'Parsing
Duration.parse(isoString: string): Duration
Duration.parseISO8601(isoString: string): DurationParse an ISO 8601 duration string.
Duration.parse('PT1H30M') // 1 hour 30 minutes
Duration.parse('P1DT2H') // 1 day 2 hours
Duration.parse('PT30.5S') // 30.5 seconds
Duration.parse('P7DT3H15M30S') // Complex durationArithmetic Operations
add()
add(other: DurationInput): DurationAdd another duration, returning a new Duration.
const d1 = Duration.fromMinutes(30)
const d2 = Duration.fromMinutes(15)
const total = d1.add(d2) // 45 minutes
// Also accepts numbers (milliseconds) or objects
d1.add(30000) // Add 30 seconds
d1.add({ minutes: 5, seconds: 30 })and()
and(value: number, unit: TimeUnit): DurationFluent API for adding time with a specific unit. Perfect for chaining.
const time = Duration.fromHours(1)
.and(30, 'minutes')
.and(45, 'seconds')
console.log(time.toString()) // PT1H30M45Ssubtract()
subtract(other: DurationInput): DurationSubtract another duration. Result bottoms out at zero (no negative durations).
const d1 = Duration.fromMinutes(30)
const d2 = Duration.fromMinutes(10)
const remaining = d1.subtract(d2) // 20 minutes
// Bottoms out at zero
const d3 = d2.subtract(d1) // 0 (not negative)roundTo()
roundTo(unit: TimeUnit): DurationRound to the nearest time unit.
const d = Duration.fromSeconds(90)
d.roundTo('minute') // 2 minutes
Duration.fromMinutes(25).roundTo('hour') // 0 hours
Duration.fromMinutes(35).roundTo('hour') // 1 hourComparison Methods
All comparison methods accept DurationInput (Duration, number, ISO 8601 string, or partial DurationLike object).
equals(other: DurationInput): boolean
isLessThan(other: DurationInput): boolean
isGreaterThan(other: DurationInput): boolean
isLessThanOrEqual(other: DurationInput): boolean
isGreaterThanOrEqual(other: DurationInput): boolean
isZero(): booleanExamples:
const d1 = Duration.fromMinutes(5)
const d2 = Duration.fromSeconds(300)
d1.equals(d2) // true
d1.equals('PT5M') // true (ISO 8601 string)
d1.isLessThan(d2) // false
d1.isGreaterThan(1000) // true (1000ms = 1s)
d1.isGreaterThan('PT1S') // true (string comparison)
d1.isZero() // false
Duration.fromMilliseconds(0).isZero() // trueConversion Methods
toMilliseconds(): number
toSeconds(): number // Truncates fractional seconds
toMinutes(): number // Truncates fractional minutes
toHours(): number // Truncates fractional hours
toDays(): number // Truncates fractional days
toWeeks(): number // Truncates fractional weeksExamples:
const d = Duration.fromMinutes(5).and(30, 'seconds')
d.toMilliseconds() // 330000
d.toSeconds() // 330
d.toMinutes() // 5 (truncated)toObject()
toObject(): DurationLikeBreak down the duration into its component parts.
const d = Duration.parse('P1DT2H30M45.5S')
console.log(d.toObject())
// {
// weeks: 0,
// days: 1,
// hours: 2,
// minutes: 30,
// seconds: 45,
// milliseconds: 500
// }toJSON()
toJSON(): DurationLikeReturns the same as toObject() for JSON serialization.
const d = Duration.fromMinutes(5)
JSON.stringify(d)
// {"weeks":0,"days":0,"hours":0,"minutes":5,"seconds":0,"milliseconds":0}Serialization
toString()
toString(): stringConvert to ISO 8601 duration format.
Duration.fromHours(1).toString() // PT1H
Duration.fromMinutes(30).toString() // PT30M
Duration.fromDays(1).and(2, 'hours').toString() // P1DT2H
// Supports decimal seconds
Duration.fromMilliseconds(1500).toString() // PT1.5S
// Zero duration
new Duration(0).toString() // PT0SCustom Inspect
Durations have a custom console.log() format for better debugging.
const d = Duration.fromWeeks(2)
.and(3, 'days')
.and(5, 'hours')
.and(30, 'minutes')
console.log(d) // Duration { 2w 3d 5h 30m }Utility Methods
Duration.min()
Duration.min(a: DurationInput, b: DurationInput): DurationReturn the smaller of two durations.
const min = Duration.min(
Duration.fromMinutes(5),
Duration.fromMinutes(10)
)
console.log(min.toMinutes()) // 5Duration.max()
Duration.max(a: DurationInput, b: DurationInput): DurationReturn the larger of two durations.
const max = Duration.max(
Duration.fromMinutes(5),
Duration.fromMinutes(10)
)
console.log(max.toMinutes()) // 10Duration.isDuration()
Duration.isDuration(obj: unknown): obj is Duration | Partial<DurationLike> | stringType guard to check if a value is a Duration, duration-like object, or valid ISO 8601 duration string.
Duration.isDuration(Duration.fromMinutes(5)) // true
Duration.isDuration({ hours: 1, minutes: 30 }) // true
Duration.isDuration('PT1H30M') // true
Duration.isDuration('P1D') // true
Duration.isDuration({ invalid: 'object' }) // false
Duration.isDuration('invalid string') // false
Duration.isDuration(null) // falseTypeScript Support
Duration is written in TypeScript and includes full type definitions.
Type Exports
import Duration, {
DurationLike,
TimeUnit,
DurationInput
} from '@ggviana.eth/duration'Type Definitions
// Supported time units (singular and plural)
type TimeUnit =
| 'millisecond' | 'milliseconds'
| 'second' | 'seconds'
| 'minute' | 'minutes'
| 'hour' | 'hours'
| 'day' | 'days'
| 'week' | 'weeks'
// Duration object with all time components
// Used for toObject()/toJSON() and as Partial<DurationLike> for construction
interface DurationLike {
weeks: number
days: number
hours: number
minutes: number
seconds: number
milliseconds: number
}
// Accepted input types
type DurationInput = number | string | Duration | Partial<DurationLike>Type Safety Examples
// Type-safe unit parameter
const d1 = Duration.of(5, 'minutes') // ✓
const d2 = Duration.of(5, 'years') // ✗ TypeScript error
// Type-safe object construction
const d3: Partial<DurationLike> = {
hours: 1,
minutes: 30,
invalid: 'field' // ✗ TypeScript error
}
// Type guard usage
function processDuration(input: unknown) {
if (Duration.isDuration(input)) {
// input is now typed as Duration | Partial<DurationLike>
const duration = Duration.toDuration(input)
console.log(duration.toMinutes())
}
}Practical Examples
Timer / Countdown
const countdown = Duration.fromMinutes(5)
let remaining = countdown
const interval = setInterval(() => {
remaining = remaining.subtract(Duration.fromSeconds(1))
console.log(remaining) // Duration { 4m 59s }, { 4m 58s }, ...
if (remaining.isZero()) {
console.log('Time is up!')
clearInterval(interval)
}
}, 1000)Calculate Time Difference
const start = Date.now()
// ... some operation ...
const end = Date.now()
const elapsed = new Duration(end - start)
console.log(`Operation took ${elapsed.toSeconds()} seconds`)Work Session Tracker
const workSession = Duration.fromHours(8)
.subtract(Duration.fromMinutes(30)) // Lunch break
.subtract(Duration.fromMinutes(15)) // Coffee break
const breaks = Duration.fromMinutes(45)
const actualWork = workSession.subtract(breaks)
console.log(`Actual work time: ${actualWork.toHours()} hours`)Video Duration Formatting
function formatVideoDuration(ms: number): string {
const d = new Duration(ms)
const parts = d.toObject()
if (parts.hours > 0) {
return `${parts.hours}:${pad(parts.minutes)}:${pad(parts.seconds)}`
}
return `${parts.minutes}:${pad(parts.seconds)}`
}
function pad(n: number): string {
return n.toString().padStart(2, '0')
}
console.log(formatVideoDuration(125000)) // "2:05"
console.log(formatVideoDuration(3725000)) // "1:02:05"SLA / Deadline Calculations
const slaResponse = Duration.fromHours(4)
const slaResolution = Duration.fromDays(1)
const incidentReceived = new Date('2024-01-01T09:00:00Z')
const responseDeadline = new Date(
incidentReceived.getTime() + slaResponse.toMilliseconds()
)
const resolutionDeadline = new Date(
incidentReceived.getTime() + slaResolution.toMilliseconds()
)
console.log(`Must respond by: ${responseDeadline}`)
console.log(`Must resolve by: ${resolutionDeadline}`)Rate Limiting
const rateLimitWindow = Duration.fromMinutes(1)
const maxRequests = 100
class RateLimiter {
private requests: number[] = []
canMakeRequest(): boolean {
const now = Date.now()
const windowStart = now - rateLimitWindow.toMilliseconds()
// Remove old requests
this.requests = this.requests.filter(t => t > windowStart)
return this.requests.length < maxRequests
}
recordRequest(): void {
this.requests.push(Date.now())
}
}Constants
Duration provides constants for all time units:
Duration.Units.Millisecond // 1
Duration.Units.Second // 1000
Duration.Units.Minute // 60000
Duration.Units.Hour // 3600000
Duration.Units.Day // 86400000
Duration.Units.Week // 604800000Immutability
All Duration operations return new instances. The original is never modified.
const original = Duration.fromMinutes(10)
const doubled = original.add(original)
const halved = original.subtract(Duration.fromMinutes(5))
console.log(original.toMinutes()) // 10 (unchanged)
console.log(doubled.toMinutes()) // 20
console.log(halved.toMinutes()) // 5Development
Setup
npm installAvailable Scripts
npm run build- Build ESM/CJS bundles with type declarationsnpm test- Run test suitenpm run test:watch- Run tests in watch modenpm run test:coverage- Run tests with coverage reportnpm run lint- Check for linting errorsnpm run lint:fix- Fix linting errors automaticallynpm run type-check- Run TypeScript type checking
Testing
Tests are written with Vitest and achieve 100% code coverage.
npm test
npm run test:coverageBuilding
The build produces both ESM and CommonJS outputs with TypeScript declarations:
npm run buildOutput files:
dist/index.js- ESM moduledist/index.cjs- CommonJS moduledist/index.d.ts- TypeScript declarations (ESM)dist/index.d.cts- TypeScript declarations (CJS)
Browser Support
Duration works in all modern browsers and Node.js environments that support:
- ES2022
- Private class fields (
#field)
Minimum versions:
- Node.js 16+
- Chrome 90+
- Firefox 90+
- Safari 15+
- Edge 90+
License
MIT
Contributing
Contributions are welcome! Please ensure:
- Tests pass (
npm test) - Linting passes (
npm run lint) - Type checking passes (
npm run type-check) - Add tests for new features
- Update documentation as needed
Made with ❤️ by the Duration team
