Skip to content

Repository files navigation

ISO 8601 machine-readable test suite

This is the CalConnect TC DATETIME machine-readable test suite for the ISO 8601 series, developed as joint work with ISO/TC 154/WG 5.

The test suite covers the syntaxes introduced by ISO 8601-1:2026 (Date and time — Representations for information interchange — Part 1: Basic rules) and ISO 8601-2:2026 (Part 2: Extensions).

This repository contains conformance tests in a format-agnostic YAML data model that can be converted into JSON, YAML and XML for consumption by test suite implementations.

Source repository

git clone git@github.com:CalConnect/iso-8601-test-suite.git

The requirement statement fields in requirements/ are independent paraphrases of the normative provisions of ISO 8601-1:2026 and ISO 8601-2:2026.

They are written in original language and do not reproduce verbatim text from the ISO standards.

ISO 8601-1:2026 and ISO 8601-2:2026 are copyrighted by the International Organization for Standardization (ISO). The requirement statements in this repository are original works that describe the same technical constraints using different language, prepared under the principle that functional specifications are not subject to copyright protection.

Data model

The test suite follows the OGC Model for Standards Specification (ModSpec) terminology:

Requirement

An atomic testable criterion (a normative "shall" statement)

Requirements class

A set of consistent requirements on a single standardization target type

Conformance test

An individual test that verifies one or more requirements (pass/fail)

Conformance class

A group of conformance tests corresponding 1:1 to a requirements class

Conformance suite

All conformance classes for a standard

Dependency

A requirements class that must be passed before another can be tested

Profile

A set of conformance classes from one or more standards

The standardization target type is iso-8601-date-time-processor — a software implementation that parses, generates, validates, or computes ISO 8601 date/time expressions.

Identifier scheme

Identifiers use a CURIE-like notation with type prefixes:

suite:{name}

Test suite manifest (e.g. suite:iso-8601)

req-class:{name}

Requirements class (e.g. req-class:calendar-date)

req:{name}

Requirement (e.g. req:cal-date-basic-full)

conf-class:{name}

Conformance class (e.g. conf-class:calendar-date)

conf-test:{name}

Conformance test (e.g. conf-test:cal-date-parse-001)

profile:{name}

Profile (e.g. profile:rfc-3339)

result:{name}

Conformance result set (e.g. result:ruby-date)

Cross-part references use a part prefix: 8601-1:conf-class:calendar-date.

These local identifiers expand to RFC 5141 URNs under the ISO namespace: urn:iso:std:iso:8601:-{part}:ed-1:en:tech:tc154.wg5:{type}:{name}

Clause references

Requirement clause fields use RFC 5141 partial URNs to identify the source clause within the standard. The format follows the pattern:

iso:8601:-{part}:ed-1:en:clause:{number}

Examples:

  • iso:8601:-1:ed-1:en:clause:5.2.2.1 — Part 1, Clause 5.2.2.1

  • iso:8601:-2:ed-1:en:clause:7.2.2 — Part 2, Clause 7.2.2

  • iso:8601:-1:ed-1:en:clause:4,5.1 — Part 1, Clauses 4 and 5.1

Test types

parsing

expression → components

Parse an expression and validate extracted components

generation

components → expression

Build an expression from components

validity

expression → boolean

Check if expression is syntactically valid

equivalence

two expressions → boolean

Two expressions resolve to the same instant

arithmetic

expression + operation → result

Date/time arithmetic

round_trip

expression → expression

Parse → extract → generate → compare (exact or semantic equivalence)

Parsing modes

Parsing and validity tests support a parse_mode field:

  • dedicated (default) — format-specific parser (e.g. strptime with explicit format string)

  • undifferentiated — general/lenient parser (e.g. Date.parse, Date.iso8601)

When parse_mode is omitted, it defaults to dedicated.

Directory structure

iso-8601-test-suite/
├── suite.yaml                        # Machine-readable suite manifest
├── scripts/
│   ├── run-tests                     # Test runner (ruby scripts/run-tests)
│   └── validate                      # Validation script (ruby scripts/validate)
├── lib/                              # Shared Ruby modules
│   ├── term.rb                       # Terminal output (colors, icons)
│   ├── yaml_store.rb                 # Cached YAML loader with error tracking
│   ├── suite_index.rb                # Cross-reference index (IDs → files)
│   ├── component_vocab.rb            # Component vocabulary (data-driven)
│   ├── stats.rb                      # Error/warning/pass tracker
│   ├── schema_validator.rb           # YAML Schema validation engine
│   ├── test_type_handlers.rb         # Test type handler registry (OCP)
│   ├── test_suite_loader.rb          # Test list builder
│   ├── adapter_loader.rb             # Adapter discovery and instantiation
│   ├── exec_adapter.rb               # JSON protocol adapter for external processes
│   ├── graph_util.rb                 # Directed cycle detection
│   └── hash_util.rb                  # Hash key normalization
├── adapters/                         # Pluggable implementation adapters
│   ├── TEMPLATE.rb                   # Ruby adapter interface template
│   ├── README.adoc                   # Adapters overview
│   ├── ruby/                         # Ruby Date/DateTime/Time
│   │   ├── README.adoc
│   │   ├── date.rb                   # In-process adapter (loaded by Ruby adapter loader)
│   │   └── date-exec.rb              # Exec wrapper — same logic, any Ruby runtime
│   ├── python/                       # Python datetime
│   │   ├── README.adoc
│   │   └── datetime.py
│   ├── node/                         # Node.js Date
│   │   ├── README.adoc
│   │   └── datetime.js
│   ├── c/                            # C strftime/strptime
│   │   ├── README.adoc
│   │   └── stdio.c
│   ├── cpp/                          # C++ std::chrono
│   │   ├── README.adoc
│   │   └── chrono.cpp
│   ├── java/                         # Java java.time
│   │   ├── README.adoc
│   │   └── JavaDateTime.java
│   └── rust/                         # Rust chrono (one source, two crate versions)
│       ├── README.adoc
│       ├── chrono-latest/            # chrono = "0.4" (resolves to latest 0.4.x)
│       └── chrono-0419/              # chrono = "=0.4.19" (pinned)
├── schema/
│   ├── suite.yaml                    # YAML Schema for suite manifest
│   ├── requirements-class.yaml       # YAML Schema for requirements class files
│   ├── conformance-class.yaml        # YAML Schema for conformance class files
│   ├── conformance-result.yaml       # YAML Schema for conformance result files
│   ├── profile.yaml                  # YAML Schema for profile files
│   ├── components.yaml               # Component vocabulary schema
│   └── meta.yaml                     # Shared YAML Schema types
├── requirements/
│   ├── 8601-1/                       # ISO 8601-1 requirements (9 files)
│   └── 8601-2/                       # ISO 8601-2 requirements (13 files)
├── tests/
│   ├── 8601-1/                       # ISO 8601-1 conformance tests (9 files)
│   └── 8601-2/                       # ISO 8601-2 conformance tests (13 files)
├── profiles/                         # Profile definitions (8 + TEMPLATE)
├── results/                          # Conformance test results per implementation
│   ├── TEMPLATE.yaml                 # Template for new result files
│   └── ruby-date.yaml                # Ruby Date/DateTime/Time results
├── site/                             # Vue 3 dashboard (Vite + Tailwind)
│   ├── src/                          # Vue components, views, composables
│   ├── public/                       # Static assets (logos, JSON data)
│   └── dist/                         # Production build output

Requirements classes

ISO 8601-1:2026 (Part 1)

Requirements class Source Dependencies

req-class:fundamentals

Clauses 4, 5.1

(core)

req-class:calendar-date

Clause 5.2.2

fundamentals

req-class:ordinal-date

Clause 5.2.3

fundamentals

req-class:week-date

Clause 5.2.4

fundamentals

req-class:time-of-day

Clause 5.3

fundamentals

req-class:date-and-time

Clause 5.4

calendar-date, time-of-day

req-class:duration

Clause 5.5.2

fundamentals

req-class:time-interval

Clause 5.5

date-and-time, duration

req-class:recurring-time-interval

Clause 5.6

time-interval

ISO 8601-2:2026 (Part 2)

Requirements class Source Dependencies

req-class:explicit-representation

Clause 7

8601-1:fundamentals

req-class:negative-values

Clause 4.4.1

explicit-representation

req-class:exponential-values

Clause 4.4.2

explicit-representation

req-class:significant-digits

Clause 4.4.3

explicit-representation

req-class:qualification

Clause 8

8601-1:date-and-time

req-class:unspecified-digits

Clause 9

8601-1:calendar-date

req-class:seasons

Clause 4.8

8601-1:fundamentals

req-class:grouped-time-scale-units

Clause 5

8601-1:fundamentals

req-class:set-representation

Clause 6

8601-1:date-and-time

req-class:extended-time-intervals

Clause 10

8601-1:time-interval

req-class:date-time-selection

Clause 12

8601-1:date-and-time

req-class:recurring-time-intervals-with-rules

Clause 13

8601-1:recurring-time-interval

req-class:arithmetic

Clause 14

8601-1:date-and-time, 8601-1:duration

Profiles

Profiles use a traceability field that explicitly selects specific requirements from conformance classes. This replaces the legacy conformance_classes field (which referenced entire classes wholesale).

Profile Description Add’l tests

profile:iso-8601-1-complete

All ISO 8601-1:2026 requirements (9 classes)

profile:iso-8601-2-complete

All ISO 8601-1 + ISO 8601-2 requirements (22 classes)

profile:iso-8601-1-basic-format

Basic format representations only (no delimiters)

profile:edtf-level-0

Extended Date/Time Format Level 0

10

profile:edtf-level-1

EDTF Level 1 (uncertain, approximate, unspecified)

19

profile:edtf-level-2

EDTF Level 2 (structured date/time expressions)

24

profile:rfc-3339

RFC 3339 (Internet date/time)

9

profile:w3c-datetime

W3C Date and Time Formats (NOTE-datetime)

13

Statistics

  • 99 requirements in ISO 8601-1:2026 across 9 requirements classes

  • 158 requirements in ISO 8601-2:2026 across 13 requirements classes

  • 51 additional requirements across 8 profiles

  • 308 total requirements

  • 275 conformance tests for ISO 8601-1

  • 363 conformance tests for ISO 8601-2

  • 95 additional tests across 8 profiles

  • 733 total tests

How to use

Running conformance tests with the Ruby test runner

The scripts/run-tests script executes test definitions from the YAML files against a pluggable adapter. The default adapter uses Ruby’s standard library Date, DateTime, and Time classes. No gem dependencies are required.

ruby scripts/run-tests                            # run all 733 tests (default: ruby-date)
ruby scripts/run-tests --adapter ruby-date        # explicit adapter selection
ruby scripts/run-tests --profile rfc-3339         # run RFC 3339 profile
ruby scripts/run-tests --profile w3c-datetime     # run W3C Datetime profile
ruby scripts/run-tests --profile edtf-level-0     # run EDTF Level 0 profile
ruby scripts/run-tests --class calendar-date      # run a single conformance class (32 tests)
ruby scripts/run-tests --class time-of-day        # run time-of-day tests
ruby scripts/run-tests --list                     # list available adapters, profiles, and classes
ruby scripts/run-tests --verbose                  # show test descriptions
ruby scripts/run-tests --quiet                    # only show summary
ruby scripts/run-tests --output results/new.yaml  # write results to YAML file

A profile runs all tests from its traceability requirements plus the profile-specific additional tests. For example, --profile rfc-3339 runs tests from the calendar-date, time-of-day, and date-and-time conformance classes (filtered by RFC 3339’s traceability requirements) plus 15 RFC 3339-specific additional tests.

Writing a new adapter

Adapters wrap a specific date/time implementation so the test harness can execute conformance tests against it. There are two adapter shapes:

Shape When to use

Ruby class (adapters/{name}.rb)

The implementation is Ruby or has a Ruby binding. The harness loads it in-process — fastest, no JSON marshalling overhead.

External process (exec:…​)

Anything else: JavaScript, Python, Rust, C, C++, Java, a binary you built by hand. The harness spawns a long-lived child process and talks to it over newline-delimited JSON.

Every adapter implements the same logical interface, regardless of shape:

try_parse(expression, options)

Parse an expression; return {valid:, parsed:, api:} or {valid: false, error:, api:}. options may include parse_mode ("dedicated" or "undifferentiated").

extract_components(parsed)

Extract components (year, month, day, etc.) from a parsed object.

generate(components)

Build an expression from components; return {expression:} or nil.

equivalent?(obj_a, obj_b)

Check if two parsed objects represent the same instant.

run_arithmetic(test)

Optional: execute an arithmetic test (default: not-supported).

declared_conformance_classes

Optional: which conformance classes the implementation claims. Tests outside this list are recorded as not-supported rather than fail.

declared_profiles

Optional: which profiles the implementation explicitly targets. When set, the dashboard uses this to scope per-library statistics (overrides inference from declared_conformance_classes).

Ruby adapter
  1. Copy adapters/TEMPLATE.rb to adapters/{name}.rb

  2. Rename the class to {CamelCaseName}Adapter (e.g. ruby-date.rbRubyDateAdapter)

  3. Implement the required methods and set declared_conformance_classes / declared_profiles

  4. Run: ruby scripts/run-tests --adapter {name}

The class naming convention converts the filename by capitalizing each hyphen-separated segment and appending Adapter (e.g. my-libMyLibAdapter).

Exec adapter (any language)

The exec: adapter type lets you test any implementation in any language — JavaScript, Python, Rust, an XML parser, a web API — without writing Ruby code. It communicates with a child process via a newline-delimited JSON protocol.

ruby scripts/run-tests --adapter "exec:python3 adapters/python/datetime.py"
ruby scripts/run-tests --adapter "exec:node adapters/node/datetime.js"
ruby scripts/run-tests --adapter exec:./adapters/rust/chrono-latest/target/release/rust-chrono
JSON protocol

The harness starts the adapter process once and keeps it running for all tests. For each operation, it writes one JSON command to stdin and reads one JSON response from stdout — each on a single line.

Request:

{"method":"<name>","params":{...}}

Response:

{"result":<value>}

Error response:

{"error":"<message>"}

Protocol methods:

Method Description

info

Return {"name":"…​","language":"…​","version":"…​"}. Called once at startup.

try_parse

Params: {"expression":"…​","options":{"parse_mode":"dedicated"}}.
Return {"valid":true,"parsed":"<handle>","api":"Class.method"}
or {"valid":false,"error":"…​","api":"Class.method"}.
options.parse_mode is "dedicated" (format-specific) or "undifferentiated" (general parser).

extract_components

Params: {"parsed":"<handle>"}.
Return component hash, e.g. {"calendar":{"year":1985,"month":4,"day":12}}.

generate

Params: {"components":{…​}}.
Return {"expression":"1985-04-12"} or null.

equivalent

Params: {"parsed_a":"<h>","parsed_b":"<h>"}.
Return true, false, or null (cannot determine).

run_arithmetic

Params: {"test":{…​}}.
Return {"result":"not-supported","notes":"…​"} if unsupported.

declared_conformance_classes

No params.
Return ["conf-class:fundamentals", …​] — the conformance classes this implementation claims. Tests outside this list are recorded as not-supported rather than fail.

declared_profiles

No params.
Return ["profile:iso-8601-1-core", …​] — the profiles this implementation explicitly targets. When non-empty, the dashboard uses this to scope per-library statistics; otherwise it infers target profiles from declared_conformance_classes.

The parsed value is an opaque handle (any JSON value) managed by the adapter process. When try_parse succeeds, the adapter caches the internal parsed object and returns a handle. The harness passes this handle to extract_components and equivalent. The adapter looks up the cached object by handle.

See adapters/python/datetime.py for a working example (Python standard library). See adapters/node/datetime.js for a working example (JavaScript Date). See adapters/TEMPLATE.rb for the full adapter interface specification.

Bundled adapters

The repository ships adapters for 24 standard-library implementations across 7 language families. Each adapter’s declared_conformance_classes and declared_profiles reflect what the implementation actually claims to implement — by convention, every adapter currently declares only the 4 ISO 8601-1 Core classes and targets profile:iso-8601-1-core.

Family Adapter ID Implementation / invocation

Ruby Date

ruby-date

Ruby Date/DateTime/Time via in-process adapter (uses whatever Ruby is on PATH)

Ruby Date

ruby-30 / ruby-31 / ruby-32 / ruby-33 / ruby-date-40

Same Ruby adapter run under specific Ruby versions (via mise) — uses adapters/ruby/date-exec.rb as a long-lived child process

Python datetime

python-datetime

Python datetime via exec:python3 adapters/python/datetime.py

Python datetime

python-38 / python-39 / python-312 / python-313

Same Python adapter run under specific Python versions (via mise)

Node.js Date

node-datetime

JavaScript Date.parse via exec:node adapters/node/datetime.js

Node.js Date

node-18 / node-20 / node-22

Same Node adapter under specific Node versions (via mise)

C stdio

c-stdio

BSD/macOS strptime/strftime via exec:gcc -o /tmp/c-stdio-adapter adapters/c/stdio.c && /tmp/c-stdio-adapter

C stdio

c-stdio-glibc

glibc strptime/strftime via Docker gcc:15

C++ chrono

cpp-chrono

C20 `std::chrono` compiled with Homebrew LLVM (`/opt/homebrew/opt/llvm/bin/clang`)

C++ chrono

cpp-chrono-apple

Same source compiled with Apple Clang (/usr/bin/clang++)

Rust chrono

rust-chrono

Rust chrono 0.4 (latest) — pre-built binary at adapters/rust/chrono-latest/target/release/rust-chrono

Rust chrono

rust-chrono-0419

Rust chrono =0.4.19 — pre-built binary at adapters/rust/chrono-0419/target/release/rust-chrono-0419

Java java.time

java-time

Java 21 java.time via exec:javac + java

Java java.time

java-8 / java-15

Same source compiled under Java 8 / Java 15 (via AdoptOpenJDK)

Building the compiled adapters

The Ruby, Python, Node, and C adapters are compiled (or interpreted) on each invocation — no separate build step. The Rust and Java adapters require a one-time build:

# Rust (both versions share adapters/rust/chrono-latest/src/main.rs)
cargo build --release --manifest-path adapters/rust/chrono-latest/Cargo.toml
cargo build --release --manifest-path adapters/rust/chrono-0419/Cargo.toml

Java is recompiled on each invocation via the exec: command in lib/test_suite/capability_matrix.rb, so no separate build step is needed.

Regenerating all result files

scripts/regenerate-all-results runs the full test suite against every adapter defined in CapabilityMatrix::ADAPTER_DEFS and writes one results/{id}.yaml per adapter:

ruby scripts/regenerate-all-results                              # all 24
ruby scripts/regenerate-all-results --only rust-chrono,java-8    # subset
ruby scripts/regenerate-all-results --skip c-stdio-glibc         # skip slow Docker build

After regenerating result YAMLs, refresh the dashboard JSON:

ruby scripts/capability-matrix    # writes site/public/summary.json + detail.json

The Rust adapter binaries are not rebuilt automatically — if you edit adapters/rust/chrono-latest/src/main.rs, rebuild both Cargo projects before running regenerate-all-results (see above).

Creating a new profile

Profiles define subsets of ISO 8601 for specific use cases. To create one:

  1. Copy profiles/TEMPLATE.yaml to profiles/{name}.yaml

  2. Set id: profile:{name}, name, and description

  3. Define traceability — list the specific requirements selected from each conformance class:

    traceability:
      - conformance_class: conf-class:calendar-date
        requirements:
          - req:cal-date-parse-001
          - req:cal-date-gen-001
  4. Optionally add additional_requirements and additional_tests for profile-specific rules

  5. Validate: ruby scripts/validate

  6. Test against an adapter: ruby scripts/run-tests --profile {name}

Available requirement IDs are defined in requirements/8601-1/.yaml and requirements/8601-2/.yaml. Each requirements class file lists its requirements with their req: identifiers.

Running conformance tests manually

The test suite is format-agnostic — the YAML test definitions can be consumed by any implementation in any language. The general procedure is:

  1. An implementation declares which requirements classes it supports.

  2. For each supported requirements class, the implementation runs all conformance tests in the corresponding conformance class.

  3. An implementation passes a conformance class if all tests pass.

  4. An implementation passes a profile if all referenced conformance classes pass (including additional profile-specific tests).

Recording conformance results

Results are recorded in YAML files under results/, one file per implementation, following the schema in schema/conformance-result.yaml.

Each result file contains:

  • Implementation metadata — name, language, version, modules/classes used

  • Verification modeautomated (test script), manual (human), or inferred (known behavior without execution)

  • Per-class results — aggregate status (passed, failed, partial, not-supported) with optional default method (e.g. Date.parse(expr))

  • Per-test results — individual pass/fail/error/not-supported status with documented API calls, actual output, and exception details

  • Profile results — results for profile-specific additional tests (e.g. RFC 3339, EDTF Level 0/1/2, W3C Datetime)

Example result entry:

- test: conf-test:cal-date-parse-001
  result: pass
  method:
    code: 'Date.parse("19850412")'
    language: ruby
    api: Date.parse
  actual:
    valid: true
    returned: "1985-04-12"

To add a new implementation result:

  1. Copy results/TEMPLATE.yaml to results/{implementation}.yaml

  2. Set id: result:{name} and fill in implementation metadata

  3. For each conformance class, record aggregate status and individual test results

  4. Use verification: automated if a script ran the tests, manual if a human tested and recorded results, or inferred if results are based on known behavior

Validating the test suite

The scripts/validate script performs 15 phases of validation:

  1. YAML syntax — all files parse correctly

  2. Schema references — every data file has a $schema header

  3. Schema validation — each file is validated against its declared YAML Schema (type checking, required fields, pattern matching, enum values, oneOf discriminated unions)

  4. Requirements indexing — unique IDs, correct structure

  5. Conformance test indexing — unique IDs, valid requirement references

  6. Requirements coverage — every requirement is referenced by at least one test

  7. Profile references — all conformance class references resolve

  8. Result cross-references — all test IDs in result files match test definitions

  9. Dependency graph — no cycles in requirements class dependencies

  10. Source consistency — source clause references are consistent across related files

  11. URN format — clause references follow RFC 5141 URN format

  12. Statistics — summary counts (requirements, tests, files)

  13. Pattern coverage — pattern fields are present where expected

  14. Component validity — component keys match the vocabulary in schema/components.yaml

  15. Test ID naming — test IDs follow the conf-test:{prefix}-{type}-{NNN} convention

ruby scripts/validate              # run all checks (colored output)
ruby scripts/validate -q           # quiet — only show errors
ruby scripts/validate -v           # verbose — show every file processed

This is also run automatically via GitHub Actions on every push that modifies YAML files.

Each YAML file includes a $schema reference for validation in editors that support YAML Schema (e.g. VS Code with the YAML extension).

Dashboard site

The site/ directory contains a Vue 3 dashboard (Vite + Tailwind CSS) that visualizes conformance results across implementations and profiles.

Prerequisites

  • Node.js 18+ and npm

Generate data

The site loads two JSON files produced by the capability-matrix script:

  • site/public/summary.json — libraries, profiles, categories, requirements with pass/fail summaries (~24 KB gzipped)

  • site/public/detail.json — full test details and profile traceability (~164 KB gzipped)

ruby scripts/capability-matrix                    # regenerate both files
ruby scripts/capability-matrix -o site/public     # explicit output directory

Run development server

cd site
npm install
npm run dev            # starts at http://localhost:3000

Build for production

cd site
npm run build          # outputs to site/dist/
npm run preview        # preview the production build locally

The build includes pre-compressed .gz files for all assets. The data loads progressively: the summary renders the dashboard and navigation instantly, and full test details load on demand when viewing the matrix or detail pages.

Conformance

A conformant implementation for a particular feature shall pass all conformance tests within the corresponding conformance class.

This is ongoing work by CalConnect TC DATETIME in collaboration with ISO/TC 154/WG 5.

Copyright (c) 2025–2026 Ribose Inc.

Content (examples and syntax from ISO 8601) copyrighted to its respective owners.

Developed as cooperative work with CalConnect TC DATETIME and ISO/TC 154/WG 5.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages