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.