Perch
Lua APITime

Time

perch.time: zoned wall-clock time for now or any instant, the machine's zone, formatting and parsing, and the time types. Always granted.

2 min readUpdated Sep 9, 2026

The sandbox has no os.time or os.date; this library replaces them. Every function is synchronous and needs no scope. Zones are IANA names ("Europe/Berlin", "America/New_York"), which is what a settings.timezone row stores.

Four analog clocks for four zones, a clock ticker with the seconds gauge, and a date pill

perch.time.now

perch.time.now(zone: string?): civil_time_t

The current wall-clock time in zone, or in the machine’s zone when omitted. An unknown zone returns { error = "unknown zone" }.

local t = perch.time.now("Asia/Tokyo")
clock:push({ time = string.format("%02d:%02d", t.hour, t.min) })

perch.time.at

perch.time.at(epoch: number, zone: string?): instant_t

The same question about any instant: epoch is seconds since the Unix epoch. Returns the civil_time_t fields plus epoch echoed back.

local d = perch.time.at(news.items[1].date)
w:push({ when = string.format("%d/%d", d.mon, d.day) })

perch.time.parse

perch.time.parse(text: string): number?

Turns an ISO-8601 timestamp into epoch seconds, or nil when the text is not a date. Accepted: a plain date ("2026-08-30"), a date and time ("2026-08-30T12:34", optional ":56" seconds), a trailing "Z", and an offset as "+hh:mm", "+hhmm" or "+hh". Text with no offset is read as UTC.

local epoch = perch.time.parse(payload.published_at)
if epoch then
  local d = perch.time.at(epoch)
  w:push({ line = string.format("%02d:%02d", d.hour, d.min) })
end

perch.time.zone

perch.time.zone(): string

The machine’s own IANA zone name ("America/Chicago"), or "UTC" when it cannot be determined.

perch.time.format

perch.time.format(spec: string, zone: string?): string

Formats the current time with a C++ chrono spec without braces: "%H:%M", "%A %d %B", "%I:%M %p". Returns "?" for an invalid spec or zone. For any other instant, use perch.time.at and format the fields yourself.

date:push({ line = perch.time.format("%A, %d %B") })

Types

export type civil_time_t = { year: number, mon: number, day: number, hour: number, min: number, sec: number, dow: number, off: number, error: string? }
export type instant_t = civil_time_t & { epoch: number }
FieldMeaning
year, mon, daythe date; mon is 1..12
hour, min, sec0..23, 0..59, 0..59
dowday of week, 0 is Sunday
offthe zone’s current UTC offset in minutes, DST applied
error"unknown zone" when the zone was not recognised; the other fields are then absent
epochon an instant_t: the seconds you passed to perch.time.at
esc
Type to search
navigate open