Functional Weave
Code in Python

time.iso-to-unix

An RFC 3339 timestamp with a Z or offset, such as 2026-09-26T13:00:00+01:00, to Unix time in whole seconds.

1.0.0 · published 2026-10-03 by charlie · Anterra

Pinned by 20 tests, run in TypeScript, Python and Rust.

What it does

`isoToUnix("2009-02-13T23:31:30Z")` is `1234567890`, and `isoToUnix("2026-09-26T13:00:00+01:00")` is the same instant as `2026-09-26T12:00:00Z`. It reads the timestamps `time.unix-to-iso` writes, and the ones other systems send, so that times can be compared as integers.

The accepted form is RFC 3339's `date-time`: `YYYY-MM-DD`, `T`, `HH:MM:SS`, an optional fraction (`.5`, `.123456`), and a zone of `Z` or `+HH:MM` / `-HH:MM`. RFC 3339 lets `T` and `Z` be lowercase, so they may be. A time with no zone is refused, because the same wall-clock time is a different instant in every zone and guessing one is how an hour goes missing twice a year.

For example

  • iso_to_unix(1970-01-01T00:00:00Z) → 0 the epoch
  • iso_to_unix(2009-02-13T23:31:30Z) → 1,234,567,890 the famous 1234567890
  • iso_to_unix(1969-12-31T23:59:59Z) → -1 one second before the epoch

The function

The same function in TypeScript, Python and Rust, pinned by the same tests. Pick your language; the choice follows you around the registry.

def iso_to_unix(timestamp: str) -> int
timestampstringYYYY-MM-DDTHH:MM:SS, an optional fraction, then Z or +HH:MM / -HH:MM
returnsintseconds since 1970-01-01T00:00:00Z; a fraction of a second is dropped

Your code names it in one line, in the file that uses it

from fune.time.iso_to_unix import iso_to_unix  # time.iso-to-unix@^1
impl/python.py · 59 lines · open · raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from .dates_add_days import epoch_day_from_iso  ← from dates.add-days ^1.0.0 · built alongside by fune


def _is_digit(ch: str) -> bool:
    # A character range, not str.isdigit(), which accepts other scripts' digits.
    return "0" <= ch <= "9"


def iso_to_unix(timestamp: str) -> int:
    """An RFC 3339 date-time to Unix seconds, parsed by position.

    datetime.fromisoformat accepts more shapes than this (and fewer before
    3.11), so the rules are written out to match the other languages.
    """
    if not isinstance(timestamp, str):
        raise ValueError('"%s" is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z' % (timestamp,))
    t = timestamp

    def bad() -> ValueError:
        return ValueError('"%s" is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z' % (t,))

    if len(t) < 20:
        raise bad()
    for i in (0, 1, 2, 3, 5, 6, 8, 9, 11, 12, 14, 15, 17, 18):
        if not _is_digit(t[i]):
            raise bad()
    if t[4] != "-" or t[7] != "-" or t[10] not in ("T", "t") or t[13] != ":" or t[16] != ":":
        raise bad()
    i = 19
    if t[i] == ".":
        i += 1
        start = i
        while i < len(t) and _is_digit(t[i]):
            i += 1
        if i == start:
            raise bad()
    if i < len(t) and t[i] in ("Z", "z") and i + 1 == len(t):
        offset = 0
    elif (
        i < len(t)
        and t[i] in ("+", "-")
        and i + 6 == len(t)
        and _is_digit(t[i + 1])
        and _is_digit(t[i + 2])
        and t[i + 3] == ":"
        and _is_digit(t[i + 4])
        and _is_digit(t[i + 5])
    ):
        oh = int(t[i + 1 : i + 3])
        om = int(t[i + 4 : i + 6])
        if oh > 23 or om > 59:
            raise bad()
        offset = (1 if t[i] == "+" else -1) * (oh * 3600 + om * 60)
    else:
        raise bad()
    h, m, s = int(t[11:13]), int(t[14:16]), int(t[17:19])
    if h > 23 or m > 59 or s > 59:
        raise bad()
    return epoch_day_from_iso(t[0:10]) * 86400 + h * 3600 + m * 60 + s - offset

Install

fune build

With that line in your source, in a Python project (language python in fune.project), fune build resolves it and its 1 dependency, pins them in fune.lock, downloads only the Python package of each, and builds the code above into your project’s .fune/build, one readable file per capability with a header linking back here. Or pin a range in fune.project and build in one step:

fune add time.iso-to-unix
Download for Python time.iso-to-unix-1.0.0-python.fune · 7,596 bytes sha256 3994435095d5ef06d7dfb639027e2fd0956f6d90cc7b0b5385602c7f37311505

The manifest, vectors and README with only the Python implementation. Install it without the registry with fune add ./time.iso-to-unix-1.0.0-python.fune, or fetch it from a terminal with fune pull time.iso-to-unix@1.0.0:python.

The whole function, every language, is one file too: time.iso-to-unix-1.0.0.fune, 11,961 bytes, sha256 2d3f5e9d5cc2f5bf95e84e0be406e3b7a459602bbc4581e126bdd823dc7f1cf4. It installs into a project of any language.

Customise it in your app

The seams this capability offers. Put a marker directly above a function of your own and fune build wires it into the built code; the package on the registry is not changed, the built file’s header lists it under CUSTOMISED, and fune hooks lists every hook in the project. How hooks work.

before — your function gets the arguments and returns them, changed or not, or throws to refuse the call.

# fune: before time.iso-to-unix

after — your function gets the result and the arguments, and returns the final result.

# fune: after time.iso-to-unix

replace — inside this capability’s code only, calls to a dependency go to your function, with the same signature. Other capabilities that use it are unaffected; write in * to replace it everywhere.

# fune: replace dates.add-days in time.iso-to-unix

step — your function runs at a numbered point inside the function’s body, receives the in-scope values it names as parameters, and may return replacements. List the points with fune show time.iso-to-unix --steps.

# fune: step time.iso-to-unix after <n|label>

Tests

A version published now needs at least 8 tests for every function, and one that expects the error for each function that throws; the registry refuses it otherwise. fune verify --all runs each case in TypeScript, Python and Rust, and a project runs them again with fune verify. This page lists the cases; it does not run them. The exact JSON is vectors.json.

CaseArgumentsExpected
the epoch 1970-01-01T00:00:00Z → 0
the famous 1234567890 2009-02-13T23:31:30Z → 1,234,567,890
one second before the epoch 1969-12-31T23:59:59Z → -1
the last second of a signed 32-bit time_t 2038-01-19T03:14:07Z → 2,147,483,647
British Summer Time: an hour ahead of UTC, so an hour is taken off 2026-09-26T13:00:00+01:00 → 1,790,424,000
a negative half-hour offset is added back 2026-09-26T06:30:00-05:30 → 1,790,424,000
a fraction is dropped, not rounded up to the next second 2026-09-26T12:00:00.999Z → 1,790,424,000
RFC 3339 allows a lowercase t and z 2026-09-26t12:00:00z → 1,790,424,000
-00:00 is UTC, on a leap day 2000-02-29T12:00:00-00:00 → 951,825,600
the first second of year 0001 0001-01-01T00:00:00Z → -62,135,596,800
Show the other 10 tests
CaseArgumentsExpected
a time with no zone is ambiguous 2026-09-26T12:00:00 → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
a space instead of T 2026-09-26 12:00:00Z → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
hour 24 2026-09-26T24:00:00Z → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
a leap second has no Unix time 2026-09-26T23:59:60Z → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
a decimal point with no digits 2026-09-26T12:00:00.Z → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
no seconds 2026-09-26T12:00Z → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
an offset without its colon 2026-09-26T12:00:00+0100 → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
a trailing newline 2026-09-26T12:00:00Z → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
Arabic-Indic digits in the year ٢٠٢٦-09-26T12:00:00Z → error: is not an RFC 3339 timestamp such as 2026-09-26T12:00:00Z
a well-formed timestamp on a day that does not exist 2026-02-30T00:00:00Z → error: is not a real calendar date

More from the author

A fraction is dropped, so `.999` still belongs to the second it started in (Unix time floors). Seconds run 00-59: RFC 3339 allows `:60` for a leap second, but Unix time has no number for it, so it is refused rather than silently moved. Hours 00-23 (`24:00:00` is refused), offsets up to 23:59.

The date itself is checked by `dates.add-days`, so `2026-02-30` is refused with its message (`is not a real calendar date`), and years run 0001 to 9999. No spaces, no week or ordinal dates, no missing seconds; ASCII digits only.

Source: RFC 3339, Date and Time on the Internet: Timestamps, section 5.6 (https://www.rfc-editor.org/rfc/rfc3339#section-5.6).

Files

PathBytes
README.md1,321
impl/python.py1,943
impl/rust.rs2,266
impl/typescript.ts1,903
vectors.json2,573