Skip to main content
Pre-release

This release is a pre-release and may not be stable for production use.

sqlite-dateutil

A SQLite extension, written in C, that ports python-dateutil's dateutil.parser.parse() to a set of SQL functions.

select dateutil_parse('Thu, 25 Sep 2003 10:49:41 -0300');
-- 2003-09-25T10:49:41-03:00

select dateutil_parse_fuzzy('Today is January 1, 2047 at 8:21:00AM');
-- 2047-01-01T08:21:00

select dateutil_parse('10/09/2003'), dateutil_parse_dayfirst('10/09/2003');
-- 2003-10-09T00:00:00 | 2003-09-10T00:00:00

select dateutil_parse('not a date');
-- NULL
SQL function Python equivalent
dateutil_parse(text) parse(text).isoformat()
dateutil_parse_fuzzy(text) parse(text, fuzzy=True).isoformat()
dateutil_parse_dayfirst(text) parse(text, dayfirst=True).isoformat()
dateutil_parse_fuzzy_dayfirst(text) parse(text, fuzzy=True, dayfirst=True).isoformat()

The function names match the datasette-dateutil plugin. Where dateutil raises an exception, these functions return NULL. Missing fields come from today's local date at midnight, as in dateutil (so dateutil_parse('10:30') is today at 10:30). That also means the functions aren't marked deterministic.

Installation

The extension is packaged for Python, with prebuilt wheels for macOS (Apple Silicon and Intel), Linux (x86_64 and aarch64) and Windows (x64):

pip install sqlite-dateutil
import sqlite3
import sqlite_dateutil

db = sqlite3.connect(":memory:")
sqlite_dateutil.load(db)
print(db.execute("select dateutil_parse('Sep 25 2003 5pm')").fetchone()[0])
# 2003-09-25T17:00:00

load() turns extension loading on just long enough to load the extension, then turns it off again. sqlite_dateutil.loadable_path() gives the path to the bundled library for use with other tools.

Pyodide

There are also wheels for Pyodide:

Pyodide Wheel tag
314.x pyemscripten_2026_0_wasm32
0.28.x, 0.29.x pyodide_2025_0_wasm32

Pyodide's sqlite3 module can't load extensions, so in Pyodide import sqlite_dateutil registers the functions with SQLite's sqlite3_auto_extension() instead. Every connection opened after the import has them, and load(conn) just checks that:

import micropip
await micropip.install("sqlite-dateutil")
# Pyodide 0.28/0.29 only: sqlite3 is a separate package there
# await micropip.install("sqlite3")

import sqlite3
import sqlite_dateutil  # import before connecting

db = sqlite3.connect(":memory:")
print(db.execute("select dateutil_parse('Sep 25 2003 5pm')").fetchone()[0])

All of these Pyodide releases link in SQLite 3.39.0, older than the headers in vendor/sqlite, but the extension only uses APIs that SQLite had long before that version. Pyodide 0.28/0.29 build SQLite without load-extension support, so SQLite hands extensions no table of its API functions. tools/pyodide_shim.c gets those function addresses from Python instead.

There are also plugins that add the functions to every connection:

datasette install datasette-sqlite-dateutil
sqlite-utils install sqlite-utils-sqlite-dateutil

Building

uv run python tools/build.py    # or just: make

This builds dateutil_parse.dylib on macOS, .so on Linux, or .dll on Windows. The build script uses only the Python standard library and picks the compiler from $CC, or falls back to cc. On Windows it uses MSVC cl from a Native Tools prompt, or MinGW gcc / clang. It compiles against the SQLite headers in vendor/sqlite/, never the system headers, because some system copies (notably the macOS SDK's sqlite3ext.h) turn off the loadable-extension API.

sqlite> .load ./dateutil_parse
sqlite> select dateutil_parse('Sep 25 2003 5pm');
2003-09-25T17:00:00
import sqlite3
db = sqlite3.connect(":memory:")
db.enable_load_extension(True)
db.load_extension("./dateutil_parse")

The extension is dateutil_parse.c plus the generated dateutil_unicode.h. It has no dependencies beyond the SQLite headers. The entry point is sqlite3_dateutilparse_init.

Tests

uv run pytest

That is all you need: uv creates the environment, and the test suite compiles the extension first whenever it is missing or older than its sources. The project is set to use only uv-managed Pythons, because some system Pythons, such as Apple's /usr/bin/python3, are built without enable_load_extension(). The sanitizer tests are skipped when ASan/UBSan isn't available (for example with MSVC).

The test suite (tests/) is differential: for every input it checks that the SQL functions return exactly what dateutil.parser.parse(...).isoformat() returns, or NULL where dateutil raises. It covers:

  • 190 hand-picked inputs (ISO 8601, RFC 2822, Unix date, numeric ambiguity, am/pm, 10h36m, GMT+3, -0300 (BRST), fuzzy sentences, NUL bytes, Unicode whitespace and punctuation, and many inputs that should fail), across all four functions.
  • 40,000 generated inputs (token soup plus datetime-shaped strings with out-of-range parts).
  • The parser core built with AddressSanitizer + UBSan (tools/du_cli.c). This runs the same corpus against fixed default dates (Jan 31, Feb 29, 9999-12-28, ...) to test month-end clamping and weekday arithmetic, plus 20,000 random byte strings with invalid UTF-8.
  • Property-based tests with Hypothesis (tests/test_properties.py): arbitrary Unicode, token soup, real datetimes rendered in about 20 strftime formats, and fuzzy sentences must all match dateutil. Any result must be valid ISO 8601 that parses back to itself, and arbitrary BLOBs must never raise an error. Failures shrink to a minimal input. Run them for longer with HYPOTHESIS_PROFILE=thorough uv run pytest tests/test_properties.py (20,000 examples per property).
  • Limits on input size and running time, behavior in SQL queries, and the known differences below.

The parser core is also fuzzed with libFuzzer + ASan + UBSan on Linux, for 3 minutes on every push, and for 30 minutes daily if main has changed in the last 24 hours. See fuzz/README.md.

During development, a further 1.35 million generated comparisons found zero mismatches with dateutil 2.9.0.

Safety and performance

  • Bounded memory, no overflow. Every token lookahead is bounds-checked. Integer parsing is strict and saturates at 10^15, so arithmetic can't overflow. Output goes into a fixed buffer through snprintf. There is no recursion.
  • Linear time. One pass tokenizes and one pass parses. Inputs up to 256 bytes need no heap allocation. Longer inputs use one allocation, sized to the input.
  • Size cap. Inputs over 64 KiB return NULL (compile with -DDATEUTIL_MAX_INPUT=... to change this).
  • Arbitrary bytes are safe. Embedded NULs are skipped, as dateutil does. Invalid UTF-8 is handled.
  • Exact numbers. dateutil uses Decimal to avoid float rounding (its GH #427). This port does the same arithmetic on the decimal digits, so 1.7h is 1:42, not 1:41.
  • Registered as SQLITE_INNOCUOUS.

One machine, 100,000 mixed inputs with fuzzy=True: ~1.1 million rows/second, about 38x faster than calling dateutil from Python.

Known differences from dateutil

These are deliberate:

  1. No tzinfos, and no local time zone names. As in dateutil without tzinfos, a zone name it can't resolve (e.g. EST) gives a naive result. dateutil also maps names in time.tzname (the server's own zone) to tzlocal(). This port doesn't, so results never depend on the server's TZ setting. UTC, GMT, Z and numeric offsets work as in dateutil.
  2. Only ASCII digits form numbers. Characters are classified exactly as Python's isalpha(), isdigit() and isspace() do (table generated by tools/gen_unicode.py). Python's float()/int() also accept other Unicode decimal digits, such as full-width 2003. Those inputs return NULL here.
  3. Numeric extremes. dateutil's Decimal arithmetic uses 28 significant digits. Fractions longer than that may round differently. Absurdly long numbers make dateutil raise decimal.InvalidOperation; here they return NULL.
  4. Inputs over 64 KiB return NULL.

Layout

dateutil_parse.c        the extension (port of dateutil/parser/_parser.py)
dateutil_unicode.h      generated character-class table
tools/gen_unicode.py    regenerates the table from Python's Unicode data
tools/du_cli.c          SQLite-free driver used for sanitizer tests
tools/build.py          cross-platform build script (used by the tests)
tools/build_wheels.py   packages the built extension as wheels, plus the plugin wheels
fuzz/                   libFuzzer harness, dictionary and seed generator
vendor/sqlite/          SQLite 3.53.2 public-domain headers
tests/                  pytest differential test suite, plus wheel packaging tests
tests/pyodide/          installs each Pyodide wheel with micropip under Node and checks it
tools/pyodide_shim.c    Pyodide entry point that copes with SQLite built without extension support

Releasing

Bump version in pyproject.toml, then publish a GitHub release with that version as its tag. .github/workflows/release.yml runs the tests, builds a wheel on each platform (in the manylinux_2_28 images on Linux), installs and tests each one, then uploads them and the two plugin wheels to PyPI through trusted publishing.

To build wheels locally:

uv run python tools/build_wheels.py            # this platform, into dist/
uv run python tools/build_wheels.py --plugins  # datasette- and sqlite-utils- plugins

On macOS the wheel targets macOS 11 (arm64) or 10.13 (x86_64), unless MACOSX_DEPLOYMENT_TARGET says otherwise. The script refuses to package a binary built for a newer macOS than its wheel tag claims.

Each Pyodide wheel needs the exact Emscripten version its Pyodide ABI uses (see PYODIDE_TARGETS in tools/build_wheels.py): 4.0.9 for ABI 2025_0 (Pyodide 0.28/0.29), 5.0.3 for 2026_0 (Pyodide 314). With that Emscripten active:

uv run python tools/build_wheels.py --pyodide 2026_0   # or 2025_0
cd tests/pyodide && npm ci && node test.mjs ../../dist/sqlite_dateutil-*_2026_0_wasm32.whl

emsdk keeps one active version per checkout, so use a separate emsdk directory for each, or point EMCC at the right upstream/emscripten/emcc.

tests/pyodide/test.mjs picks the Pyodide release from the wheel's tag, installs the wheel with micropip under Node, and checks every hand-picked case against python-dateutil running in that Pyodide. On 0.28/0.29 it also checks that importing without sqlite3 explains what to do. CI runs it for every ABI on every push. Adding a Pyodide ABI means a new PYODIDE_TARGETS entry, a matching entry in the test.yml Pyodide matrix (with its emsdk commit), a pyodide-<ABI> alias in tests/pyodide/package.json, and the wheel count and publish step in release.yml.

Metadata

Release files for sqlite-dateutil 0.1a0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Built distributions (wheels)

Table of built distributions (wheels) for sqlite-dateutil 0.1a0
File Actions
sqlite_dateutil-0.1a0-py3-none-win_amd64.whl Python 3 none Windows x86-64 Details for sqlite_dateutil-0.1a0-py3-none-win_amd64.whl
sqlite_dateutil-0.1a0-py3-none-pyemscripten_2026_0_wasm32.whl Python 3 none PyEmscripten 2026.0+ WebAssembly Details for sqlite_dateutil-0.1a0-py3-none-pyemscripten_2026_0_wasm32.whl
sqlite_dateutil-0.1a0-py3-none-pyemscripten_2025_0_wasm32.whl Python 3 none PyEmscripten 2025.0+ WebAssembly Details for sqlite_dateutil-0.1a0-py3-none-pyemscripten_2025_0_wasm32.whl
sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_x86_64.whl Python 3 none Linux glibc 2.28+ x86-64 Details for sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_x86_64.whl
sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_aarch64.whl Python 3 none Linux glibc 2.28+ ARM64 Details for sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_aarch64.whl
sqlite_dateutil-0.1a0-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details for sqlite_dateutil-0.1a0-py3-none-macosx_11_0_arm64.whl
sqlite_dateutil-0.1a0-py3-none-macosx_10_13_x86_64.whl Python 3 none macOS 10.13+ x86-64 Details for sqlite_dateutil-0.1a0-py3-none-macosx_10_13_x86_64.whl

Total release size: 233.3 kB

Release files / sqlite_dateutil-0.1a0-py3-none-win_amd64.whl

Download URL sqlite_dateutil-0.1a0-py3-none-win_amd64.whl
Size 97.8 kB
Tags Python 3 Windows x86-64
SHA-256 checksum
How to use checksums
8d0226267aa688042675b1cc241cd899c2ebd67f9064539e68e3b3bde1bd1e72
BLAKE2b-256 checksum
How to use checksums
2091f6b482b76002457d1f787c30f50c83aabe97129bf92ae2d03e316d5499cf
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / sqlite_dateutil-0.1a0-py3-none-pyemscripten_2026_0_wasm32.whl

Download URL sqlite_dateutil-0.1a0-py3-none-pyemscripten_2026_0_wasm32.whl
Size 19.2 kB
Tags PyEmscripten 2026.0+ WebAssembly Python 3
SHA-256 checksum
How to use checksums
d5df3d8a8b1ba8610805f7dc1d3c3fc0d42f8bb607edeb40c1faf31e1563cf97
BLAKE2b-256 checksum
How to use checksums
393ffcc06025f20e9f4e04304bd73dbaf967ebdb17548bb24455d56a4affe003
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / sqlite_dateutil-0.1a0-py3-none-pyemscripten_2025_0_wasm32.whl

Download URL sqlite_dateutil-0.1a0-py3-none-pyemscripten_2025_0_wasm32.whl
Size 19.4 kB
Tags PyEmscripten 2025.0+ WebAssembly Python 3
SHA-256 checksum
How to use checksums
53d2a903742baffb76fa5fd45dc054ca3e4aab36c366ef3a83b14758add8b3ec
BLAKE2b-256 checksum
How to use checksums
7b6b08d2309e127f4a8442f1ddfd13a4c74fe55f995fa1dcc0febe6d08b6756c
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.13.0 {"installer":{"name":"uv","version":"0.13.0","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

Release files / sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_x86_64.whl

Download URL sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_x86_64.whl
Size 24.0 kB
Tags Linux glibc 2.28+ x86-64 Python 3
SHA-256 checksum
How to use checksums
f00093ce60d7661af2db2957f87b46854fe23784e5f90aa025c6ac566dda7562
BLAKE2b-256 checksum
How to use checksums
52b782012236a1473f5df3c3ccd584293ec799573fd4619276a84763c9f24e01
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_aarch64.whl

Download URL sqlite_dateutil-0.1a0-py3-none-manylinux_2_28_aarch64.whl
Size 24.3 kB
Tags Linux glibc 2.28+ ARM64 Python 3
SHA-256 checksum
How to use checksums
e0f02a331ab53a3c9cb75e2cf6c4e7ce175bed6c2fa71db419ca4a41e9ec26d3
BLAKE2b-256 checksum
How to use checksums
fd9c5d48e4c3d74b9f3f320ede93104e1c79b044a2a566253806c474bb527cb5
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / sqlite_dateutil-0.1a0-py3-none-macosx_11_0_arm64.whl

Download URL sqlite_dateutil-0.1a0-py3-none-macosx_11_0_arm64.whl
Size 24.2 kB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
61bbe007c421ea8a6abf4516e9c222cfe22ab9e340c9769e98b45bd1a3644eb5
BLAKE2b-256 checksum
How to use checksums
0d74a53fd0d19c3ef2804fb58744be85b9e90c89eeed693d362a88940f75e608
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / sqlite_dateutil-0.1a0-py3-none-macosx_10_13_x86_64.whl

Download URL sqlite_dateutil-0.1a0-py3-none-macosx_10_13_x86_64.whl
Size 24.6 kB
Tags Python 3 macOS 10.13+ x86-64
SHA-256 checksum
How to use checksums
9e902ed7730ff7a43701d3053b25e9ed73163fa240eb9fe9e6d16ef9d54f1316
BLAKE2b-256 checksum
How to use checksums
deeae6168fc37118fb67a46ba4b2d2302c9f7e3d70df201c077a5181fa97c6df
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for sqlite-dateutil 0.1a0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release
Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page