Skip to content

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 ​

FunctionReturns
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

FunctionResult
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

MethodReturns
: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") }

Connect machines, automate workflows, and build custom apps for print production.