Appearance
Time (ctx.time and time)
Scripts have two time APIs. There is no os.time or os.date.
ctx.time: the current time as a plain number or string.time: a library of time values (instants and periods) for parsing, formatting and arithmetic.
Current time: ctx.time
| Function | Returns |
|---|---|
ctx.time.now() | integer milliseconds since the Unix epoch, UTC |
ctx.time.nowIso() | ISO-8601 UTC string with milliseconds, ending in Z |
lua
local millis = ctx.time.now()
local iso = ctx.time.nowIso()
local seconds = millis // 1000
return { millis = millis, iso = iso, seconds = seconds }ctx.time.now() is milliseconds, not seconds. Divide by 1000 before handing it to anything that expects Unix seconds.
Time values: time
Instants and periods are immutable values. Every method returns a new value, so calls can be chained.
Creating instants
| Function | Result |
|---|---|
time.now() | the current instant (not available in product-template resolvers) |
time.new({ year, month, day, hour, minute, second, millis, offset }) | an instant; missing fields default to 1970-01-01 00:00:00.000, offset in minutes (default 0) |
time.parse(isoString) | an instant from ISO-8601 text such as 2026-03-01T08:30:00Z or 2026-03-01T08:30:00+02:00 |
time.fromepoch(millis, offsetMinutes?) | an instant from epoch milliseconds |
Invalid dates and unparseable strings raise an error.
Periods are fixed-length durations: time.period({ days, hours, minutes, seconds, millis }), or time.days(n), time.hours(n), time.minutes(n), time.seconds(n), time.millis(n). A period has :millis(), :add(other) and :negate(). There is no month or year period.
Instant methods
| Method | Returns |
|---|---|
:add(period), :subtract(period) | a new instant |
:diff(other) | the period between the two instants |
:before(other), :after(other), :equals(other) | boolean |
:start_of_minute(), :start_of_hour(), :start_of_day(), :start_of_month(), :start_of_year() | a truncated instant |
:year(), :month(), :day(), :hour(), :minute(), :second(), :offset() | integer |
:epoch_millis() | integer epoch milliseconds |
:with({ … }) | a copy with the given fields replaced |
:to_offset(minutes) | the same instant at a different UTC offset |
:format(fmt?) | a string; ISO-8601 without fmt |
:format supports %Y %m %d %H %M %S %z %%. There are no weekday or month names and no locale.
lua
local shipped = time.parse("2026-03-01T08:30:00Z")
local due = shipped:add(time.days(3)):start_of_day()
local window = due:diff(shipped)
return {
due = due:format("%Y-%m-%d"),
dueIso = due:format(),
hoursUntilDue = window:millis() // 3600000,
overdue = time.now():after(due),
}Time zones
Only fixed UTC offsets exist, given in minutes. There are no named time zones and no daylight-saving rules; convert to an offset yourself if you need local time.
lua
local utc = time.new({ year = 2026, month = 7, day = 1, hour = 12 })
local cest = utc:to_offset(120)
return { utc = utc:format(), local_time = cest:format("%Y-%m-%d %H:%M %z") }