Time Reference¶
Timestamp detection, parsing, time windows and time functions. For a task-oriented walkthrough see guide/time.md.
Which Field Is the Timestamp¶
Kelora parses one timestamp per event. The parsed value drives --since/--until, --span, --mark-gaps, --merge-sorted, -z/-Z and --normalize-ts, and is available in scripts as meta.parsed_ts (UTC datetime, () if missing). The original field keeps its raw string.
Without --ts-field, the first of these names present in the event is used, in this priority order (exact, case-sensitive match):
ts, _ts, timestamp, at, time, @timestamp, log_timestamp, event_time, datetime, date_time, created_at, logged_at, _t, @t, t
Only that field is tried: if ts exists but cannot be parsed, a later time field is not used. Timestamp or TIME are not detected. Name the field with --ts-field:
echo '{"timestamp":"2024-01-15T10:30:00Z","created_at":"2024-01-15T10:31:00Z"}' \
| kelora -j --ts-field created_at
Parsers that produce a timestamp (syslog, combined, cri, log4j, glog, … — see Input Formats) put it in ts.
Without --ts-format, common layouts are recognized automatically: RFC 3339/ISO 8601 (with T or space, any fraction, Z or offset), Apache 15/Jan/2024:10:30:00 +0000, syslog Jan 15 10:30:00, Python 2024-01-15 10:30:00,123, and Unix epochs as numbers or numeric strings:
| Input | Read as |
|---|---|
1735566123 |
seconds (10 digits) |
1735566123000 |
milliseconds (13 digits) |
1735566123000000 |
microseconds (16 digits) |
1735566123000000000 |
nanoseconds (19 digits) |
1735566123.456 |
seconds with fraction |
Parsing Options¶
| Flag | Purpose |
|---|---|
--ts-field <FIELD> |
field to parse instead of the auto-detected one |
--ts-format <FMT> |
chrono format of that field; see Format Codes |
--input-tz <TZ> |
zone for timestamps without an offset: UTC (default), local, or an IANA name such as Europe/Berlin. The TZ environment variable also sets it. |
--input-year <YEAR> |
year for year-less timestamps, or auto (default) |
Year and Timezone¶
Timezone. A numeric offset in the timestamp (+0200, Z) is always used. A timestamp without one (syslog, log4j, python-logging, glog, apache-error, postgres, 2024-01-15 10:30:00, …) is read in --input-tz, else the TZ environment variable, else UTC. A trailing zone abbreviation such as CEST or EST makes auto-detection fail, because abbreviations are ambiguous and carry no DST information; put %Z in --ts-format to skip it and set --input-tz. If logs were written in local time and --input-tz is not set, every timestamp is shifted, and so are time windows, span boundaries and merge order. Kelora prints a hint once when a time filter, --span or --normalize-ts relies on the UTC assumption.
Year. Year-less layouts (syslog Jan 15 10:30:00, glog, haproxy) get last year, this year or next year: the candidate nearest the current clock that is at most a day in the future. That is right for recent logs and logs that cross New Year, wrong for archives. -s/--stats reports how many years were guessed. --input-year 2005 puts every year-less timestamp in 2005, which means a December→January log gets January 2005, not 2006; split such files or keep auto.
kelora Linux_2k.log --input-year 2005 --since 2005-06-01 --until 2005-07-01
kelora app.log --ts-format '%Y-%m-%d %H:%M:%S' --input-tz Europe/Berlin
Format Codes¶
--ts-format, to_datetime(text, fmt), dt.format(fmt) and -M timestamp:format= use chrono format strings. Quote the format in single quotes so the shell leaves % alone.
| Code | Example | Meaning |
|---|---|---|
%Y / %y |
2024 / 24 |
year, 4 / 2 digits |
%m |
01 |
month 01–12 |
%b / %B |
Jan / January |
month name |
%d |
15 |
day 01–31 |
%j |
015 |
day of year |
%H / %I |
14 / 02 |
hour, 24h / 12h |
%p |
PM |
AM/PM |
%M |
30 |
minute |
%S |
45 |
second |
%.f |
.123, .123456 |
dot plus any number of fraction digits; a comma is accepted too |
%3f / %6f / %9f |
123 / 123456 / 123456789 |
exactly 3 / 6 / 9 fraction digits (put the . or , before it) |
%f |
123456789 |
fraction digits read as a nanosecond count: .%f on .123456 gives 123456 ns, not 0.123456 s. Prefer %.f or %6f. |
%z / %:z |
+0100 / +01:00 |
numeric offset |
%Z |
EST |
zone abbreviation; consumed but ignored |
%a / %A |
Mon / Monday |
weekday name |
%w |
1 |
weekday, 0 = Sunday |
%W / %U |
03 |
week number, Monday / Sunday first |
%s |
1705318200 |
Unix seconds |
--ts-format |
Matches |
|---|---|
'%Y-%m-%dT%H:%M:%S%.f%:z' |
2024-01-15T10:30:00.123+02:00 |
'%Y-%m-%d %H:%M:%S' |
2024-01-15 10:30:00 |
'%Y-%m-%d %H:%M:%S,%3f' |
2024-01-15 10:30:00,123 (Python logging) |
'%d/%b/%Y:%H:%M:%S %z' |
15/Jan/2024:10:30:00 +0100 (Apache) |
'%b %d %H:%M:%S' |
Jan 15 10:30:00 (syslog; year guessed) |
'%m/%d/%Y %I:%M:%S %p' |
01/15/2024 02:30:00 PM |
'%a %b %d %H:%M:%S %Y' |
Mon Jan 15 14:30:45 2024 |
Time Windows: --since, --until¶
--since keeps events at or after a time, --until at or before.
| Value | Meaning |
|---|---|
2024-01-15T10:00:00Z, 2024-01-15T10:00:00+01:00 |
absolute |
2024-01-15 10:00, 2024-01-15T10:00:00 |
absolute, read in --input-tz |
2024-01-15, 2024/01/15, 01/15/2024, 15.01.2024, January 15, 2024, 15 January 2024 |
midnight UTC of that date (not --input-tz) |
10:30, 10:30:00 |
that UTC clock time on today's local date |
1705318200, 1705318200.5, 1705318200000 |
Unix epoch, same digit rules as above |
now |
now |
today, yesterday, tomorrow |
00:00 UTC on that local date |
1h, -1h, 30m, 2d, 1w, 90s, 3 days |
that long ago |
+1h, +30m |
that far in the future |
now-15m, now+5m |
relative to now |
since+30m, since-30m |
relative to the --since value (use in --until) |
until+1h, until-1h |
relative to the --until value (use in --since) |
Units: s, m, h, d, w, also spelled out (min, hours, days, weeks, …). One number and one unit only: 1h30m and 500ms are rejected; write 90m.
kelora app.log --since 1h # last hour
kelora app.log --since now-15m
kelora app.log --since 2024-01-15T10:00:00Z --until 2024-01-15T11:00:00Z
kelora app.log --since 2024-01-15T10:00:00Z --until since+45m
kelora app.log --since until-1h --until 2024-01-15T11:00:00Z
kelora app.log --since -2h --until since+1h # one hour, starting two hours ago
- Only one bound may refer to the other:
--since until-1h --until since+1his an error, as issince+…without--since. 1h,todayand bare clock times are relative to the current clock, not to the log. On an archived log use absolute timestamps.- The window reads the timestamp the parser produced and runs before every script stage. Setting a timestamp field in
--execdoes not move an event in or out of the window. For a timestamp you build in a script, filter on it instead:
kelora -f json app.log \
--exec 'e.derived = to_datetime(e.date + "T" + e.time + "Z")' \
--filter 'e.derived >= to_datetime("2024-05-01T00:00:00Z")'
- An event without a parsed timestamp is dropped by the window (it never reaches
--filter,--execor--assert), and a warning gives the count. Fix the timestamp with--ts-field/--ts-format, merge continuation lines with-M, or use--filterinstead of a window for undated cascadelineevents.--strictaborts on the first such event. --cut-at(for--drain-diff) accepts the same values except thesince/untilanchors.
Display and Normalization¶
| Flag | Effect |
|---|---|
-z, --show-ts-local |
show the timestamp field as local RFC 3339; default output format only |
-Z, --show-ts-utc |
show it as UTC RFC 3339; default output format only |
--normalize-ts |
rewrite the timestamp field as RFC 3339 UTC (2024-01-15T10:30:00+00:00) in every output format. Applied at output: scripts still see the raw value. |
-z/-Z have no effect on -F json, logfmt or csv; use --normalize-ts there.
Other time-based flags: --span 5m (fixed time windows, see guide/spans.md), --mark-gaps 5m (divider line where consecutive events are at least that far apart), --merge-sorted (merge files by timestamp, see guide/files.md).
Rhai Functions¶
Use meta.parsed_ts for the event's own timestamp; to_datetime() is for other fields. Full list: functions.md.
| Function | Returns / notes |
|---|---|
now() |
current time, UTC |
to_datetime(text) |
parse with the auto-detection rules above; naive text is read as UTC |
to_datetime(text, fmt) |
parse with a chrono format; error if it does not match |
to_datetime(text, fmt, tz) |
text without an offset is read as wall-clock time in tz: to_datetime("2024-01-15 10:30:00", "%Y-%m-%d %H:%M:%S", "America/New_York") is 10:30-05:00 (15:30 UTC) |
dt.year() .month() .day() .hour() .minute() .second() |
int, in dt's zone |
dt.ts_nanos() |
Unix nanoseconds |
dt.to_iso() |
2024-01-15T10:30:00+00:00 |
dt.format(fmt) |
chrono format, e.g. "%Y-%m-%d %I:%M %p" → 2024-01-15 10:30 AM |
dt.to_utc(), dt.to_local(), dt.to_timezone("Europe/Berlin") |
same instant in another zone |
dt.timezone_name() |
"UTC", "Europe/Berlin" |
dt.round_to("5m") |
floor to the interval (12:34:56 → 12:30:00). Boundaries are computed in UTC, so "1d" on a New York time gives 19:00-05:00; the result keeps dt's zone |
dt.ceil_to("5m") |
up to the next boundary (UTC-based, like round_to); unchanged if already on one |
dt + dur, dt - dur |
datetime |
dt1 - dt2 |
duration, always positive (absolute difference) |
== != < <= > >= |
compare datetimes, or durations |
to_duration("1h30m") |
duration; accepts combined units, spaces, ms, fractions ("1.5h") |
duration_from_seconds(n), _milliseconds, _nanoseconds, _minutes, _hours, _days |
duration |
dur.as_seconds(), as_milliseconds(), as_nanoseconds(), as_minutes(), as_hours(), as_days() |
int, truncated: 90 minutes .as_hours() is 1 |
dur.to_string() |
humanized, largest two units: "1h 30m" |
humanize_duration(ms) |
5000 → "5s" |
d1 + d2, d1 - d2 (positive), d * n, d / n |
duration |
kelora app.log --filter 'meta.parsed_ts.hour() >= 9 && meta.parsed_ts.hour() < 17'
kelora app.log -m --exec 'track_freq("per_5m", meta.parsed_ts.round_to("5m"))'
kelora app.log --exec 'e.age_s = (now() - meta.parsed_ts).as_seconds()'
kelora app.log --exec 'e.duration_ms = (to_datetime(e.end_time) - to_datetime(e.start_time)).as_milliseconds()'
Troubleshooting¶
| Symptom | Check |
|---|---|
-s shows Timestamp: (none found…) |
field name not in the list above (case matters): --ts-field |
-s shows 0/N parsed |
layout not recognized: --ts-format |
| times off by whole hours | naive timestamps read as UTC: --input-tz |
| syslog/glog dated in the wrong year | --input-year |
--since 1h keeps nothing |
it means one hour before now; use an absolute time for old logs |
events vanish with --since/--until |
events without a parsed timestamp are dropped; see the warning count |