metadate
Fast natural-language date parsing for Python — with an optional C-accelerated scanner.
metadate parses human-written date expressions into structured datetime ranges.
It handles everything from "tomorrow at 3 pm" to "the last 2 weeks of March 2024" — and does it fast, with an optional C extension that provides up to 50x speedup over pure-Python regex scanning.
Features
- Natural language — understands relative dates (
"next tuesday","3 days ago"), absolute dates ("June 25","2024-01-15"), times ("at 3pm"), ranges, and combinations - Fast C scanner — optional C extension using a FNV-1a hash table for sub-millisecond parsing
- ISO 8601 fast-path — optional
ciso8601integration for near-instant ISO date parsing - Granularity levels — know whether a result has year, month, day, hour, minute, or second precision
- Start & end dates — every result is a period with
start_dateandend_date - Multi-date extraction — pull all dates out of a block of text
- Timezone support — recognizes timezone names and abbreviations
- Locale support — English and Dutch built-in, extensible to other languages
- CLI included — parse dates from the command line
Installation
pip install metadate
For faster ISO 8601 parsing:
pip install metadate[fastciso]
The C extension is compiled automatically when a C compiler is available. If compilation fails (e.g. no compiler installed), metadate falls back to the pure-Python scanner — no functionality is lost, only speed.
Quick start
from metadate import parse_date
# Simple dates
r = parse_date("tomorrow at 3pm")
print(r.start_date) # 2026-03-13 15:00:00
print(r.end_date) # 2026-03-13 16:00:00
# Relative dates
r = parse_date("last 2 weeks")
print(r.start_date) # 2 weeks ago
print(r.end_date) # now
# Absolute dates
r = parse_date("June 25, 2024")
print(r.start_date) # 2024-06-25 00:00:00
print(r.end_date) # 2024-06-26 00:00:00
# Extract multiple dates from text
results = parse_date(
"The meeting moved from March 5 to March 12 at 2pm",
multi=True
)
for r in results:
print(r.start_date, "—", r.end_date)
Using the C scanner
The C scanner is used automatically when available. To explicitly enable or disable it:
# Force C scanner
r = parse_date("next friday", use_c_scanner=True)
# Force pure-Python scanner
r = parse_date("next friday", use_c_scanner=False)
API reference
parse_date(text, **kwargs) -> MetaPeriod | list[MetaPeriod] | None
| Parameter | Type | Default | Description |
|---|---|---|---|
text |
str |
— | The text to parse |
reference_date |
datetime |
now() |
Anchor for relative dates |
future |
relativedelta |
30 years |
How far ahead to search |
past |
relativedelta |
100 years |
How far back to search |
lang |
str |
"en" |
Locale ("en" or "nl") |
multi |
bool |
False |
Return all dates found |
use_c_scanner |
bool |
False |
Use C-accelerated scanner |
min_level |
Units |
None |
Minimum granularity filter |
max_level |
Units |
None |
Maximum granularity filter |
verbose |
bool |
False |
Print debug info |
MetaPeriod
| Property | Type | Description |
|---|---|---|
start_date |
datetime |
Start of the parsed period |
end_date |
datetime |
End of the parsed period |
levels |
set[Units] |
Granularity levels present |
matches |
list[str] |
Matched text fragments |
has_time |
bool |
Whether time was specified |
has_day |
bool |
Whether day was specified |
has_month |
bool |
Whether month was specified |
has_year |
bool |
Whether year was specified |
is_in_past |
bool |
Whether the date is in the past |
tz |
tzinfo|None |
Timezone if detected |
to_dict() |
dict |
Serialize to dictionary |
Units
Granularity levels from coarsest to finest:
YEAR (9) > SEASON (8) > QUARTER (7) > MONTH (6) > WEEK (5) > DAY (4) > HOUR (3) > MINUTE (2) > SECOND (1) > MICROSECOND (0)
Every MetaPeriod carries a levels set that tells you exactly which components were specified in the input. Use min_level and max_level on parse_date to filter results by granularity:
from metadate import parse_date, Units
# "June 2024" has levels {YEAR, MONTH} — no day or time
r = parse_date("June 2024")
print(r.levels) # {<Units.YEAR: 9>, <Units.MONTH: 6>}
print(r.has_month) # True
print(r.has_day) # False
print(r.has_time) # False
# "tomorrow at 3pm" has levels {DAY, HOUR, MINUTE}
r = parse_date("tomorrow at 3pm")
print(r.levels) # {<Units.DAY: 4>, <Units.HOUR: 3>, <Units.MINUTE: 2>}
print(r.has_time) # True
print(r.min_level) # Units.MINUTE (finest level present)
print(r.max_level) # Units.DAY (coarsest level present)
# Filter: only accept results that include at least HOUR precision
# min_level=Units.HOUR means "the finest level must be HOUR or finer"
r = parse_date("June 2024", min_level=Units.HOUR)
print(r) # None — "June 2024" only has MONTH precision, no time
r = parse_date("tomorrow at 3pm", min_level=Units.HOUR)
print(r.start_date) # matches — has HOUR
# Filter: only accept results no coarser than MONTH
# max_level=Units.MONTH means "the coarsest level must be MONTH or finer"
r = parse_date("2024", max_level=Units.MONTH)
print(r) # None — "2024" has YEAR as its coarsest level
r = parse_date("June 2024", max_level=Units.MONTH)
print(r.start_date) # matches — coarsest level is MONTH
# Multi-date extraction with level filtering
text = "Sometime in 2024, specifically June 15 at 10am"
results = parse_date(text, multi=True, min_level=Units.DAY)
# Only returns "June 15 at 10am", skips "2024" (no day precision)
CLI
# Parse a single expression
metadate tomorrow at 3pm
# Parse with a specific locale
metadate --lang nl overmorgen om 15 uur
# Distance between two dates
metadate "January 1" "March 15"
Supported expressions
| Category | Examples |
|---|---|
| Today/tomorrow | today, tomorrow, yesterday, day after tomorrow |
| Weekdays | next tuesday, last friday, this wednesday |
| Months | June, June 2024, June 25, June 25, 2024 |
| Relative | 3 days ago, in 2 weeks, next month, last year |
| ISO dates | 2024-01-15, 2024-01-15T10:30:00 |
| Times | at 3pm, 10:30, 15:00:00 |
| Ranges | last 2 weeks, first 3 days of March |
| Seasons | winter, next spring, last summer |
| Quarters | Q1, Q3 2024, last quarter |
| Combined | next tuesday at 3pm, June 25 2024 10:30am |
License
Metadata
Release files for metadate 0.6.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| metadate-0.6.2.tar.gz | 55.5 kB | Details |
Built distributions (wheels)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| metadate-0.6.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.13 | CPython 3.13 | Linux glibc 2.17+ x86-64, Linux glibc 2.5+ x86-64 | Details |
| metadate-0.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.12 | CPython 3.12 | Linux glibc 2.17+ x86-64, Linux glibc 2.5+ x86-64 | Details |
| metadate-0.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.11 | CPython 3.11 | Linux glibc 2.5+ x86-64, Linux glibc 2.17+ x86-64 | Details |
Total release size: 423.4 kB
Release files / metadate-0.6.2.tar.gz
| Download URL | metadate-0.6.2.tar.gz |
|---|---|
| Size | 55.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0780056ca92d285ed3e678b8b7b39590aac5fe836f4ee3cd40e85dd1a34c64e2
|
|
BLAKE2b-256 checksum How to use checksums |
ab0c0e2f78f9bc1c257e5e5df12c8bc11ce9170a4231a8e128cc596cbd575891
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 13, 2026.
Transparency logRelease files / metadate-0.6.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | metadate-0.6.2-cp313-cp313-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 123.2 kB |
| Tags | CPython 3.13 Linux glibc 2.17+ x86-64 Linux glibc 2.5+ x86-64 |
|
SHA-256 checksum How to use checksums |
f4e63cffa01c7cd450793873d463b8517c379bbe61145853dfd0197eff151e9f
|
|
BLAKE2b-256 checksum How to use checksums |
955bd25ee9edafb246356bba29521f1842da5b60d159799144e1e8cfa706fe1f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 13, 2026.
Transparency logRelease files / metadate-0.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | metadate-0.6.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 123.2 kB |
| Tags | CPython 3.12 Linux glibc 2.17+ x86-64 Linux glibc 2.5+ x86-64 |
|
SHA-256 checksum How to use checksums |
0e2348a6233c009df33752ff69afe5a7530a754f2ebef04db81f03d04b88c8bc
|
|
BLAKE2b-256 checksum How to use checksums |
1c84be74b3d7172071ae8d9d84c2ac41429de14e7971dff6b3a3827c984b8c64
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 13, 2026.
Transparency logRelease files / metadate-0.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | metadate-0.6.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 121.6 kB |
| Tags | CPython 3.11 Linux glibc 2.17+ x86-64 Linux glibc 2.5+ x86-64 |
|
SHA-256 checksum How to use checksums |
4fe8e579826f344b8aea521994cba366832fbf13a153149e7d81d7caeb242490
|
|
BLAKE2b-256 checksum How to use checksums |
39b925d97978d4054a08e24a3d0e55a5ced15292d02513531e985a76753c0de5
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.7
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Mar 13, 2026.
Transparency log