Draft · Proposed for publication with OFP-014

Date & Time Handling Across the Standard

CategoryIntegrationRead5 minStatusDraft · OFP-014

Every date-time field in the standard identifies an instant in time, not a wall-clock reading. The same instant has many valid spellings, and all of them must behave identically. Getting this wrong produces errors that are invisible at long time horizons and severe at short ones.

Data SharingConsentsOzone Connect

This article is a draft attached to OFP-014. It sets out how to produce and consume date-times across the standard, and publishes to the knowledge base if that proposal is agreed. The date-range query parameters on the transaction and statement list endpoints are specified separately and are not covered here.

01 The model

A date-time is an instant, not a wall clock

Date-time fields across the standard — ExpirationDateTime, CreationDateTime, TransactionDateTime, BookingDateTime, ValueDateTime and the rest — are defined as type: string, format: date-time: an ISO 8601 / RFC 3339 date-time carrying a mandatory timezone offset. Three rules follow, and everything else here is a consequence of them.

  • A date-time is an instant. The offset is part of the value, not decoration.
  • Equivalent representations MUST behave identically. Two encodings of the same moment are the same value.
  • The offset is mandatory. A value without one is not valid against the standard and MUST NOT be produced.
Five spellings of one momenttext
2027-07-22T00:00:00Z
2027-07-22T00:00:00.000Z
2027-07-22T00:00:00+00:00
2027-07-22T00:00:00.000+00:00
2027-07-22T04:00:00+04:00

# Five encodings. One instant. A conforming implementation
# resolves all five to the same moment.

Why this matters more than it looks. The UAE is UTC+04:00. An implementation that reads the digits and ignores the offset resolves a UTC value to an instant four hours earlier than intended. At a thirty-day consent expiry that error is invisible. At a one-hour expiry the consent is already expired on arrival. The bug does not announce itself — it ships, and surfaces later as an unreproducible customer complaint.

Do not rely on your counterparty, or on the platform, to normalise for you

Normalise at your own boundary, on the way in. Treat every date-time you receive as a string that must be parsed into an instant before it is used, stored, or compared.

02 For TPPs

What to send, and how to read what comes back

Sending

  • SHOULD emit UTCZ or +00:00. Both are equally valid; pick one and be consistent.
  • MUST include an offset. A value with no offset is rejected by the API Hub on consent creation, with a format - date-time error.
  • MUST NOT use -00:00. ISO 8601 prohibits a negative zero offset; RFC 3339 permits it but assigns it “UTC, local offset unknown” semantics, which is not what you mean.
  • Fractional seconds are optional. Send them or omit them — but do not rely on the value coming back at the same precision.

Receiving

  • Parse into an instant. Apply the offset. Never read the wall-clock digits.
  • Never default a missing offset to local time. Treat it as an error, or at minimum default to UTC. Defaulting to local is what turns a missing offset into a silent four-hour error.
  • Never compare date-times as strings. 2027-07-22T00:00:00Z and 2027-07-22T00:00:00.000Z are the same instant and different strings. Anything built on string equality — deduplication keys, idempotency hashes, change detection — breaks the moment a counterparty adjusts its serialiser. Key on identifiers such as TransactionId, or on the parsed instant at a fixed precision.
  • A single payload may legitimately mix offsets. A consent record can carry the TPP's ExpirationDateTime as Z alongside the platform's own CreationDateTime as +04:00. Both are correct.
Expect variation across LFIs

Different institutions serialise differently, and legitimately so. Record what each counterparty emits and alert when the shape changes — a serialiser change upstream produces no error, only different bytes.

03 For LFIs

Apply the offset, and check your own stack first

Receiving from the API Hub

  • Parse the offset. This is the single most important line in this article. A consent expiry of 2027-07-22T00:00:00Z is 2027-07-22T04:00:00 UAE time — not 2027-07-22T00:00:00 UAE time.
  • Do not re-implement validation the platform already performs. The API Hub validates consent date-times on creation: it rejects values with no offset, and rejects an ExpirationDateTime that is not in the future. Re-validating in the LFI adds a second, divergent implementation of a centralised check.
  • Beware your own framework. In most reported cases of “we received it without an offset”, the offset was present on the wire and removed by a deserialiser, an ORM column mapping, or a log formatter.
Capture the raw body before you raise a ticket

When diagnosing a missing offset, capture the raw request body before any JSON parsing. A value read back from your own database or logs has already passed through the layer most likely to be at fault, so it cannot tell you what arrived.

Emitting

  • SHOULD emit UTCZ or +00:00 — on every date-time you produce.
  • MUST include an offset on every date-time in every response.
  • Where a transaction's local context matters, carry it in the dedicated LocalTimeZone field (format UTC+04:00, including the UTC prefix — a bare +04:00 fails that field's pattern). Do not express local context by shifting the offset on TransactionDateTime.

A two-minute self-test

Submit the same instant twice, spelled differentlytext
2027-07-22T00:00:00Z
2027-07-22T04:00:00+04:00

# The same moment, written two ways. If your system behaves
# differently for these two, it is not applying the offset.

If your system behaves differently for those two values, your implementation is not applying the offset — and you have reproduced the defect without instrumenting anything.

04 Test vectors

Verify your normalisation

Epoch is seconds since 1970-01-01T00:00:00Z.

ValueEpochUAE local time
2027-07-22T00:00:00Z18162144002027-07-22T04:00:00+04:00
2027-07-22T00:00:00.000Z18162144002027-07-22T04:00:00+04:00
2027-07-22T00:00:00+00:0018162144002027-07-22T04:00:00+04:00
2027-07-22T00:00:00.000+00:0018162144002027-07-22T04:00:00+04:00
2027-07-22T04:00:00+04:0018162144002027-07-22T04:00:00+04:00
2027-07-22T00:00:00-05:0018162324002027-07-22T09:00:00+04:00
2027-04-05T10:43:07+00:0018069217872027-04-05T14:43:07+04:00

The first five rows are one instant. If your implementation produces more than one distinct value for them, it is not conforming. The sixth row is a genuinely different instant, five hours later — included so the test distinguishes “applies the offset” from “ignores the offset and happens to agree”.