Duration
Duration values represent time spansβthe difference between two points in time. Parsley tracks durations as two separate components: months (for calendar-based units) and seconds (for fixed-length units). This dual representation ensures accurate arithmetic with variable-length calendar months.
let vacation = @2w
let project = @3mo
let meeting = @1h30m
vacation.seconds // 1209600
project.months // 3
meeting.fmt() // "in 2 hours"
Literals
Duration literals use the @ prefix followed by one or more number-unit pairs.
Supported Units
| Unit | Description | Storage |
|---|---|---|
y |
Years | 12 months |
mo |
Months | months |
w |
Weeks | 604,800 seconds |
d |
Days | 86,400 seconds |
h |
Hours | 3,600 seconds |
m |
Minutes | 60 seconds |
s |
Seconds | seconds |
Simple Durations
@30s // 30 seconds
@5m // 5 minutes
@2h // 2 hours
@7d // 7 days
@2w // 2 weeks
@6mo // 6 months
@1y // 1 year
Compound Durations
Combine multiple units in a single literal (order: years, months, weeks, days, hours, minutes, seconds):
@2h30m // 2 hours, 30 minutes
@1d12h // 1 day, 12 hours
@1y6mo // 1 year, 6 months
@3w2d // 3 weeks, 2 days
@1y2mo3w4d5h6m7s // All units combined
Negative Durations
Prefix with - for negative durations (time in the past):
@-1d // 1 day ago
@-2h30m // 2 hours 30 minutes ago
@-1y // 1 year ago
Constructor
The duration() function creates durations dynamically from strings or dictionaries. Use this when parsing user input or building durations from variables.
From String
Parse a duration string using the same format as literals (without the @ prefix):
duration("30s") // 30 seconds
duration("2h30m") // 2 hours 30 minutes
duration("1y6mo") // 1 year 6 months
duration("-1d") // negative 1 day
From Dictionary
Create a duration from named components:
duration({seconds: 30})
duration({hours: 2, minutes: 30})
duration({years: 1, months: 6})
duration({days: 7})
Available keys: years, months, weeks, days, hours, minutes, seconds
// All keys example
duration({
years: 1,
months: 2,
weeks: 3,
days: 4,
hours: 5,
minutes: 6,
seconds: 7
})
When to Use
Prefer literals for static durations; use duration() for dynamic values:
// Static: use literals
let timeout = @30s
let deadline = @now + @7d
// Dynamic: use constructor
let userInput = "2h30m"
let parsed = duration(userInput)
let config = {hours: 8, minutes: 30}
let workday = duration(config)
Operators
Addition (+)
Add two durations together:
@2h + @30m // 2 hours 30 minutes
@1y + @6mo // 1 year 6 months (18 months)
@1d + @-6h // 18 hours (1 day minus 6 hours)
Add a duration to a datetime (commutative):
@2024-12-25 + @7d // January 1, 2025
@7d + @2024-12-25 // January 1, 2025 (same result)
@2024-01-15 + @1mo // February 15, 2024
@12:00 + @2h30m // 14:30
Subtraction (-)
Subtract one duration from another:
@1d - @6h // 18 hours
@2y - @3mo // 21 months
@1w - @1d // 6 days
Subtract a duration from a datetime:
@2024-12-25 - @7d // December 18, 2024
@2024-03-01 - @1mo // February 1, 2024
Subtract two datetimes to get a duration:
@2024-12-25 - @2024-12-20 // 5 days
@2024-12-25T14:00:00 - @2024-12-25T12:00:00 // 2 hours
Multiplication (*)
Multiply a duration by an integer:
@2h * 3 // 6 hours
@1d * 7 // 1 week
@1mo * 6 // 6 months
Division (/)
Divide a duration by an integer:
@1d / 2 // 12 hours
@6mo / 3 // 2 months
@1h / 4 // 15 minutes
Divide two durations to get a ratio:
@7d / @1d // 7
@6mo / @1y // 0.5
@2h / @30m // 4
// Practical example: calculate age
let birthdate = @1990-05-15
let today = @today
let age = (today - birthdate) / @1y // Approximate years
Note: Division involving months uses an approximate conversion (1 month β 30.44 days) for accurate ratios.
Comparison Operators
Compare durations of the same type (seconds-only):
@2h > @1h // true
@30m < @1h // true
@1d == @24h // true
@1w != @6d // true
@2h <= @2h // true
@3d >= @2d // true
Note: Comparisons with month-based durations are not allowed due to variable month lengths.
Properties
Core Properties
| Property | Type | Description |
|---|---|---|
.months |
Integer | Month component of the duration |
.seconds |
Integer | Seconds component of the duration |
.totalSeconds |
Integer | Total seconds (only for durations without months) |
.__type |
String | Always "duration" |
let d = @1y2mo3d4h
d.months // 14 (12 + 2)
d.seconds // 273600 (3*86400 + 4*3600)
d.__type // "duration"
For durations without months, .totalSeconds provides the complete duration:
let d = @2d12h
d.totalSeconds // 216000 (2*86400 + 12*3600)
d.seconds // 216000 (same value)
For durations with months, .totalSeconds is not available:
let d = @1y2mo
d.totalSeconds // null (months have variable length)
d.months // 14
d.seconds // 0
Computed Properties
These properties calculate derived values from the seconds component:
| Property | Type | Description |
|---|---|---|
.days |
Integer | Total seconds as days (integer division) |
.hours |
Integer | Total seconds as hours (integer division) |
.minutes |
Integer | Total seconds as minutes (integer division) |
let d = @2d12h30m
d.seconds // 217800
d.days // 2 (217800 / 86400)
d.hours // 60 (217800 / 3600)
d.minutes // 3630 (217800 / 60)
For week-based durations:
let w = @1w
w.days // 7
w.hours // 168
w.minutes // 10080
Note: Computed properties return
nullfor month-based durations since months have variable lengths (28-31 days):
let y = @1y
y.days // null
y.hours // null
y.minutes // null
let mixed = @1mo2d
mixed.days // null (has month component)
Methods
fmt()
The primary formatting method with multiple overloads.
Usage: fmt()
Format with default style (medium) and locale (en-US). Returns relative time:
@2h.fmt() // "in 2 hours"
@-1d.fmt() // "yesterday"
@7d.fmt() // "next week"
@1mo.fmt() // "next month"
Usage: fmt(style)
Format with a named style:
let d = @2h30m
d.fmt("short") // "2h30m"
d.fmt("medium") // "in 3 hours"
d.fmt("long") // "2 hours 30 minutes"
Note: Duration does not support the
fullstyle. Calling.fmt("full")or.full()will produce an error.
Usage: fmt(style, locale)
Format with style and locale:
@1d.fmt("medium", "de-DE") // "morgen"
@-1d.fmt("medium", "de-DE") // "gestern"
@7d.fmt("medium", "fr-FR") // "la semaine prochaine"
Usage: fmt(options)
Format with an options dictionary:
@2h30m.fmt({style: "long"}) // "2 hours 30 minutes"
@1d.fmt({style: "medium", locale: "de-DE"}) // "morgen"
short()
Compact format using abbreviations:
@2h.short() // "2h"
@2h30m.short() // "2h30m"
@1d12h.short() // "1d12h"
@1y6mo.short() // "1y6mo"
// With locale:
@2h30m.short("de-DE") // "2h30m"
medium()
Relative time format (default style):
@1d.medium() // "tomorrow"
@-1d.medium() // "yesterday"
@7d.medium() // "next week"
@-7d.medium() // "last week"
@1mo.medium() // "next month"
@1y.medium() // "next year"
@2h.medium() // "in 2 hours"
@-30m.medium() // "30 minutes ago"
// With locale:
@1d.medium("de-DE") // "morgen"
@-1d.medium("de-DE") // "gestern"
@1mo.medium("fr-FR") // "le mois prochain"
long()
Verbose human-readable format:
@2h.long() // "2 hours"
@2h30m.long() // "2 hours 30 minutes"
@1d12h.long() // "1 day 12 hours"
@1y6mo.long() // "1 year 6 months"
// With locale:
@2h30m.long("de-DE") // "2 Stunden 30 Minuten"
format()
Alias for fmt(). Retained for backward compatibility:
@1d.format() // "tomorrow"
@2h30m.format("de-DE") // Locale-formatted
repr()
Returns a parseable literal representation:
@2h.repr() // "@duration{hours: 2}"
@2h30m.repr() // "@duration{hours: 2, minutes: 30}"
@1y6mo.repr() // "@duration{years: 1, months: 6}"
toJSON()
Returns the JSON representation:
@2h30m.toJSON() // "{\"months\":0,\"seconds\":9000}"
inspect()
Returns a debug dictionary with type information:
@2h30m.inspect()
// {__type: "duration", months: 0, seconds: 9000}
@1y6mo.inspect()
// {__type: "duration", months: 18, seconds: 0}
toDict()
Returns a clean dictionary for reconstruction (without __type):
@2h30m.toDict()
// {months: 0, seconds: 9000}
@1y6mo.toDict()
// {months: 18, seconds: 0}
Useful for serialization or passing duration data to other systems.
toBox()
Renders the duration in a box diagram:
@2h30m.toBox()
ββββββββββββββββββββββββββ
β 2 hours 30 minutes β
ββββββββββββββββββββββββββ
Formatting Styles Summary
| Style | Example (@2h30m) |
Description |
|---|---|---|
short |
"2h30m" |
Compact abbreviations |
medium (default) |
"in 3 hours" |
Relative time |
long |
"2 hours 30 minutes" |
Verbose human-readable |
Note: Duration does not support
fullstyle.
String Conversion
Durations automatically convert to human-readable strings in output contexts:
let d = @2h30m
d // "2 hours 30 minutes"
toString(d) // "2 hours 30 minutes"
"Time: " + toString(d) // "Time: 2 hours 30 minutes"
let long = @1y2mo3d
toString(long) // "1 year 2 months 3 days"
The string format uses plural forms appropriately:
@1d // "1 day"
@2d // "2 days"
@1h // "1 hour"
@3h // "3 hours"
Zero duration:
@0s // "0 seconds"
Month vs. Second Components
Parsley separates durations into two components to handle the fact that months have variable lengths (28-31 days):
Seconds-Only Durations
Units w, d, h, m, s are stored as seconds:
@1w // 604800 seconds
@1d // 86400 seconds
@1h // 3600 seconds
@1m // 60 seconds
@1s // 1 second
These can be compared and have .totalSeconds:
@7d == @1w // true
@24h == @1d // true
@60m == @1h // true
Month-Based Durations
Units y and mo are stored as months:
@1y // 12 months, 0 seconds
@6mo // 6 months, 0 seconds
@1y6mo // 18 months, 0 seconds
These cannot be compared (variable month lengths):
@1y > @365d // Error: cannot compare durations with month components
Mixed Durations
Compound durations can have both:
@1y2mo3d // 14 months, 259200 seconds
Common Patterns
Calculate Time Until Event
let deadline = @2025-01-01
let remaining = deadline - @today
remaining.fmt() // "in 2 weeks"
remaining.long() // "18 days"
remaining.seconds / 86400 // Days remaining (numeric)
Schedule Future Dates
let nextReview = @today + @3mo
let followUp = @now + @2w
let reminder = @now + @1h30m
Calculate Age
let birthdate = @1990-05-15
let age = (@today - birthdate) / @1y
age.round() // Approximate age in years
Work Duration Calculations
let taskTime = @2h30m
let tasks = 5
let totalTime = taskTime * tasks // 12 hours 30 minutes
let workDay = @8h
let workers = 3
let perWorker = workDay / workers // 2 hours 40 minutes each
Time Zone-Safe Scheduling
Since durations add exact seconds (except months), they're predictable across time zones:
let meeting = @2024-12-15T10:00:00
let buffer = @30m
let start = meeting - buffer // 09:30 (exact)
Relative Time Display
let posted = @2024-12-10T14:00:00
let now = @now
let ago = now - posted
ago.fmt() // "5 days ago" (depends on current date)
ago.short() // "5d"
ago.long() // "5 days"
Format Selection by Context
let duration = @2h30m
// For compact UI elements (badges, timers)
duration.short() // "2h30m"
// For natural language contexts
duration.medium() // "in 3 hours"
// For detailed displays
duration.long() // "2 hours 30 minutes"
Duration vs. Datetime Arithmetic
| Operation | Result |
|---|---|
duration + duration |
Duration |
duration - duration |
Duration |
duration * integer |
Duration |
duration / integer |
Duration |
duration / duration |
Float (ratio) |
datetime + duration |
Datetime |
duration + datetime |
Datetime (commutative) |
datetime - duration |
Datetime |
datetime - datetime |
Duration |
integer + datetime |
Datetime |
datetime + integer |
Datetime |