A comprehensive Python library for date and time manipulation, parsing, formatting, and calculations. Provides intuitive interfaces for common date operations and timezone handling.
Project description
DateUtils Python
A comprehensive collection of date and time utilities for Python applications.
Features
- UTC Operations: Easily work with UTC timestamps and dates
- Date/Time Parsing: Parse dates and times from strings in various formats
- Timezone Handling: Convert between timezones with robust error handling
- Business Days: Calculate business days with support for holidays and validation
- Quarter/Month/Week Operations: Helpers for working with larger time spans
- Date Formatting: Convert dates to various string formats including ISO 8601
- Human-readable Dates: Generate user-friendly date strings like "2 hours ago"
- Additional Utilities: Age calculation, weekend detection, week numbers, and more
- Memory Efficiency: Generator-based functions for large date ranges
- Input Validation: Comprehensive validation with helpful error messages
Why dateutils-python?
There are several excellent date/time libraries in the Python ecosystem. Here's how dateutils-python compares:
| Feature | dateutils-python | python-dateutil | arrow | pendulum |
|---|---|---|---|---|
| Zero dependencies | Yes | Yes | No (1 dep) | No (3 deps) |
| Business day calculations | Built-in | No | No | No |
| US Federal holidays | Built-in | No | No | No |
| Fully typed | Yes | Partial | Partial | Yes |
| Human-readable output | Yes | No | Yes | Yes |
| Timezone handling | Yes | Yes | Yes | Yes |
| Date parsing | Multi-format | Powerful/fuzzy | ISO-focused | ISO-focused |
| Relative date parsing | No | Yes ("next Friday") | Yes | Yes |
| Maintained | Active | Active | Active | Active |
Choose dateutils-python if you want:
- Zero dependency overhead - Uses only Python's standard library. No transitive dependencies to audit, no version conflicts, no supply chain concerns.
- Business day logic out of the box - Calculate workdays, skip holidays, find next/previous business day without additional libraries.
- US Federal holidays included - No need to maintain your own holiday list or pull in another package.
- Full type coverage - Every function is fully typed for IDE autocompletion and static analysis.
- Lightweight and focused - Does common date operations well without trying to be everything.
Consider alternatives if you need:
- Fuzzy date parsing ("next tuesday", "in 2 weeks") - Use
python-dateutil - Immutable date objects with method chaining - Use
pendulum - Drop-in datetime replacement - Use
arroworpendulum - Extensive internationalization - Use
pendulumorbabel
Installation
pip install dateutils-python
Usage Examples
UTC and Timestamp Operations
from dateutils import utc_now_seconds, utc_today, epoch_s
# Get current UTC timestamp
timestamp = utc_now_seconds() # e.g., 1717682345
# Get current UTC date
today = utc_today() # e.g., datetime.date(2024, 6, 6)
# Convert a datetime to timestamp
from datetime import datetime
dt = datetime(2024, 6, 6, 12, 30, 0)
ts = epoch_s(dt) # 1717677000
Business Day Calculations
from dateutils import (
workdays_between, add_business_days, next_business_day,
previous_business_day, is_business_day
)
from datetime import date
# Calculate workdays between dates
start = date(2024, 6, 1)
end = date(2024, 6, 10)
workdays = workdays_between(start, end) # Excludes weekends
# With holiday support
holidays = [date(2024, 6, 5)] # Example holiday
workdays = workdays_between(start, end, holidays) # Excludes weekends and holidays
# Add business days
new_date = add_business_days(start, 5) # Skip weekends
new_date = add_business_days(start, 5, holidays) # Skip weekends and holidays
# Check if a date is a business day
monday = date(2024, 6, 3)
is_business = is_business_day(monday) # True
is_business_with_holiday = is_business_day(monday, holidays=[monday]) # False
# Get next/previous business day
next_bday = next_business_day(date(2024, 6, 7)) # Friday -> Monday
prev_bday = previous_business_day(date(2024, 6, 3)) # Monday -> Friday
Holiday Support
from dateutils import get_us_federal_holidays, get_us_federal_holidays_list
# Get all US federal holidays for a year (cached for performance)
holidays_2024 = get_us_federal_holidays(2024) # Returns 11 holidays
# Get observed holidays for business-day calculations (weekend holidays shift to Fri/Mon)
observed_2021 = get_us_federal_holidays(2021, observed=True)
# Get specific holiday types
fixed_holidays = get_us_federal_holidays(2024, ("NEW_YEARS_DAY", "CHRISTMAS"))
# Use with business day functions
workdays = workdays_between(start, end, holidays=holidays_2024)
US federal holiday calculations support 1971 and later. The calendar models rule
changes within that range, including Veterans Day's fourth-Monday-in-October
schedule from 1971 through 1977, MLK Day from 1986, and Juneteenth from 2021.
Years before 1971 raise ValueError rather than projecting modern rules backward.
Date Parsing and Formatting
from dateutils import ParseError, parse_date, parse_datetime, parse_iso8601, to_iso8601
# Parse a date string (tries multiple formats, raises ParseError on failure)
dt = parse_date("2024-06-06") # Returns date object
dt = parse_date("06/06/2024") # Also works
dt = parse_date("June 6, 2024") # Also works
# Ambiguous numeric dates follow the current locale by default. Use explicit
# ordering or reject ambiguous imports when the input source is untrusted.
dt = parse_date("03/04/2024", dayfirst=False)
dt = parse_date("03/04/2024", ambiguous="reject") # Raises ParseError
# Parse ISO 8601 datetime
dt = parse_iso8601("2024-06-06T12:30:45Z") # With timezone
dt = parse_iso8601("2024-06-06") # Date only
# Parse with explicit error handling
try:
parse_datetime("not a datetime")
except ParseError as e:
print(e) # Includes parser name, bad value, and parse reason
# Format as ISO 8601
iso_str = to_iso8601(dt) # e.g., "2024-06-06T12:30:45+00:00"
Timezone Handling
from dateutils import (
now_in_timezone, localize_datetime, convert_timezone, get_available_timezones,
get_timezone_offset, format_timezone_offset
)
# Get current time in a timezone
nyc_now = now_in_timezone("America/New_York")
# Timezone arguments also accept tzinfo instances
utc_now = now_in_timezone(timezone.utc)
# Convert between timezones
from datetime import datetime, timezone
utc_dt = datetime(2024, 6, 6, 12, 0, 0, tzinfo=timezone.utc)
tokyo_dt = convert_timezone(utc_dt, "Asia/Tokyo")
# Attach a timezone to a naive local time. DST edge cases require a policy.
meeting = localize_datetime(datetime(2024, 11, 3, 1, 30), "America/New_York", ambiguous="latest")
# Get available timezones
timezones = get_available_timezones() # List of all available timezone names
# Get timezone offset
offset = get_timezone_offset("America/New_York") # Returns timedelta
offset_str = format_timezone_offset("America/New_York") # Returns "-05:00" or "-04:00" (DST)
Quarter and Month Operations
from dateutils import (
date_to_quarter, date_to_start_of_quarter, get_quarter_start_end,
start_of_quarter, end_of_quarter, start_of_month, end_of_month
)
from datetime import datetime
# Get quarter for a date
dt = datetime(2024, 5, 15)
quarter = date_to_quarter(dt) # Returns 2 (Q2)
# Get start of quarter
start_date = date_to_start_of_quarter(dt) # Returns 2024-04-01
# Get quarter date range
start, end = get_quarter_start_end(2024, 2) # Q2 2024: (2024-04-01, 2024-06-30)
# Get precise quarter boundaries
q_start = start_of_quarter(2024, 2) # 2024-04-01 00:00:00
q_end = end_of_quarter(2024, 2) # 2024-06-30 23:59:59.999999
Additional Utility Functions
from dateutils import (
age_in_years, days_until_weekend, days_since_weekend,
get_week_number, next_occurrence, time_until_next_occurrence
)
from datetime import date, datetime
# Calculate age in years
birth_date = date(1990, 6, 15)
age = age_in_years(birth_date, date(2024, 6, 20)) # 34 years old
# Weekend-related functions
monday = date(2024, 6, 3)
days_to_weekend = days_until_weekend(monday) # 5 days until Saturday
days_from_weekend = days_since_weekend(monday) # 1 day since Sunday
# Get ISO week number
week_num = get_week_number(date(2024, 6, 6)) # Week number (1-53)
# Calculate time until next occurrence
target_time = datetime(2024, 6, 6, 15, 30, 0) # 3:30 PM today/tomorrow
time_delta = time_until_next_occurrence(target_time) # Time until next 3:30 PM
next_run = next_occurrence(target_time) # Datetime of the next 3:30 PM
Memory-Efficient Date Ranges
from dateutils import date_range, date_range_generator
from datetime import date
# Standard date range (good for small ranges)
start = date(2024, 1, 1)
end = date(2024, 1, 10)
dates = date_range(start, end) # Returns list of dates
# Memory-efficient generator (good for large ranges)
large_end = date(2024, 12, 31)
date_gen = date_range_generator(start, large_end) # Returns generator
# Process large ranges without memory issues
for dt in date_gen:
if dt.month == 6: # Only process June dates
print(dt)
break
Human-readable Dates
from dateutils import pretty_date
from datetime import datetime, timedelta
# Format a datetime as a human-readable string
now = datetime.now()
an_hour_ago = now - timedelta(hours=1)
a_day_ago = now - timedelta(days=1)
pretty_date(an_hour_ago) # "an hour ago"
pretty_date(a_day_ago) # "Yesterday"
Error Handling and Validation
The library now includes comprehensive input validation with helpful error messages:
Functions whose parameters and return values are dates treat a datetime input
as its local calendar date (value.date()). They return plain date values and
do not convert timezone-aware inputs to another timezone first.
from datetime import date
from zoneinfo import ZoneInfoNotFoundError
from dateutils import ParseError, now_in_timezone, parse_date, start_of_quarter, workdays_between
# Invalid quarter raises ValueError
try:
start_of_quarter(2024, 5) # Quarters must be 1-4
except ValueError as e:
print(e) # "Quarter must be between 1 and 4, got 5"
# Invalid date ordering raises ValueError
try:
workdays_between(date(2024, 6, 10), date(2024, 6, 1)) # end < start
except ValueError as e:
print(e) # "start_date (2024-06-10) must be <= end_date (2024-06-01)"
# Invalid parse raises ParseError
try:
parse_date("2024-99-99")
except ParseError as e:
print(e) # "Failed to parse date from '2024-99-99': ..."
# Invalid timezone names raise ZoneInfoNotFoundError
try:
now_in_timezone("Invalid/Timezone")
except ZoneInfoNotFoundError as e:
print(e) # e.g., "No time zone found with key Invalid/Timezone"
Performance Features
- Cached Holiday Calculations: US federal holidays are cached for better performance when called multiple times
- Memory-Efficient Generators: Use
date_range_generator()for large date ranges - Optimized UTC Operations: Improved performance for timestamp operations
- Input Validation: Early validation prevents expensive operations on invalid data
Running Tests
Makefile
make init # ensures uv is installed
make deps
make check
License
MIT License
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dateutils_python-0.8.1.tar.gz.
File metadata
- Download URL: dateutils_python-0.8.1.tar.gz
- Upload date:
- Size: 59.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6c615d642ae47c0de989491fab1f80a79b80b7c35ab83704087ac0b7974d9c38
|
|
| MD5 |
f31c84b1b2f595c4a6f51563f53dab51
|
|
| BLAKE2b-256 |
e6099a80711dbd1bf344b22513837b4e68db18cb86a477c54cf15b821ea42cd2
|
Provenance
The following attestation bundles were made for dateutils_python-0.8.1.tar.gz:
Publisher:
publish.yml on chetmancini/dateutils-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dateutils_python-0.8.1.tar.gz -
Subject digest:
6c615d642ae47c0de989491fab1f80a79b80b7c35ab83704087ac0b7974d9c38 - Sigstore transparency entry: 2174988055
- Sigstore integration time:
-
Permalink:
chetmancini/dateutils-python@7d72537cef46324c229b4d3a19fbfc7f7aa981ca -
Branch / Tag:
refs/tags/v0.8.1 - Owner: https://github.com/chetmancini
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d72537cef46324c229b4d3a19fbfc7f7aa981ca -
Trigger Event:
release
-
Statement type:
File details
Details for the file dateutils_python-0.8.1-py3-none-any.whl.
File metadata
- Download URL: dateutils_python-0.8.1-py3-none-any.whl
- Upload date:
- Size: 31.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.13
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4da2f6346ccf2a6836c8d44d097b6aa843b1be8e0237f68e71c63277ff8b1a19
|
|
| MD5 |
06d6e96209bfe0fda3a7bb21244857c9
|
|
| BLAKE2b-256 |
d42340621f68c1a7712d2f720ee032058ed49d7106f8a4e69ca934ec98364697
|
Provenance
The following attestation bundles were made for dateutils_python-0.8.1-py3-none-any.whl:
Publisher:
publish.yml on chetmancini/dateutils-python
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
dateutils_python-0.8.1-py3-none-any.whl -
Subject digest:
4da2f6346ccf2a6836c8d44d097b6aa843b1be8e0237f68e71c63277ff8b1a19 - Sigstore transparency entry: 2174988632
- Sigstore integration time:
-
Permalink:
chetmancini/dateutils-python@7d72537cef46324c229b4d3a19fbfc7f7aa981ca -
Branch / Tag:
refs/tags/v0.8.1 - Owner: https://github.com/chetmancini
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@7d72537cef46324c229b4d3a19fbfc7f7aa981ca -
Trigger Event:
release
-
Statement type: