Type: explanation · Last reviewed: 2026-07-18
Every value in Pulse is stamped with when it happened, and getting that right across servers, browsers, and plants in different regions is where time-series systems usually trip. Pulse keeps it simple by picking one representation everywhere: epoch milliseconds — the number of milliseconds since 1970-01-01 UTC. That is an absolute instant with no timezone attached, so there is never any ambiguity about what a timestamp means. This page explains how time flows through the system and where a timezone does (and doesn't) enter the picture.
Everywhere you exchange a time with Pulse — a write timestamp, a query start/end, or a timestamp in a response — it is epoch milliseconds (a 64-bit unsigned integer), unless a specific endpoint says otherwise.
1720008000000 // = 2024-07-03T12:00:00.000Z (UTC)
Because epoch ms is UTC-based and timezone-free, the same instant has the same number no matter where the client is. There is no "the server's timezone" to reason about for stored data — a value written from a plant in Chennai and read in a browser in Berlin is the same integer to both.
The Kairos-compatible SDK output (kairos=true) uses the same millisecond timestamps.
A collection has a fixed sampling interval (interval_ms) — 60,000 ms (one minute) in the canonical acme-power / plant-1 / boiler-2 example. Chronos stores data on a dense grid: each day is a file of fixed-width slots, and every timestamp maps to exactly one slot.
slot = (timestamp_ms − day_midnight_ms) / interval_ms
Day files partition on UTC midnight (day_midnight_ms = floor(timestamp_ms / 86,400,000) × 86,400,000), so a 1-minute grid has 1,440 slots per day. A write whose timestamp falls between grid points lands in its slot; a read returns the grid timestamps. This is why two writes 20 seconds apart on a 1-minute grid resolve to the same slot — the grid interval sets your effective resolution.
Under the hood → How Pulse Chronos stores data · Storage engine reference.
A timezone only matters when a human reads a time. "12:00 UTC" should show as 17:30 IST to an operator in India and 14:00 CEST to one in Germany — but that conversion happens at the edge, in the app or your client code, from the same underlying epoch ms. Pulse stores and serves UTC instants; the presentation layer localizes them.
So:
Using boiler-2 on a 60,000 ms grid:
steam_pressure = 78.4 at 1720008000000 (12:00:00 UTC). It lands in today's day file at slot (1720008000000 − 1719964800000) / 60000 = 720 (the 720th minute of the UTC day).1720008020000 (12:00:20 UTC) — 20 s later — maps to the same slot 720; on a 1-minute grid it occupies the same grid point.start=1720008000000, end=1720008120000 returns grid timestamps 1720008000000 and 1720008060000 — the minute marks, not the exact write instants.17:30 and 17:31 IST — a pure display conversion; the stored integers never changed.| Pitfall | Why it bites | Do this instead |
|---|---|---|
Sending a local time string ("2024-07-03 12:00") |
Pulse expects epoch ms; ambiguous without a zone | Convert to epoch ms (UTC) before the call |
| Sending seconds, not milliseconds | Off by 1000× — data lands ~55 years in the past | Multiply Unix seconds by 1000 |
| Expecting back the exact write instant | Reads return grid timestamps | Match your grid interval_ms to the resolution you need |
| Assuming day files follow local midnight | Day boundaries are UTC midnight | Reason about day rollover in UTC |
collection · grid · Kairos · tag