Timestamp format
Last updated: Oct 28, 2026
Rationale
All the timestamps that the infrastructure, applications, logs, and databases of Fluid Attacks make and keep use one canonical format:
YYYY-MM-DDTHH:MM:SS.sssZThis format is the strict intersection of ISO 8601-1:2019,
RFC 3339,
and ECMAScript's Date.prototype.toISOString().
All conformant parsers for these standards accept it,
and all conformant toISOString() generators make it.
Each standard accepts less strict variants. These are the primary reasons why Fluid Attacks selected this exact format and not the less strict variants:
- It makes sure that each UTC instant has only one canonical string. Thus, when two services serialize the same instant, they make strings with the same bytes.
- Lexicographic sequence agrees with chronological sequence. Thus, you can sort timestamps as plain strings without parsing.
- It gives stable bytes for hashing and signing of payloads with timestamps.
- It makes log correlation across services clear, because it does not use local-time conventions or implicit zone assumptions.
- Because it uses UTC, it is not necessary to calculate timezones at read time.
Alternatives
These are the other approaches that Fluid Attacks examined to keep and exchange timestamps. The list starts with the approach that is most interesting for the requirements of Fluid Attacks.
Unix timestamps
This approach keeps instants as integer seconds (or milliseconds, microseconds,
nanoseconds) since the 1970 epoch, for example, 1745939025123.
- They are compact, and by definition they are always UTC.
- They sort correctly when you compare them as integers.
- They are not human-readable. An engineer must convert logs, database dumps, and API responses before the engineer can understand them.
- The unit (s, ms, µs, ns) does not show in the value. Thus, all consumers must agree on the resolution in a different channel. Errors are silent and cause off-by-1000 bugs.
- If you keep them as 32-bit signed integers, the Year 2038 problem applies. If you keep them as 64-bit integers, you prevent this problem, but they are not interoperable with systems that use 32-bit integers by default.
- They do not show context that the canonical string form keeps (calendar date, time of day). This context is important for partitioning, retention, and audit reads.
Locale-formatted strings
These are forms that are easy for humans to read, and that use a regional convention,
for example, 30/04/2026 09:23:45 or Apr 30, 2026 9:23 AM.
- Humans inside one locale know them.
- They are not clear across locales:
04/05/2026is April 5 in the United States and May 4 in most of the other countries. - Lexicographic sequence does not agree with chronological sequence. Thus, you cannot sort them as plain strings.
- There is no standard parser. Thus, each consumer must implement validation and timezone management again.
- They are not correct for machine-to-machine interchange in JSON, OpenAPI, or JSON Schema contexts.
Naive datetimes (no zone designator)
This is a date-and-time string without zone information,
for example, 2026-04-30T14:23:45.123.
- It looks clean, and it is easy to type.
- The same string shows different instants for readers that assume different implicit zones.
- It breaks log correlation across regions and across daylight-saving transitions.
- Each producer and consumer must track the implicit zone in a different channel. This hidden coupling is a usual cause of incidents.
A timezone other than UTC
This approach anchors timestamps to a zone that is not UTC.
Each system can use its own local zone
(for example, 2026-04-30T09:23:45.123-05:00), or all the platform
can use one regional zone, for example, America/Bogota.
- It agrees with the wall clock that engineers in that zone see.
- Two producers in different zones, or the same producer before and after a daylight-saving transition, write different strings for the same instant. This breaks byte-stable hashing, deduplication, and lexicographic ordering.
- Daylight-saving transitions cause wall-clock times that are not clear or that do not occur, and these times do not have a canonical representation.
- Cloud services, databases, container runtimes, and standards use UTC by default. If you select a different anchor, you must change that default in each layer.
- External consumers of the APIs of Fluid Attacks must learn and remember its zone to read the responses. Fluid Attacks does not have a reason to add this friction.
Specification
Rules
- Year (
YYYY): exactly 4 decimal digits, range0001–9999. No sign and no extended years. - Date separator: literal hyphen
-(U+002D). - Date/time separator: literal uppercase
T(U+0054). You cannot use a space or a lowercaset. - Time separator: literal colon
:(U+003A). - Time fields:
HHin00–23,MMin00–59,SSin00–59. You cannot use leap seconds (:60). - Fractional seconds: literal dot
.and then exactly 3 decimal digits (millisecond precision), with zeros on the left if necessary. For example, write 40 ms as.040, not.40. - Time zone: literal uppercase
Z(U+005A), which shows UTC. You cannot use a lowercasez, numeric offsets, or no zone designator. - Total length: exactly 24 characters.
Validation regex
^[0-9]{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])T([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]\.[0-9]{3}Z$The regex does not validate the day of the month against the calendar month or leap years. For this, a calendar-aware check is necessary.
Examples
Correct:
2026-04-30T14:23:45.123Z2026-01-01T00:00:00.000Z2026-12-31T23:59:59.999Z2026-07-15T08:05:02.040Z
Incorrect (each one breaks one of the rules above):
2026-04-30T14:23:45Z: no fractional seconds.2026-04-30T14:23:45.1Z: the fractional width is not 3.2026-04-30T14:23:45.40Z: the fractional width is not 3.2026-04-30T14:23:45.123456Z: the fractional width is not 3.2026-04-30T14:23:45.123: no zone designator.2026-04-30T14:23:45.123z: lowercase zone designator.2026-04-30T14:23:45.123+00:00: numeric offset.2026-04-30T14:23:45.123-00:00: numeric offset.2026-04-30 14:23:45.123Z: space separator.2026-04-30t14:23:45.123Z: lowercase date/time separator.20260430T142345.123Z: basic (compact) form.2026-04-30T24:00:00.000Z: hour 24.2026-04-30T14:23:60.000Z: leap second.2026-02-30T14:23:45.123Z: February 30 is not a calendar date.26-04-30T14:23:45.123Z: two-digit year.+2026-04-30T14:23:45.123Z: signed year.
Usage
The platform uses this format in these ways:
- API responses serialize all temporal fields with it.
- Database records keep all temporal data with it.
- Application and audit logs write timestamps with it.
- The SIEM ingests and correlates security events with it.
For convenience, the user interface of the platform shows timestamps in the local timezone of the user. But the platform keeps and processes all the underlying data in UTC with this format. For the wider UTC policy that this format applies, read Standard timezone.