Guides / Timestamp strings

Keep Timestamp Offsets When Converting JSON to CSV

Export the original ISO 8601 timestamp as text, then add a separate UTC column only when the offset is known. A timestamp with +08:00, -04:00 or Z can identify an instant. A timestamp without an offset cannot: leave its UTC cell blank and flag it for clarification. Do not silently use the computer's local time zone.

Three different strings can identify the same instant

Our original export contains three records with different local clock readings and explicit offsets. They all normalize to 2026-10-06T16:15:00Z. Preserve each original string: normalization changes its representation and does not tell you which named regional time zone produced it.

2026-10-07T00:15:00+08:00
2026-10-06T16:15:00Z
2026-10-06T12:15:00-04:00

Python distinguishes aware and naive datetimes. An offset-free value does not say whether it means UTC or a local clock. The astimezone documentation also describes how a naive value may be presumed to use system local time. This recipe prevents that path by requiring an explicit offset before conversion.

A four-column contract that makes unresolved rows visible

Use id, created_at_raw, created_at_utc and timestamp_status. Keep every input record. Rows lacking a usable offset stay in the output with blank UTC and an explicit status; this is a rejection of UTC derivation, not a dropped CSV row. Consumers must select timestamp_status == ok before treating a UTC value as usable.

IDOriginal stringUTC derived valueStatus
T012026-10-07T00:15:00+08:002026-10-06T16:15:00Zok
T022026-10-06T16:15:00Z2026-10-06T16:15:00Zok
T032026-10-06T12:15:00-04:002026-10-06T16:15:00Zok
T042026-10-07T00:15:00(blank)missing_offset
T052026-10-07T00:15:00-00:00(blank)unknown_offset
T062026-02-30T00:15:00Z(blank)invalid_datetime
T072026-10-06T16:15:00.123400Z2026-10-06T16:15:00.123400Zok

Run the explicit conversion recipe

Download the original input and Python script below, then run:

python iso-offset-to-csv.py iso-offset-input.json new-offset-output.csv

The script uses datetime.fromisoformat after a deliberately narrow format check, and converts aware values with astimezone(timezone.utc). It preserves the raw string rather than replacing it. The output file must not already exist. All records are validated for the declared two-string-field input shape before the output is created.

Original seven-record inputPython conversion scriptMeasured outputExecution evidence

Interpret each flag before importing the CSV

What we measured, and what we did not

On 2026-10-08, Python 3.12.14 exported our seven original records into four columns. CSV read-back preserved all seven raw timestamp strings. The first three UTC cells matched exactly. The missing-offset, unknown-offset and invalid-date rows remained present with blank UTC values. A six-digit fractional-second example retained .123400 in its UTC representation. These findings apply to the downloadable fixture and recipe, not every converter.

The recipe accepts extended calendar dates with uppercase T, seconds, optional one-to-six fractional digits, and uppercase Z or a colon-separated numeric offset. It excludes leap seconds, named zones, week dates and greater fractional precision. It is not a general ISO 8601 validator or a daylight-saving resolver. Its 2,000,000-byte input cap is a deliberate recipe policy; it loads the small input into memory. It rejects duplicate object keys and nonstandard JSON constants. An ok status validates conversion under this contract, not the truth of the source clock.

Keep spreadsheet display separate from source evidence

Import the raw column as text when using a spreadsheet. Formatting a cell as a date may change how it looks; do not use the display as proof that the offset or original spelling survived. Compare a CSV read-back to the source strings and retain the source file. Review untrusted spreadsheet formulas separately before opening an arbitrary export.

Verify every row and field after export. If values are numeric epochs, use the distinct guides list to find the seconds-versus-milliseconds workflow; this page handles timestamp strings.

Preserve raw timestamp strings, derive a separate UTC value, and flag unresolved offsets.
Original strings remain evidence even when three clock readings map to one UTC instant.

Watch the workflow

Open the JSON to CSV converter