MeridianMERIDIAN

ISO 8601 vs RFC 3339 timestamps

The types

TypeFormPatternDuckDB transformSample
ISO 8601T and Z^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d{1,6})?Z$strptime({col}, '%Y-%m-%dT%H:%M:%SZ')2024-01-15T10:30:00Z
ISO 8601 with Timezone OffsetT and offset^\d{4}-\d{2}-\d{2}[Tt]\d{2}:\d{2}:\d{2}[+-](?:\d{2}(?::?\d{2})?|\d:\d{2})$CAST(regexp_replace(upper({col}), '([+-])([0-9]):([0-9]{2})$', '\10\2:\3') AS TIMESTAMPTZ)2024-01-15T10:30:00+05:00
ISO Datetime, second precision (no timezone)T, no zone^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}$strptime({col}, '%Y-%m-%dT%H:%M:%S')2013-06-04T01:02:03
ISO with Space Separator and ZSpace and Z^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}Z$strptime({col}, '%Y-%m-%d %H:%M:%SZ')2024-01-15 14:30:00Z
RFC 3339 (Space Separator)Space and offset^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}(?:\.\d+)? ?(?:[Zz]|[+-]\d{2}:?\d{2})$CAST(regexp_replace(upper({col}), ' ([+-]|Z)', '\1') AS TIMESTAMPTZ)2024-01-15 10:30:00+00:00

What the standards say

ISO 8601 joins a date and a time of day into one value with the letter T.[1] ISO 8601:2004 allowed the T to be left out by mutual agreement, but ISO 8601-1:2019 removed that provision.[1] ISO 8601 itself does not allow a space between the date and time parts.[1] RFC 3339 defines a date and time format for use in Internet protocols that is a profile of the ISO 8601 standard.[2] RFC 3339's grammar joins the full date and the full time with the letter T.[3] A note in RFC 3339 says applications may choose, for the sake of readability, to separate the full date and full time with a space character instead.[3]

ISO 8601 says that if no UTC relation information is given with a time representation, the time is assumed to be in local time.[1] RFC 3339's date-time grammar requires a time offset, either Z or a numeric offset, after the time.[3] RFC 3339 defines Z as a suffix that, applied to a time, denotes a UTC offset of 00:00.[3] Wikipedia notes that RFC 3339 deviates from ISO 8601 by allowing a zero offset to be written as -00:00.[1]

RFC 3339 notes that the T and Z characters may alternatively be written in lower case as t and z.[3] RFC 3339 says applications that generate its format should use upper case letters.[3] RFC 3339 only allows hours from 00 to 23, although ISO 8601 permits the hour to be 24.[2]

FineType's registry labels the space-separated form datetime.timestamp.rfc_3339.[4] FineType's registry labels the T-separated form iso_8601_offset.[4]

How finetype tells them apart

From finetype 0.6.66's source.

finetype's datetime format reader reads a value with a T between the date and the time, and whole seconds, as iso_8601 when a Z follows the seconds, as iso_8601_offset when a four-digit offset such as +05:30 follows them, and as iso_seconds when nothing does.[5][6] It reads a value with a space between the date and the time, and whole seconds, as iso_space_zulu when a Z follows the seconds, and as rfc_3339 when a four-digit offset such as +05:30 does; a two-digit offset such as +03 reads as pg_short_offset instead.[7][8] The reader takes the offset with or without a colon, as +05:30 or +0530, and for rfc_3339 it also takes a space before the offset.[9][10] finetype's source says each format is anchored at both ends with exact field widths, so a value matches at most one format.[11] The reader picks a format only when at least 95% of the column's sampled non-null values match it.[12][13]

Because these forms carry separators, finetype does not ask its model to agree before applying the reader's choice, as it does for a bare integer.[14][15] finetype applies that reading only when at least 90% of the column's non-empty sampled values pass the chosen type's own validation rule.[16][17]

finetype's own tests pin that a space-separated value ending in Z reads as iso_space_zulu, not rfc_3339.[18] The same tests pin that the reader reads none of 2024-01-01t10:00:00+05:30, 2024-01-01 10:00:00z, 2024-01-01T10:00:00+05 or 2024-01-01T10:00:00+5:30, although the registry's published rules accept them.[19][20][21] When no single format matches at least 95% of a column of these timestamps, the reader returns nothing and this step leaves the column's type as it was.[22][23]

Read the names as a description of the characters a value holds, not as a verdict on which standard its writer followed. (Meridian's view)

What finetype's evaluation shows

The evaluation report in finetype's repository at release 0.6.66 measured release 0.6.60 on 2026-09-23.[24] The report says its tables are generated by a separate tool that has drifted from the SQL harness it claims to mirror, and that a figure taken from it for publication must be its SQL headline.[25] Until finetype publishes a measurement for each type, this page gives no figure for how often it mistakes one form for another. (Meridian's view)

Who writes each form

T and Z

MDN documents the output of JavaScript's Date.prototype.toISOString() as always 24 or 27 characters long, in the form YYYY-MM-DDTHH:mm:ss.sssZ or ±YYYYYY-MM-DDTHH:mm:ss.sssZ.[26] GitHub's REST API documentation says all timestamps return in UTC time, ISO 8601 format: YYYY-MM-DDTHH:MM:SSZ.[27]

T and offset

The OpenAQ air-quality API returns each datetime with both a UTC value and a local value, the local one written with an offset such as 2019-07-11T14:00:00-06:00.[28]

T, no zone

Java's LocalDateTime class is a date-time without a time-zone in the ISO-8601 calendar system, such as 2007-12-03T10:15:30.[29] The HTML Standard's normalised local date and time string separates the date from the time with a capital T.[30]

Space and Z

Elixir's DateTime.to_string renders a UTC datetime in this shape, with 2000-02-29 23:00:07Z as its documented example.[31]

Space and offset

PostgreSQL accepts the T separator on input, but on output it uses a space rather than T.[32] Python's documentation shows isoformat(' ') producing 2002-12-25 00:00:00-06:39.[33]

Sources

  1. [1]ISO 8601secondary source
  2. [2]Date and Time on the Internet: Timestampsstandard
  3. [3]RFC 3339: Date and Time on the Internet: Timestampsstandard
  4. [4]finetype/labels/definitions_datetime.yaml at main · meridian-online/finetype · GitHubFineType
  5. [5]finetype 0.6.66, datetime_format.rsFineType
  6. [6]finetype 0.6.66, datetime_format.rsFineType
  7. [7]finetype 0.6.66, datetime_format.rsFineType
  8. [8]finetype 0.6.66, datetime_format.rsFineType
  9. [9]finetype 0.6.66, datetime_format.rsFineType
  10. [10]finetype 0.6.66, datetime_format.rsFineType
  11. [11]finetype 0.6.66, datetime_format.rsFineType
  12. [12]finetype 0.6.66, datetime_format.rsFineType
  13. [13]finetype 0.6.66, datetime_format.rsFineType
  14. [14]finetype 0.6.66, datetime_format.rsFineType
  15. [15]finetype 0.6.66, column/mod.rsFineType
  16. [16]finetype 0.6.66, column/mod.rsFineType
  17. [17]finetype 0.6.66, column/mod.rsFineType
  18. [18]finetype 0.6.66, datetime_format.rsFineType
  19. [19]finetype 0.6.66, datetime_format.rsFineType
  20. [20]finetype 0.6.66, definitions_datetime.yamlFineType
  21. [21]finetype 0.6.66, definitions_datetime.yamlFineType
  22. [22]finetype 0.6.66, datetime_format.rsFineType
  23. [23]finetype 0.6.66, column/mod.rsFineType
  24. [24]finetype evaluation report (release 0.6.60)FineType
  25. [25]finetype evaluation report (release 0.6.60)FineType
  26. [26]Date.prototype.toISOString() - JavaScript | MDNdocumentation
  27. [27]Getting started with the REST APIorganisation
  28. [28]Dates, times and timezonesorganisation
  29. [29]LocalDateTime (Java SE 21 & JDK 21)documentation
  30. [30]HTML Standardstandard
  31. [31]DateTime (Elixir v1.20.4)documentation
  32. [32]PostgreSQL: Documentation: 18: 8.5. Date/Time Typesdocumentation
  33. [33]datetime — Basic date and time typesdocumentation

Type Registry