mrkeyoor.com_
Wed 05 Aug 19:51 UTC
PyPIUtilsupdated 05 Aug 2026

python-dateutil

python-dateutil extends the standard datetime module with the pieces it never grew: a parser that reads dates out of almost any string, relativedelta for calendar-aware arithmetic (next month, last Friday of the month), rrule for iCalendar-style recurrence rules, timezone implementations that read tzfile and TZ strings, and an Easter calculator. It has been around since 2003, is imported (as dateutil, not python-dateutil) by a huge share of the Python ecosystem, and pandas depends on it, which is a big part of its download numbers.

Verdict

Keep it for the three things nothing else does as well: fuzzy string parsing, relativedelta arithmetic, and rrule recurrences. For plain timezone handling or ISO parsing on modern Python, the standard library has caught up and you do not need the dependency.

API stability5/5The 2.x API has been essentially unchanged for a decade; code written against parse, relativedelta, and rrule in 2015 still runs today, and breaking changes are close to nonexistent.
Docs3/5dateutil.readthedocs.io documents every module with examples, but it is reference-style with little guidance on pitfalls like ambiguous parsing defaults or when to prefer stdlib zoneinfo.
Maintenance2/5Releases arrive years apart (2.8.2 in 2021, 2.9.0 in March 2024, nothing since), 469 issues and PRs sit open, and the repo's last push was May 2026; it works because it is finished, not because it is tended.
Ecosystem5/5Roughly 280M weekly downloads, a pandas dependency, and 20+ years of Stack Overflow answers; nearly every Python developer has it installed whether they know it or not.

Use it if

  • You receive dates as messy strings ('Sat Oct 11 17:13:46 UTC 2003', '4th of July 2026') and need one call that copes with most formats
  • You need calendar arithmetic the timedelta class cannot do: add one month, jump to the last weekday of a month, or compute a human-style year/month/day difference between two dates
  • You need recurrence rules ('every second Tuesday until December') and want the RFC 5545 superset that rrule implements, including parsing RRULE strings from calendar feeds
  • You maintain code that must run on old Python: the package still supports back to 2.7, which almost nothing else does
Skip it if

Setup reality

pip install python-dateutil, then import dateutil; the mismatch between install name and import name trips up every requirements scanner and some new users. It is pure Python with no compiled parts, but it installs six (a Python 2/3 shim) as a dependency in 2026, which security scanners occasionally flag as a smell. Timezone data comes from the OS tzfile database plus a bundled fallback, so results on minimal Docker images can differ from your laptop unless tzdata is installed.

Patterns

Parse a date out of almost any stringparse-any-date-string

from dateutil import parser

parser.parse("Sat Oct 11 17:13:46 UTC 2003")
# datetime.datetime(2003, 10, 11, 17, 13, 46, tzinfo=tzutc())
parser.parse("4th of July 2026")
# datetime.datetime(2026, 7, 4, 0, 0)

Fields missing from the string are filled from today's date at 00:00, so parse('12:30') returns today at 12:30; pass default= to control that.

Handle DD/MM/YYYY dates explicitlyparse-day-first

from dateutil import parser

parser.parse("10/09/2003")                 # Oct 9 (US order)
parser.parse("10/09/2003", dayfirst=True)  # Sep 10

The default is month-first, silently. Any code parsing European-style dates without dayfirst=True has a latent bug that only shows on days 1-12.

Parse ISO 8601 strictlystrict-iso-parse

from dateutil import parser

parser.isoparse("2026-08-05T14:30:00+05:30")
# datetime with tzoffset(None, 19800)

isoparse rejects non-ISO input instead of guessing and is faster than parse(). On Python 3.11+ datetime.fromisoformat does the same job without the dependency.

Parse named timezone abbreviationsparse-timezone-abbreviations

from dateutil import parser, tz

tzinfos = {"IST": tz.gettz("Asia/Kolkata"), "CST": tz.gettz("America/Chicago")}
parser.parse("2026-08-05 14:30 IST", tzinfos=tzinfos)

Only UTC/GMT and numeric offsets are understood out of the box; unknown abbreviations like IST produce a naive datetime plus a warning unless you supply tzinfos.

Add months or years correctlyadd-months

from datetime import date
from dateutil.relativedelta import relativedelta

date(2026, 1, 31) + relativedelta(months=+1)  # date(2026, 2, 28)
date(2026, 8, 5) + relativedelta(years=+1, months=+2)

relativedelta clamps to the last valid day instead of overflowing, which timedelta cannot express at all. Plural arguments (months=) shift; singular (month=) set an absolute value.

Jump to the next or last weekdaynext-weekday

from datetime import date
from dateutil.relativedelta import relativedelta, FR

date(2026, 8, 5) + relativedelta(weekday=FR)       # next Friday
date(2026, 8, 5) + relativedelta(day=31, weekday=FR(-1))  # last Friday of month

weekday=FR includes the start date if it already is a Friday; use FR(+1) semantics carefully when 'strictly next' matters.

Human-style difference between two datescalendar-diff

from datetime import date
from dateutil.relativedelta import relativedelta

delta = relativedelta(date(2026, 8, 5), date(1994, 3, 11))
print(delta.years, delta.months, delta.days)  # 32 4 25

This is the correct way to compute an age in years/months/days; subtracting dates gives only a day count.

Generate recurring dates with rrulerecurrence-rule

from datetime import datetime
from dateutil.rrule import rrule, WEEKLY, MO, WE

list(rrule(WEEKLY, byweekday=(MO, WE), count=4,
           dtstart=datetime(2026, 8, 3)))
# Mon 3rd, Wed 5th, Mon 10th, Wed 12th

Without dtstart the rule starts from now including the current time, which makes results non-deterministic in tests; always pass dtstart.

Parse an RRULE string from a calendar feedparse-ical-rrule

from datetime import datetime
from dateutil.rrule import rrulestr

rule = rrulestr("FREQ=MONTHLY;BYDAY=2TU;COUNT=6",
                dtstart=datetime(2026, 8, 1))
rule.between(datetime(2026, 8, 1), datetime(2027, 1, 1))

between() excludes both endpoints by default; pass inc=True to include them. This bites everyone once.

Build timezone-aware datetimestimezone-aware-now

from datetime import datetime
from dateutil import tz

nyc = tz.gettz("America/New_York")
datetime(2026, 11, 1, 1, 30, tzinfo=nyc, fold=1)  # DST-ambiguous time
datetime.now(tz.UTC)

gettz reads the OS zone database with a bundled fallback. On Python 3.9+ stdlib zoneinfo.ZoneInfo does this without dateutil; use it in new code.

Alternatives

PackageRegistryPick it when
arrowPyPIYou want a friendlier all-in-one datetime object with humanize() and span helpers rather than extensions to stdlib datetime.
pendulumPyPIYou want a datetime drop-in subclass with sane timezone defaults and duration formatting; check its release activity first.
wheneverPyPIYou want a strict, typed API that makes naive vs aware mistakes impossible and is fast (Rust-backed).