cheat sheet

python-dateutil

Package-level reference for python-dateutil on PyPI — parser, relativedelta, rrule, timezone, install, alternatives.

python-dateutil

What it is

python-dateutil is a long-running extension to the Python standard library datetime module. Its three flagship features are: a permissive date-string parser that accepts almost any human-readable format; relativedelta, an arithmetic object that supports calendar-aware deltas (add 1 month, not "approximately 30.4 days"); and rrule, a full RFC 5545 (iCalendar) recurrence-rule implementation for generating schedules.

Reach for python-dateutil when you need to: parse arbitrary date strings without knowing the format upfront; do calendar-aware arithmetic (month/year addition that handles end-of-month correctly); generate recurring schedules (every second Tuesday); or parse strings that the stdlib datetime.strptime would reject.

Install

bash
pip install python-dateutil

Output: (none — exits 0 on success; depends on six for legacy Python 2/3 compat)

bash
uv add python-dateutil

Output: dependency resolved + added to pyproject.toml

bash
poetry add python-dateutil

Output: updated lockfile + virtualenv install

The PyPI name is python-dateutil; the import name is dateutil. Always use the full PyPI name in install commands.

Versioning & Python support

  • Current line is the 2.9.x series. Releases are infrequent — major design has been stable for a decade.
  • Supports Python 3.7+ on recent releases; 3.x line dropped Python 2 in 2.8.2 (2021).
  • Updates mostly cover IANA tzdata refreshes (tzdata package now does this better) and stdlib API changes.
  • The library does NOT follow strict semver — 2.x minor bumps occasionally include behavior changes around ambiguous parsing.

Package metadata

  • Maintainer: Paul Ganssle, Yaron de Leeuw, et al.
  • Project home: github.com/dateutil/dateutil
  • Docs: dateutil.readthedocs.io
  • PyPI: pypi.org/project/python-dateutil
  • License: Apache-2.0 / BSD-3-Clause dual
  • Governance: community-maintained
  • First released: 2003
  • Downloads: consistently top 10 on PyPI (transitive via pandas, boto3, matplotlib)

Optional dependencies & extras

  • six — required at runtime for some legacy Python 2/3 compat shims. Pure Python.
  • No optional extras.

Alternatives

PackageTrade-off
Stdlib datetime + zoneinfoPython 3.9+ has IANA tz built-in. Use for new code where format is known.
arrowHigher-level wrapper. Nicer API but heavier. Many recent libraries use arrow as their datetime layer.
pendulumMost ambitious — drop-in datetime replacement with tz everywhere. Niche but loyal users.
wheneverModern (2024+) typed datetime library; strict semantics. Compelling for new code.
dateparserMore aggressive natural-language parsing ("yesterday", "in 3 days"). Slower than dateutil.

Common gotchas

  1. parser.parse() is permissive — sometimes too permissive. parse("9/4/2020") returns September 4 on US locale machines, April 9 elsewhere. Pass dayfirst=True or yearfirst=True to disambiguate.
  2. parse("12345") returns a datedateutil interprets it as an integer year/timestamp depending on context. Validate input length first.
  3. relativedelta adds calendar units; timedelta adds wall-clock seconds. today + relativedelta(months=1) gives Feb 28 from Jan 28 (or Feb 29 in leap years); today + timedelta(days=30) gives Jan 30 → Feb 27. Pick the right one.
  4. Timezone parsing requires a tzinfos mapping when the string contains a tz abbreviation (EST, PDT, …). Abbreviations are ambiguous — use dateutil.tz.gettz("America/New_York") directly.
  5. rrule is verbose but precise. Build it once and reuse; constructing per query is wasteful.
  6. tz.tzlocal() reads the OS timezone — in Docker without /etc/localtime you get UTC. In tests, freeze with tz.gettz("UTC").
  7. Two-digit years. parse("01/02/02") defaults to 2002. Set parserinfo(yearfirst=True) for ambiguous YY-MM-DD strings.

Real-world recipes

The five recipes target the daily-use surface — parsing, calendar arithmetic, timezones, and recurrence.

Recipe 1 — Parse arbitrary date strings.

python
from dateutil import parser

for s in [
    "2026-05-31T14:32:00Z",
    "May 31, 2026 2:32 PM",
    "31/5/2026 14:32",
    "2026-W22-7",
    "Tue, 31 May 2026 14:32:00 +0000",
]:
    print(repr(s), "->", parser.parse(s, dayfirst=True))

Output:

rust
'2026-05-31T14:32:00Z' -> 2026-05-31 14:32:00+00:00
'May 31, 2026 2:32 PM' -> 2026-05-31 14:32:00
'31/5/2026 14:32' -> 2026-05-31 14:32:00
'2026-W22-7' -> 2026-05-31 00:00:00
'Tue, 31 May 2026 14:32:00 +0000' -> 2026-05-31 14:32:00+00:00

dayfirst=True resolves DD/MM/YYYY ambiguity. The ISO week format YYYY-Www-D is also accepted.

Recipe 2 — Calendar-aware arithmetic with relativedelta.

python
from datetime import date
from dateutil.relativedelta import relativedelta, MO

today = date(2026, 5, 31)
print(today + relativedelta(months=1))                       # natural month
print(today + relativedelta(years=1, months=-2, days=+10))   # combined
print(today + relativedelta(day=31))                         # last day of THIS month
print(today + relativedelta(months=1, day=31))               # last day of next month
print(today + relativedelta(weekday=MO(+1)))                 # next Monday
print(today + relativedelta(weekday=MO(-1)))                 # previous Monday

Output:

yaml
2026-06-30
2027-04-10
2026-05-31
2026-06-30
2026-06-01
2026-05-25

day=N clamps to the maximum valid day; weekday=MO(+1) selects the next Monday. +1 means "the next one if today isn't already that weekday".

Recipe 3 — Timezone-aware parsing.

python
from dateutil import parser, tz

tzinfos = {"PST": tz.gettz("America/Los_Angeles"), "EST": tz.gettz("America/New_York")}
dt = parser.parse("2026-05-31 09:30 PST", tzinfos=tzinfos)
print(dt)                              # 09:30 in LA
print(dt.astimezone(tz.UTC))           # converted to UTC
print(dt.astimezone(tz.gettz("Asia/Tokyo")))

Output:

yaml
2026-05-31 09:30:00-07:00
2026-05-31 16:30:00+00:00
2026-06-01 01:30:00+09:00

Always pass tzinfos when abbreviations are present — otherwise the parser raises or returns naive datetimes.

Recipe 4 — Recurring schedule with rrule.

python
from datetime import date
from dateutil.rrule import rrule, MONTHLY, MO

# First Monday of each month for 6 occurrences
schedule = list(rrule(
    freq=MONTHLY,
    bymonthday=range(1, 8),
    byweekday=MO,
    dtstart=date(2026, 5, 1),
    count=6,
))
for d in schedule:
    print(d.date())

Output:

yaml
2026-05-04
2026-06-01
2026-07-06
2026-08-03
2026-09-07
2026-10-05

rrule is RFC 5545 — every iCalendar-supported recurrence pattern is expressible.

Recipe 5 — ISO 8601 round-trip.

python
from datetime import datetime, timezone
from dateutil import parser

original = datetime(2026, 5, 31, 14, 32, 0, tzinfo=timezone.utc)
s = original.isoformat()
parsed = parser.isoparse(s)             # stricter than .parse(); fails on non-ISO
print(s)
print(parsed)
print(parsed == original)

Output:

yaml
2026-05-31T14:32:00+00:00
2026-05-31 14:32:00+00:00
True

parser.isoparse() is strict ISO 8601 — prefer it over the permissive parse() when you control the input format.

Performance tuning

  • parser.parse() is slow by HTTP-handler standards (~50-200 µs per call). For high-volume input, prefer isoparse() (~5x faster) or stdlib datetime.strptime(fmt, s) when format is known.
  • Cache rrule objects — construction is moderately expensive; iteration is cheap.
  • Use tz.gettz("UTC") once per process — repeated lookups parse the OS tz file each time.
  • Don't import dateutil in cold-start-sensitive code (Lambda) — the import itself is ~30 ms. Lazy-import inside the function body.

Version migration guide

  • 2.7 → 2.8 — Python 2 EOL bookkeeping; some relativedelta edge cases tightened.
  • 2.8.0 → 2.8.2 — Python 2 support dropped; minimum Python 3.6 then 3.7.
  • 2.8.2 → 2.9.0 — minimum Python 3.7. parser.parse tightened around ambiguous 2-digit years.
python
# Pre-2.9 — sometimes returned naive datetime for inputs with explicit Z
parser.parse("2026-05-31T00:00:00Z")     # tz=tzutc()
# 2.9+ — same behavior, but isoparse() is now the documented strict path
parser.isoparse("2026-05-31T00:00:00Z")  # tz=tzutc()

Output: stable across the 2.x line; prefer isoparse for ISO-only input.

Security considerations

  • parser.parse() can be slow on adversarial input. Pathological strings ("123" × N) can spend disproportionate CPU. Cap input length for untrusted sources.
  • Timezone abbreviations are ambiguousEST is both US Eastern Standard and Australian Eastern Standard. Never trust tzinfos defaults from untrusted input.
  • parse(default=) lets you seed missing fields. If the string is "Monday", the date defaults to the current Monday — surprising and exploitable in scheduling.
  • fuzzy=True parses date-like substrings out of free-form text. Powerful and dangerous: a log line containing "1 of 12345 errors" might parse to year 12345.

Testing & CI

python
from datetime import date
from dateutil.relativedelta import relativedelta

def test_month_end_arithmetic():
    assert date(2026, 1, 31) + relativedelta(months=1) == date(2026, 2, 28)
    assert date(2024, 1, 31) + relativedelta(months=1) == date(2024, 2, 29)  # leap year

def test_iso_roundtrip():
    from dateutil import parser
    from datetime import datetime, timezone
    dt = datetime.now(timezone.utc).replace(microsecond=0)
    assert parser.isoparse(dt.isoformat()) == dt

Output: asserts hold across versions; protects against accidental regressions on the calendar-arithmetic behavior.

For tz tests, freeze the system clock with freezegun:

python
# pip install freezegun
from freezegun import freeze_time

@freeze_time("2026-05-31T12:00:00+00:00")
def test_today():
    from datetime import datetime, timezone
    assert datetime.now(timezone.utc).date().isoformat() == "2026-05-31"

Output: test passes regardless of CI machine clock.

Ecosystem integrations

  • pandas — uses dateutil.parser for pd.to_datetime.
  • boto3 — uses it for AWS response timestamps.
  • matplotlib — uses it for axis date parsing.
  • requests — uses email.utils.parsedate_to_datetime instead, but many wrappers use dateutil.
  • zoneinfo (stdlib 3.9+) — alternative tz source; pair with dateutil for tz-aware parsing.
  • tzdata — runtime IANA tz data, especially useful on Windows where the OS tz database is incomplete.

Compatibility matrix

Pythonpython-dateutilNotes
2.72.8.2 (frozen)Final supported version.
3.62.8.2 (frozen)Final supported version.
3.72.9.xLowest current floor.
3.82.9.xStable.
3.92.9.xStable.
3.102.9.xStable.
3.112.9.xStable.
3.122.9.xStable.
3.132.9.xStable; wheel universal.

Production deployment

  • Pin a minimum version in libraries (python-dateutil>=2.8.2) but allow flexibility — the library updates infrequently.
  • Install tzdata alongside python-dateutil on Alpine / Windows containers where the OS tz database is missing or incomplete.
  • Set TZ=UTC in container environments — predictable behavior trumps localization.
  • Audit parse() calls in input handlers — make sure dayfirst / yearfirst is set deterministically.
  • Reach for zoneinfo (stdlib) when targeting 3.9+ — it's faster than dateutil.tz for repeated lookups.

When NOT to use this

  • You know the formatdatetime.strptime("2026-05-31", "%Y-%m-%d") is ~10× faster than parser.parse().
  • You're on 3.9+ and need IANA timezones only — stdlib zoneinfo covers it without an extra dep.
  • You need natural-language parsing ("tomorrow at 5") — use dateparser instead.
  • You want strict semver / typed datetimes — try whenever (newer, smaller, strictly typed).

Troubleshooting common errors

Error / SymptomLikely causeFix
ValueError: Unknown string formatInput not date-shaped at allValidate first; reject obvious garbage.
parse("9/4/2020") returns wrong monthDD/MM vs MM/DD ambiguityPass dayfirst=True or yearfirst=True.
Tz abbreviation parsing returns naive datetimeAbbreviation not in tzinfosBuild a tzinfos dict mapping abbreviations to IANA zones.
rrule schedule has zero entriesMismatched bymonthday/byweekdayInspect with list(rrule(...))[:10]; verify constraints intersect.
Tests behave differently in CI vs localContainer has no tz dataInstall tzdata (PyPI) and set TZ=UTC.
Python 3.13 import warningDeprecated alias usedSwitch to documented imports under dateutil.parser / dateutil.rrule.

See also

  • Python: datetime — stdlib datetime fundamentals
  • Official python-dateutil docs
  • RFC 5545 — iCalendar recurrence rules