Parse Elasticsearch-style date math in Python with clean timezone-aware results.
Date math is the compact syntax used by systems like Elasticsearch, Splunk and Solr for expressions such as now-15m, now/d, or 2024-01-01||+1M. python-datemath brings that same style to Python and returns proper timezone-aware values you can keep as Arrow, convert to datetime, or render as timestamps.
- Familiar Elasticsearch, Splunk, Datadog and Solr-style expressions
- Timezone-aware parsing and arithmetic
- Support for rounding like
/d,/w, and/M - Works with
Arrow,datetime, and unix timestamps - Small library, simple API, no framework required
pip install python-datemathPython 3.10+ is supported.
from datemath import dm, datemath
print(dm("now-15m"))
print(dm("now/d+8h"))
print(datemath("2024-01-01||+1M"))
print(dm("now", tz="US/Eastern"))now-1h one hour ago
now+1d/d tomorrow, rounded to the start of day
now/w+2d two days after the start of this week
2024-01-01||+1M one month after an anchored date
2024-01-01||/M start of that month
2024-01-01T12:00:00-05:00||+2h
preserve an explicit offset from the source string
Date math is shorthand arithmetic for finding times relative to a fixed moment.
An expression is made of:
- An anchor, either
nowor a timestamp ending in|| - Zero or more operations using
+,-, or/ - A supported time unit such as year, month, week, day, hour, minute, or second
Examples:
now+1hnow+1h+1mnow+1h/d2012-01-01||+1M/d
When roundDown=False, rounding uses the end of the interval instead of the beginning.
y or Y year
M month
w or W week
d or D day
h or H hour
m minute
s or S second
now current date and time
dm() returns an Arrow object by default.
>>> from datemath import dm
>>>
>>> dm("now+1h")
<Arrow [2016-01-01T01:00:00+00:00]>
>>> dm("now+1h+1m")
<Arrow [2016-01-01T01:01:00+00:00]>
>>> dm("now+1h/d")
<Arrow [2016-01-02T00:00:00+00:00]>
>>> dm("now-1d")
<Arrow [2015-12-31T00:00:00+00:00]>
>>> dm("2016-01-01||+1/d")
<Arrow [2016-01-02T00:00:00+00:00]>
>>> dm("now/d+2h+3m")
<Arrow [2016-01-01T02:03:00+00:00]>
>>> dm("now+/d", roundDown=False)
<Arrow [2016-01-01T23:59:00+00:00]>
>>> dm("now/d")
<Arrow [2016-01-01T00:00:00+00:00]>
>>> dm(1451610061)
<Arrow [2016-01-01T01:01:01+00:00]>
>>> dm("1451610061")
<Arrow [2016-01-01T01:01:01+00:00]>If you want a string, use Arrow's .format() method.
>>> from datemath import dm
>>>
>>> src_timestamp = dm("2016-01-01")
>>> src_timestamp
<Arrow [2016-01-01T00:00:00+00:00]>
>>>
>>> new_timestamp = dm("-2w", now=src_timestamp)
>>> new_timestamp
<Arrow [2015-12-18T00:00:00+00:00]>
>>>
>>> new_timestamp.format("YYYY.MM.DD")
'2015.12.18'Use datemath() if you want a standard Python datetime.
>>> from datemath import datemath
>>>
>>> datemath("now-1h")
datetime.datetime(2015, 12, 31, 23, 0, tzinfo=tzutc())
>>> str(datemath("now-1h"))
'2015-12-31 23:00:00+00:00'
>>> datemath("2016-01-01T16:20:00||/d")
datetime.datetime(2016, 1, 1, 0, 0, tzinfo=tzutc())
>>> datemath("2016-01-01T16:20:00||/d", roundDown=False)
datetime.datetime(2016, 1, 1, 23, 59, 59, 999999, tzinfo=tzutc())You can also use dm() with type="datetime":
>>> from datemath import dm
>>>
>>> dm("now", type="datetime")
datetime.datetime(2016, 1, 22, 22, 58, 28, 338060, tzinfo=tzutc())
>>> dm("now+2d-1m", type="datetime")
datetime.datetime(2016, 1, 24, 22, 57, 45, 394470, tzinfo=tzutc())>>> from datemath import dm
>>>
>>> dm("now+2d-1m", type="timestamp")
1453676321By default, returned values are in UTC.
If you want a different timezone, pass the tz argument:
>>> from datemath import dm
>>>
>>> dm("now")
<Arrow [2016-01-26T01:00:53.601088+00:00]>
>>> dm("now", tz="US/Eastern")
<Arrow [2016-01-25T20:01:05.976880-05:00]>
>>> dm("now", tz="US/Pacific")
<Arrow [2016-01-25T17:01:18.456882-08:00]>
>>> dm("2017-10-20 09:15:20", tz="US/Pacific")
<Arrow [2017-10-20T09:15:20.000000-08:00]>If a timestamp string already contains an explicit timezone offset, that offset is preserved:
>>> dm("2016-01-01T00:00:00-05:00")
<Arrow [2016-01-01T00:00:00-05:00]>
>>> dm("2016-01-01T00:00:00-05:00||+2d+3h+5m")
<Arrow [2016-01-03T03:05:00-05:00]>
>>> dm("2016-01-01T00:00:00-05:00", tz="US/Central")
<Arrow [2016-01-01T00:00:00-05:00]>Assuming our current time is 2016-01-01T00:00:00+00:00:
Expression Result
now-1h 2015-12-31T23:00:00+00:00
now-1y 2015-01-01T00:00:00+00:00
now+1y+2d 2017-01-03T00:00:00+00:00
now+12h 2016-01-01T12:00:00+00:00
now+1d/d 2016-01-03T00:00:00+00:00
now-2.5h 2015-12-31T21:30:00+00:00
+2h 2016-01-01T02:00:00+00:00
+1h/h 2016-01-01T02:00:00+00:00
now+1w/w 2016-01-11T00:00:00+00:00
now/d+7d+12h 2016-01-08T12:00:00+00:00
2016-01-01||+1d 2016-01-02T00:00:00+00:00
2015-01-01||+2w 2015-01-15T00:00:00+00:00
roundDown=False
now/d 2016-01-01T23:59:59+00:00
now/Y 2016-12-31T23:59:59+00:00pip install python-datemathuv add python-datemathuv sync --group devuv run pre-commit install
uv run pre-commit install --hook-type pre-pushThis project uses pyproject.toml for packaging and uv for local workflows.
uv sync --group dev
uv run pre-commit run --all-files
uv run pytest
uv run ruff check .
uv run mypy
uv buildIf you prefer make, the common tasks are wrapped there too:
make sync
make check
make build
make precommit-install
make precommit-runReleases now use python-semantic-release with configuration in .releaserc.toml.
- Merge conventional commits like
feat: ...,fix: ..., orperf: ...intomaster - The
Semantic Releaseworkflow calculates the next version, updatesdatemath/_version.py, updatesCHANGELOG.md, tags the release, and creates the GitHub release - The existing publish workflow then builds and uploads the package to PyPI from that GitHub release
Useful local commands:
uv run semantic-release -c .releaserc.toml version --print
make release-checkIf you want verbose debug output, set DATEMATH_DEBUG=true in your shell before running examples or tests:
export DATEMATH_DEBUG=true
uv run pytest
unset DATEMATH_DEBUGSee CHANGELOG.md.
Built for people who think that dates and time should be easier to use in python