Time & Duration
The time module provides time values, durations, time-zone handling, parsing, formatting, sleeps, and timers. Supported workflow time calls are recorded so they can replay deterministically.
This is an API reference. Code blocks are isolated examples or partial scheduling patterns, not a complete entry. Names such as do_work, try_operation, make_request, send_reminder, user_activity, check_health, and process represent application callbacks, channels, or data. Where a snippet assigns an error return to _, it assumes the shown literal is valid; handle errors when values can come from input or configuration.
Loading
local time = require("time")
Add time to the executable entry's modules: list before requiring it. The ambient channel and errors globals used by scheduling examples need no module declaration.
Current Time
now
Returns the current time. In workflows, it returns the recorded workflow time reference so execution can replay deterministically.
local t = time.now()
print(t:format_rfc3339()) -- "2024-12-29T15:04:05Z"
-- Measure elapsed time
local start = time.now()
do_work()
local elapsed = time.now():sub(start)
print("Took " .. elapsed:milliseconds() .. "ms")
The timestamp and elapsed-time output are illustrative; time.now() supplies the current or recorded workflow time.
Returns: Time
Creating Time Values
Create from Components
-- Create specific date/time in UTC
local t = time.date(2024, time.DECEMBER, 25, 10, 30, 0, 0, time.utc)
print(t:format_rfc3339()) -- "2024-12-25T10:30:00Z"
-- Create in specific timezone
local ny, err = time.load_location("America/New_York")
if err then
return nil, err
end
local meeting = time.date(2024, time.JANUARY, 15, 14, 0, 0, 0, ny)
-- Defaults to local timezone if not specified
local t = time.date(2024, 1, 15, 12, 0, 0, 0)
| Parameter | Type | Description |
|---|---|---|
year |
number | Year |
month |
number | Month (1-12 or time.JANUARY etc) |
day |
number | Day of month |
hour |
number | Hour (0-23) |
minute |
number | Minute (0-59) |
second |
number | Second (0-59) |
nanosecond |
number | Nanosecond (0-999999999) |
location |
Location | Timezone (optional, defaults to local) |
Returns: Time
Create from a Unix Timestamp
-- From seconds since epoch
local t = time.unix(1703862245, 0)
print(t:utc():format_rfc3339()) -- "2023-12-29T15:04:05Z"
-- With nanoseconds
local t = time.unix(1703862245, 500000000) -- +500ms
-- Convert JavaScript timestamp (milliseconds)
local js_timestamp = 1703862245000
local t = time.unix(js_timestamp // 1000, (js_timestamp % 1000) * 1000000)
| Parameter | Type | Description |
|---|---|---|
sec |
number | Unix seconds |
nsec |
number | Nanoseconds offset |
Returns: Time
Parse from a String
Parse time strings using Go's reference time format: Mon Jan 2 15:04:05 MST 2006.
-- Parse RFC3339
local t, err = time.parse(time.RFC3339, "2024-12-29T15:04:05Z")
if err then
return nil, err
end
-- Parse custom format
local t, err = time.parse("2006-01-02", "2024-12-29")
local t, err = time.parse("15:04:05", "14:30:00")
local t, err = time.parse("2006-01-02 15:04:05 MST", "2024-12-29 14:30:00 EST")
-- Parse in specific timezone
local ny, _ = time.load_location("America/New_York")
local t, err = time.parse("2006-01-02 15:04", "2024-12-29 14:30", ny)
| Parameter | Type | Description |
|---|---|---|
layout |
string | Go time format layout |
value |
string | String to parse |
location |
Location | Default time zone (optional) |
Returns: Time, error
Time Methods
Arithmetic
local t = time.now()
-- Add duration (accepts number, string, or Duration)
local tomorrow = t:add("24h")
local later = t:add(5 * time.MINUTE)
local d, _ = time.parse_duration("1h30m")
local future = t:add(d)
-- Subtract time to get duration
local diff = tomorrow:sub(t) -- returns Duration
print(diff:hours()) -- 24
-- Add calendar units (handles month boundaries correctly)
local next_month = t:add_date(0, 1, 0) -- add 1 month
local next_year = t:add_date(1, 0, 0) -- add 1 year
local last_week = t:add_date(0, 0, -7) -- subtract 7 days
| Method | Parameters | Returns | Description |
|---|---|---|---|
add(duration) |
number/string/Duration | Time | Add duration |
sub(time) |
Time | Duration | Difference between times |
add_date(years, months, days) |
numbers | Time | Add calendar units |
Comparison
local t1 = time.date(2024, 1, 1, 0, 0, 0, 0, time.utc)
local t2 = time.date(2024, 1, 2, 0, 0, 0, 0, time.utc)
t1:before(t2) -- true
t2:after(t1) -- true
t1:equal(t1) -- true
| Method | Parameters | Returns | Description |
|---|---|---|---|
before(time) |
Time | boolean | Whether this time is before the other value |
after(time) |
Time | boolean | Whether this time is after the other value |
equal(time) |
Time | boolean | Whether the two values represent the same time |
Formatting
local t = time.now()
t:format_rfc3339() -- "2024-12-29T15:04:05Z"
t:format(time.DATE_ONLY) -- "2024-12-29"
t:format(time.TIME_ONLY) -- "15:04:05"
t:format("Mon Jan 2, 2006") -- "Sun Dec 29, 2024"
| Method | Parameters | Returns | Description |
|---|---|---|---|
format(layout) |
string | string | Format using Go layout |
format_rfc3339() |
- | string | Format as RFC3339 |
Unix Timestamps
local t = time.now()
t:unix() -- seconds since epoch
t:unix_nano() -- nanoseconds since epoch
Components
local t = time.now()
-- Get date parts
local year, month, day = t:date()
-- Get time parts
local hour, min, sec = t:clock()
-- Individual accessors
t:year() -- e.g., 2024
t:month() -- 1-12
t:day() -- 1-31
t:hour() -- 0-23
t:minute() -- 0-59
t:second() -- 0-59
t:nanosecond() -- 0-999999999
t:weekday() -- 0=Sunday .. 6=Saturday
t:year_day() -- 1-366
t:is_zero() -- true if zero value
Time-Zone Conversion
local t = time.now()
t:utc() -- convert to UTC
t:in_local() -- convert to local timezone
t:in_location(ny) -- convert to specific timezone
t:location() -- get current Location
t:location():string() -- get timezone name
| Method | Parameters | Returns | Description |
|---|---|---|---|
utc() |
- | Time | Convert to UTC |
in_local() |
- | Time | Convert to the local time zone |
in_location(loc) |
Location | Time | Convert to a specified time zone |
location() |
- | Location | Return the current time zone |
Rounding
Round or truncate to duration boundaries. Requires Duration userdata (not number or string).
local t = time.now()
local hour_duration, _ = time.parse_duration("1h")
local minute_duration, _ = time.parse_duration("15m")
t:round(hour_duration) -- round to nearest hour
t:truncate(minute_duration) -- truncate to 15-minute boundary
| Method | Parameters | Returns | Description |
|---|---|---|---|
round(duration) |
Duration | Time | Round to nearest multiple |
truncate(duration) |
Duration | Time | Truncate to multiple |
Duration
Create a Duration
-- Parse from string
local d, err = time.parse_duration("1h30m45s")
local d, err = time.parse_duration("500ms")
local d, err = time.parse_duration("2h30m45s500ms")
-- From number (nanoseconds)
local d, err = time.parse_duration(time.SECOND)
local d, err = time.parse_duration(5 * time.MINUTE)
-- Valid units: ns, us, ms, s, m, h
| Parameter | Type | Description |
|---|---|---|
value |
number/string/Duration | Duration to parse |
Returns: Duration, error
Duration Methods
local d, _ = time.parse_duration("1h30m45s500ms")
d:hours() -- 1.5126...
d:minutes() -- 90.75...
d:seconds() -- 5445.5
d:milliseconds() -- 5445500
d:microseconds() -- 5445500000
d:nanoseconds() -- 5445500000000
Time Zones
Named Locations
Load a time zone by its IANA name, such as America/New_York, Europe/London, or Asia/Tokyo.
local ny, err = time.load_location("America/New_York")
if err then
return nil, err
end
local tokyo, _ = time.load_location("Asia/Tokyo")
local london, _ = time.load_location("Europe/London")
-- Convert between timezones
local t = time.now():utc()
print("UTC:", t:format(time.TIME_ONLY))
print("New York:", t:in_location(ny):format(time.TIME_ONLY))
print("Tokyo:", t:in_location(tokyo):format(time.TIME_ONLY))
| Parameter | Type | Description |
|---|---|---|
name |
string | IANA time-zone name |
Returns: Location, error
Fixed-Offset Locations
Create a time zone with a fixed UTC offset.
-- UTC+5:30 (India Standard Time)
local ist = time.fixed_zone("IST", 5*3600 + 30*60)
-- UTC-8 (Pacific Standard Time)
local pst = time.fixed_zone("PST", -8*3600)
local t = time.date(2024, 1, 15, 12, 0, 0, 0, ist)
| Parameter | Type | Description |
|---|---|---|
name |
string | Zone name |
offset |
number | UTC offset in seconds |
Returns: Location
Built-In Locations
time.utc -- UTC timezone
time.localtz -- Local system timezone
Scheduling
sleep
Suspend execution for the specified duration. Workflow execution records the sleep for deterministic replay.
time.sleep("5s")
time.sleep(500 * time.MILLISECOND)
-- Backoff pattern
for attempt = 1, 3 do
local ok = try_operation()
if ok then break end
time.sleep(tostring(attempt) .. "s")
end
| Parameter | Type | Description |
|---|---|---|
duration |
number/string/Duration | Sleep time |
after
Returns a channel that receives one value after the duration. The channel can be used with channel.select.
-- Simple timeout
local timeout, err = time.after("5s")
if err then return nil, err end
timeout:receive() -- blocks for 5 seconds
-- Timeout with select
local response_ch = make_request()
local timeout_ch, err = time.after("30s")
if err then return nil, err end
local result = channel.select{
response_ch:case_receive(),
timeout_ch:case_receive()
}
if result.channel == timeout_ch then
return nil, errors.new({message = "Request timed out", kind = errors.TIMEOUT})
end
| Parameter | Type | Description |
|---|---|---|
duration |
number/string/Duration | Time to wait |
Returns: Channel, error
timer
Creates a one-shot timer that fires after the specified duration and can be stopped or reset.
local timer, err = time.timer("5s")
if err then
return nil, err
end
-- Wait for timer
timer:response():receive()
send_reminder()
-- Reset on activity
local idle_timer, err = time.timer("5m")
if err then
return nil, err
end
local idle_ch = idle_timer:response()
while true do
local r = channel.select{
user_activity:case_receive(),
idle_ch:case_receive()
}
if r.channel == idle_ch then
logout_user()
break
end
idle_timer:reset("5m")
end
-- Stop timer
timer:stop()
| Parameter | Type | Description |
|---|---|---|
duration |
number/string/Duration | Time until fire |
Returns: Timer, error
| Timer Method | Parameters | Returns | Description |
|---|---|---|---|
response() |
- | Channel | Get timer channel |
channel() |
- | Channel | Alias for response() |
stop() |
- | boolean | Cancel timer |
reset(duration) |
number/string/Duration | boolean | Reset with new duration |
ticker
Creates a repeating timer that fires at regular intervals.
-- Periodic task
local ticker, err = time.ticker("30s")
if err then
return nil, err
end
local ch = ticker:response()
while true do
local tick_time = ch:receive()
check_health()
end
The loop above is intended for a long-running process. A separate finite rate-limiting pattern is:
-- Rate limiting
local ticker, err = time.ticker("100ms")
if err then
return nil, err
end
for _, item in ipairs(items) do
ticker:response():receive()
process(item)
end
ticker:stop()
| Parameter | Type | Description |
|---|---|---|
duration |
number/string/Duration | Interval between ticks |
Returns: Ticker, error
| Ticker Method | Parameters | Returns | Description |
|---|---|---|---|
response() |
- | Channel | Get ticker channel |
channel() |
- | Channel | Alias for response() |
stop() |
- | boolean | Stop ticker |
Constants
Duration Units
Duration constants are expressed in nanoseconds and can be combined with arithmetic.
time.NANOSECOND -- 1
time.MICROSECOND -- 1,000
time.MILLISECOND -- 1,000,000
time.SECOND -- 1,000,000,000
time.MINUTE -- 60 * SECOND
time.HOUR -- 60 * MINUTE
-- Example usage
time.sleep(5 * time.SECOND)
local timeout, err = time.after(30 * time.SECOND)
if err then return nil, err end
Format Layouts
time.RFC3339 -- "2006-01-02T15:04:05Z07:00"
time.RFC3339NANO -- "2006-01-02T15:04:05.999999999Z07:00"
time.RFC822 -- "02 Jan 06 15:04 MST"
time.RFC822Z -- "02 Jan 06 15:04 -0700"
time.RFC850 -- "Monday, 02-Jan-06 15:04:05 MST"
time.RFC1123 -- "Mon, 02 Jan 2006 15:04:05 MST"
time.RFC1123Z -- "Mon, 02 Jan 2006 15:04:05 -0700"
time.DATE_TIME -- "2006-01-02 15:04:05"
time.DATE_ONLY -- "2006-01-02"
time.TIME_ONLY -- "15:04:05"
time.KITCHEN -- "3:04PM"
time.STAMP -- "Jan _2 15:04:05"
time.STAMP_MILLI -- "Jan _2 15:04:05.000"
time.STAMP_MICRO -- "Jan _2 15:04:05.000000"
time.STAMP_NANO -- "Jan _2 15:04:05.000000000"
Months
time.JANUARY -- 1
time.FEBRUARY -- 2
time.MARCH -- 3
time.APRIL -- 4
time.MAY -- 5
time.JUNE -- 6
time.JULY -- 7
time.AUGUST -- 8
time.SEPTEMBER -- 9
time.OCTOBER -- 10
time.NOVEMBER -- 11
time.DECEMBER -- 12
Weekdays
time.SUNDAY -- 0
time.MONDAY -- 1
time.TUESDAY -- 2
time.WEDNESDAY -- 3
time.THURSDAY -- 4
time.FRIDAY -- 5
time.SATURDAY -- 6
Errors
| Condition | Kind | Retryable |
|---|---|---|
| Invalid duration format | errors.INVALID |
no |
| Parse failed | errors.INVALID |
no |
| Empty location name | errors.INVALID |
no |
| Location not found | errors.NOT_FOUND |
no |
| Duration <= 0 (timer/ticker) | errors.INVALID |
no |
local t, err = time.parse(time.RFC3339, "invalid")
if err then
if errors.is(err, errors.INVALID) then
print("Invalid format:", err:message())
end
return nil, err
end
local loc, err = time.load_location("Unknown/Zone")
if err then
if errors.is(err, errors.NOT_FOUND) then
print("Location not found:", err:message())
end
return nil, err
end
See Error Handling for working with errors.