Date and time
Lutra has separate types for calendar dates, times of day, durations, and instants in time. That separation helps you write reporting code without mixing up local calendar logic, elapsed spans, and UTC instants.
Use these types for different jobs:
Datefor a calendar day such as@2025-11-14Timefor a time of day in the range[0, 24h)Durationfor a signed, unbounded span such as@213:06:00or@-5:12:31Timestampfor an instant in time such as@2025-12-29T16:07:44
Understand the four core types¶
Date stores a calendar day.
Internally, it is backed by days_epoch.
func main() -> {
date = @2025-11-14,
days_epoch = @2025-11-14.days_epoch,
same_value = @2025-11-14.0,
}
Duration stores a signed number of microseconds.
It is the result of subtracting two instants or two dates, and it can be
negative or larger than 24 hours.
func main() -> {
span = @213:06:00,
negative = @-5:12:31,
micros = @00:15:00.microseconds,
}
Time stores a time of day, always in the range [0, 24h).
It is backed by micros_midnight, the number of microseconds since midnight.
There is no Time literal, so you construct one with the Time constructor.
func main() -> {
time = Time(58064000000),
micros_midnight = Time(58064000000).micros_midnight,
}
Timestamp stores a UTC instant with microsecond precision.
It does not carry an embedded timezone.
func main() -> {
ts = @2025-12-29T16:07:44,
micros = @2025-12-29T16:07:44.microseconds,
}
Use literals when you need fixed values¶
Lutra has built-in literal syntax for dates, durations, and timestamps.
The @hour:minute:second form is a Duration, not a time of day:
func main() -> {
date = @2025-11-14,
duration = @16:07:44,
timestamp = @2025-12-29T16:07:44,
}
Convert a local date to a timestamp¶
Use date::to_timestamp(date, tz) when you want the UTC instant for midnight
at the start of a local day.
func start_of_day() -> date::to_timestamp(@2026-06-03, "Europe/Ljubljana")
This is a common reporting boundary. For example, you might turn a user-selected local date into the timestamp range that you pass to a filter.
Extract year, month, and day from a date¶
Use date::to_year_month_day when you need numeric calendar parts.
func main() -> date::to_year_month_day(@2025-12-29)
That returns:
{year = 2025, month = 12, day = 29}
This is useful when you need a stable calendar key or want to derive the first of a month.
Convert a timestamp to a local date¶
Use timestamp::to_date(ts, tz) when you need local calendar grouping.
func main() -> timestamp::to_date(@2025-12-31T23:30:00, "Europe/Ljubljana")
This program returns @2026-01-01, because Europe/Ljubljana timezone is an hour
ahead of UTC, which means that the local date already falls in the next year.
This conversion is the key rule for calendar reporting:
Timestamp is an instant, but reports are often grouped by local day, week, or
month.
Compute durations¶
Use timestamp::sub for elapsed time between two instants.
func main() -> timestamp::sub(
@2025-12-29T16:30:00,
@2025-12-29T16:07:44,
)
Use date::sub when you want the duration between two dates.
func main() -> date::sub(@2025-12-29, @2025-12-01)
Both results are Duration values.
Work with times of day¶
Use the time module to combine a Time with a Duration.
The add and sub functions wrap their result within [0, 24h), so adding a
span that crosses midnight rolls over to the start of the day.
func main() -> {
later = time::add(Time(82800000000), @4:00:00),
earlier = time::sub(Time(3600000000), @4:00:00),
elapsed = time::diff(Time(7200000000), Time(3600000000)),
as_span = time::to_duration(Time(3600000000)),
}
Use time::diff to get the signed Duration between two times of day, and
time::to_duration to measure a time of day as a span from midnight.
Check timezone offsets when you need them¶
Use timezone_offset(time_zone, date) when you need the UTC offset that
applies on a local date.
func main() -> timezone_offset("Europe/Ljubljana", @2025-07-01)
This is most useful when you are debugging timezone-sensitive logic.
Most reporting code can stay at the level of timestamp::to_date and
date::to_timestamp.
Group rows by local calendar month¶
When you group timestamps by month, convert them to a local Date first.
Then derive a stable month key.
type Event: {
ts: Timestamp,
amount: Int64,
}
const events: [Event] = [
{ts = @2025-01-01T00:30:00, amount = 10},
{ts = @2025-01-15T12:00:00, amount = 15},
{ts = @2025-02-01T09:00:00, amount = 7},
]
func totals_by_month(events: [Event], time_zone: Text): [{month: Int32, total: Int64}] -> (
events
| group_map(
e -> (
let d = e.ts | timestamp::to_date(time_zone);
let parts = d | date::to_year_month_day;
d.days_epoch - ((parts.day | to_int32) - 1)
),
func (month, rows) -> {
month = month,
total = rows | map(r -> r.amount) | sum(),
}
)
| sort(x -> x.month)
)
The key idea is not the arithmetic itself. The key idea is the order of operations:
- convert the instant to a local
Date - derive the calendar bucket from that date
- aggregate within that bucket